Bug 989414 - Access the store buffer through the chunk trailer; r=jonco
☠☠ backed out by e5a6e3bfa4be ☠ ☠
authorTerrence Cole <terrence@mozilla.com>
Thu, 01 May 2014 09:26:12 -0700
changeset 181622 9a6b434b34bec675539d12ce7566bc76f01ceaf1
parent 181621 c5aae1b3dc3fadfdd5914fabf1a17c4f91452223
child 181623 d811c553a16540a5dd7dd9c6194c6ffa90dd2e26
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersjonco
bugs989414
milestone32.0a1
Bug 989414 - Access the store buffer through the chunk trailer; r=jonco
js/public/HeapAPI.h
js/src/gc/Barrier.cpp
js/src/gc/Barrier.h
js/src/gc/Heap.h
js/src/gc/Nursery.h
js/src/gc/StoreBuffer.cpp
js/src/gc/StoreBuffer.h
js/src/jsgc.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/vm/ObjectImpl.cpp
js/src/vm/ObjectImpl.h
js/src/vm/Shape.h
js/src/vm/Stack.cpp
--- a/js/public/HeapAPI.h
+++ b/js/public/HeapAPI.h
@@ -36,20 +36,20 @@ const size_t ChunkShift = 20;
 const size_t ChunkSize = size_t(1) << ChunkShift;
 const size_t ChunkMask = ChunkSize - 1;
 
 const size_t CellShift = 3;
 const size_t CellSize = size_t(1) << CellShift;
 const size_t CellMask = CellSize - 1;
 
 /* These are magic constants derived from actual offsets in gc/Heap.h. */
-const size_t ChunkMarkBitmapOffset = 1032360;
+const size_t ChunkMarkBitmapOffset = 1032352;
 const size_t ChunkMarkBitmapBits = 129024;
 const size_t ChunkRuntimeOffset = ChunkSize - sizeof(void*);
-const size_t ChunkLocationOffset = ChunkSize - sizeof(void*) - sizeof(uintptr_t);
+const size_t ChunkLocationOffset = ChunkSize - 2 * sizeof(void*) - sizeof(uint64_t);
 
 /*
  * Live objects are marked black. How many other additional colors are available
  * depends on the size of the GCThing. Objects marked gray are eligible for
  * cycle collection.
  */
 static const uint32_t BLACK = 0;
 static const uint32_t GRAY = 1;
--- a/js/src/gc/Barrier.cpp
+++ b/js/src/gc/Barrier.cpp
@@ -37,22 +37,22 @@ bool
 HeapSlot::preconditionForSet(Zone *zone, JSObject *owner, Kind kind, uint32_t slot)
 {
     bool ok = kind == Slot
             ? &owner->getSlotRef(slot) == this
             : &owner->getDenseElement(slot) == (const Value *)this;
     return ok && owner->zone() == zone;
 }
 
-void
-HeapSlot::preconditionForWriteBarrierPost(JSObject *obj, Kind kind, uint32_t slot, Value target)
+bool
+HeapSlot::preconditionForWriteBarrierPost(JSObject *obj, Kind kind, uint32_t slot, Value target) const
 {
-    JS_ASSERT_IF(kind == Slot, obj->getSlotAddressUnchecked(slot)->get() == target);
-    JS_ASSERT_IF(kind == Element,
-                 static_cast<HeapSlot *>(obj->getDenseElements() + slot)->get() == target);
+    return kind == Slot
+         ? obj->getSlotAddressUnchecked(slot)->get() == target
+         : static_cast<HeapSlot *>(obj->getDenseElements() + slot)->get() == target;
 }
 
 bool
 RuntimeFromMainThreadIsHeapMajorCollecting(JS::shadow::Zone *shadowZone)
 {
     return shadowZone->runtimeFromMainThread()->isHeapMajorCollecting();
 }
 #endif // DEBUG
--- a/js/src/gc/Barrier.h
+++ b/js/src/gc/Barrier.h
@@ -228,19 +228,19 @@ class BarrieredCell : public gc::Cell
             MOZ_ASSERT(!RuntimeFromMainThreadIsHeapMajorCollecting(shadowZone));
             T *tmp = thing;
             js::gc::MarkUnbarriered<T>(shadowZone->barrierTracer(), &tmp, "write barrier");
             JS_ASSERT(tmp == thing);
         }
 #endif
     }
 
-    static void writeBarrierPost(T *thing, void *addr) {}
-    static void writeBarrierPostRelocate(T *thing, void *addr) {}
-    static void writeBarrierPostRemove(T *thing, void *addr) {}
+    static void writeBarrierPost(T *thing, void *cellp) {}
+    static void writeBarrierPostRelocate(T *thing, void *cellp) {}
+    static void writeBarrierPostRemove(T *thing, void *cellp) {}
 };
 
 } // namespace gc
 
 // Note: the following Zone-getting functions must be equivalent to the zone()
 // and shadowZone() functions implemented by the subclasses of BarrieredCell.
 
 JS::Zone *
@@ -334,47 +334,58 @@ struct InternalGCMethods<Value>
     static bool isMarkable(Value v) { return v.isMarkable(); }
 
     static void preBarrier(Value v) {
 #ifdef JSGC_INCREMENTAL
         if (v.isMarkable() && shadowRuntimeFromAnyThread(v)->needsBarrier())
             preBarrier(ZoneOfValueFromAnyThread(v), v);
 #endif
     }
+
     static void preBarrier(Zone *zone, Value v) {
 #ifdef JSGC_INCREMENTAL
         if (v.isString() && StringIsPermanentAtom(v.toString()))
             return;
         JS::shadow::Zone *shadowZone = JS::shadow::Zone::asShadowZone(zone);
         if (shadowZone->needsBarrier()) {
             JS_ASSERT_IF(v.isMarkable(), shadowRuntimeFromMainThread(v)->needsBarrier());
             Value tmp(v);
             js::gc::MarkValueUnbarriered(shadowZone->barrierTracer(), &tmp, "write barrier");
             JS_ASSERT(tmp == v);
         }
 #endif
     }
+
     static void postBarrier(Value *vp) {
 #ifdef JSGC_GENERATIONAL
-        if (vp->isObject())
-            shadowRuntimeFromAnyThread(*vp)->gcStoreBufferPtr()->putValue(vp);
+        if (vp->isObject()) {
+            gc::StoreBuffer *sb = reinterpret_cast<gc::Cell *>(&vp->toObject())->storeBuffer();
+            if (sb)
+                sb->putValueFromAnyThread(vp);
+        }
 #endif
     }
 
     static void postBarrierRelocate(Value *vp) {
 #ifdef JSGC_GENERATIONAL
-        shadowRuntimeFromAnyThread(*vp)->gcStoreBufferPtr()->putRelocatableValue(vp);
+        if (vp->isObject()) {
+            gc::StoreBuffer *sb = reinterpret_cast<gc::Cell *>(&vp->toObject())->storeBuffer();
+            if (sb)
+                sb->putRelocatableValueFromAnyThread(vp);
+        }
 #endif
     }
 
     static void postBarrierRemove(Value *vp) {
 #ifdef JSGC_GENERATIONAL
+        JS_ASSERT(vp);
+        JS_ASSERT(vp->isMarkable());
         JSRuntime *rt = static_cast<js::gc::Cell *>(vp->toGCThing())->runtimeFromAnyThread();
         JS::shadow::Runtime *shadowRuntime = JS::shadow::Runtime::asShadowRuntime(rt);
-        shadowRuntime->gcStoreBufferPtr()->removeRelocatableValue(vp);
+        shadowRuntime->gcStoreBufferPtr()->removeRelocatableValueFromAnyThread(vp);
 #endif
     }
 
     static void readBarrier(const Value &v) { ValueReadBarrier(v); }
 };
 
 template <>
 struct InternalGCMethods<jsid>
@@ -846,74 +857,53 @@ class HeapSlot : public BarrieredBase<Va
         pre();
     }
 
     void init(JSObject *owner, Kind kind, uint32_t slot, const Value &v) {
         value = v;
         post(owner, kind, slot, v);
     }
 
-    void init(JSRuntime *rt, JSObject *owner, Kind kind, uint32_t slot, const Value &v) {
-        value = v;
-        post(rt, owner, kind, slot, v);
-    }
-
 #ifdef DEBUG
     bool preconditionForSet(JSObject *owner, Kind kind, uint32_t slot);
     bool preconditionForSet(Zone *zone, JSObject *owner, Kind kind, uint32_t slot);
-    static void preconditionForWriteBarrierPost(JSObject *obj, Kind kind, uint32_t slot,
-                                                Value target);
+    bool preconditionForWriteBarrierPost(JSObject *obj, Kind kind, uint32_t slot, Value target) const;
 #endif
 
     void set(JSObject *owner, Kind kind, uint32_t slot, const Value &v) {
         JS_ASSERT(preconditionForSet(owner, kind, slot));
+        JS_ASSERT(!IsPoisonedValue(v));
         pre();
-        JS_ASSERT(!IsPoisonedValue(v));
         value = v;
         post(owner, kind, slot, v);
     }
 
     void set(Zone *zone, JSObject *owner, Kind kind, uint32_t slot, const Value &v) {
         JS_ASSERT(preconditionForSet(zone, owner, kind, slot));
-        JS::shadow::Zone *shadowZone = JS::shadow::Zone::asShadowZone(zone);
+        JS_ASSERT(!IsPoisonedValue(v));
         pre(zone);
-        JS_ASSERT(!IsPoisonedValue(v));
         value = v;
-        post(shadowZone->runtimeFromAnyThread(), owner, kind, slot, v);
+        post(owner, kind, slot, v);
     }
 
-    static void writeBarrierPost(JSObject *obj, Kind kind, uint32_t slot, const Value &target)
-    {
-#ifdef JSGC_GENERATIONAL
-        js::gc::Cell *cell = reinterpret_cast<js::gc::Cell*>(obj);
-        writeBarrierPost(cell->runtimeFromAnyThread(), obj, kind, slot, target);
-#endif
-    }
-
-    static void writeBarrierPost(JSRuntime *rt, JSObject *obj, Kind kind, uint32_t slot,
-                                 const Value &target)
-    {
-#ifdef DEBUG
-        preconditionForWriteBarrierPost(obj, kind, slot, target);
-#endif
-#ifdef JSGC_GENERATIONAL
-        if (target.isObject()) {
-            JS::shadow::Runtime *shadowRuntime = JS::shadow::Runtime::asShadowRuntime(rt);
-            shadowRuntime->gcStoreBufferPtr()->putSlot(obj, kind, slot, 1);
-        }
-#endif
+    /* For users who need to manually barrier the raw types. */
+    static void writeBarrierPost(JSObject *owner, Kind kind, uint32_t slot, const Value &target) {
+        reinterpret_cast<HeapSlot *>(const_cast<Value *>(&target))->post(owner, kind, slot, target);
     }
 
   private:
-    void post(JSObject *owner, Kind kind, uint32_t slot, Value target) {
-        HeapSlot::writeBarrierPost(owner, kind, slot, target);
-    }
-
-    void post(JSRuntime *rt, JSObject *owner, Kind kind, uint32_t slot, Value target) {
-        HeapSlot::writeBarrierPost(rt, owner, kind, slot, target);
+    void post(JSObject *owner, Kind kind, uint32_t slot, const Value &target) {
+        JS_ASSERT(preconditionForWriteBarrierPost(owner, kind, slot, target));
+#ifdef JSGC_GENERATIONAL
+        if (this->value.isObject()) {
+            gc::Cell *cell = reinterpret_cast<gc::Cell *>(&this->value.toObject());
+            if (cell->storeBuffer())
+                cell->storeBuffer()->putSlotFromAnyThread(owner, kind, slot, 1);
+        }
+#endif
     }
 };
 
 static inline const Value *
 Valueify(const BarrieredBase<Value> *array)
 {
     JS_STATIC_ASSERT(sizeof(HeapValue) == sizeof(Value));
     JS_STATIC_ASSERT(sizeof(HeapSlot) == sizeof(Value));
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -106,16 +106,18 @@ struct Cell
     inline JS::Zone *tenuredZoneFromAnyThread() const;
     inline bool tenuredIsInsideZone(JS::Zone *zone) const;
 
     // Note: Unrestricted access to the runtime of a GC thing from an arbitrary
     // thread can easily lead to races. Use this method very carefully.
     inline JSRuntime *runtimeFromAnyThread() const;
     inline JS::shadow::Runtime *shadowRuntimeFromAnyThread() const;
 
+    inline StoreBuffer *storeBuffer() const;
+
 #ifdef DEBUG
     inline bool isAligned() const;
     inline bool isTenured() const;
 #endif
 
   protected:
     inline uintptr_t address() const;
     inline Chunk *chunk() const;
@@ -603,25 +605,26 @@ ArenaHeader::getThingSize() const
  * The tail of the chunk info is shared between all chunks in the system, both
  * nursery and tenured. This structure is locatable from any GC pointer by
  * aligning to 1MiB.
  */
 struct ChunkTrailer
 {
     /* The index the chunk in the nursery, or LocationTenuredHeap. */
     uint32_t        location;
+    uint32_t        padding;
 
-#if JS_BITS_PER_WORD == 64
-    uint32_t        padding;
-#endif
+    /* The store buffer for writes to things in this chunk or nullptr. */
+    StoreBuffer     *storeBuffer;
 
     JSRuntime       *runtime;
 };
 
-static_assert(sizeof(ChunkTrailer) == 2 * sizeof(uintptr_t), "ChunkTrailer size is incorrect.");
+static_assert(sizeof(ChunkTrailer) == 2 * sizeof(uintptr_t) + sizeof(uint64_t),
+              "ChunkTrailer size is incorrect.");
 
 /* The chunk header (located at the end of the chunk to preserve arena alignment). */
 struct ChunkInfo
 {
     Chunk           *next;
     Chunk           **prevp;
 
     /* Free arenas are linked together with aheader.next. */
@@ -860,19 +863,23 @@ struct Chunk
     inline void addArenaToFreeList(JSRuntime *rt, ArenaHeader *aheader);
 };
 
 static_assert(sizeof(Chunk) == ChunkSize,
               "Ensure the hardcoded chunk size definition actually matches the struct.");
 static_assert(js::gc::ChunkMarkBitmapOffset == offsetof(Chunk, bitmap),
               "The hardcoded API bitmap offset must match the actual offset.");
 static_assert(js::gc::ChunkRuntimeOffset == offsetof(Chunk, info) +
-                                               offsetof(ChunkInfo, trailer) +
-                                               offsetof(ChunkTrailer, runtime),
+                                            offsetof(ChunkInfo, trailer) +
+                                            offsetof(ChunkTrailer, runtime),
               "The hardcoded API runtime offset must match the actual offset.");
+static_assert(js::gc::ChunkLocationOffset == offsetof(Chunk, info) +
+                                             offsetof(ChunkInfo, trailer) +
+                                             offsetof(ChunkTrailer, location),
+              "The hardcoded API location offset must match the actual offset.");
 
 inline uintptr_t
 ArenaHeader::address() const
 {
     uintptr_t addr = reinterpret_cast<uintptr_t>(this);
     JS_ASSERT(!(addr & ArenaMask));
     JS_ASSERT(Chunk::withinArenasRange(addr));
     return addr;
@@ -1092,16 +1099,22 @@ Chunk *
 Cell::chunk() const
 {
     uintptr_t addr = uintptr_t(this);
     JS_ASSERT(addr % CellSize == 0);
     addr &= ~(ChunkSize - 1);
     return reinterpret_cast<Chunk *>(addr);
 }
 
+inline StoreBuffer *
+Cell::storeBuffer() const
+{
+    return chunk()->info.trailer.storeBuffer;
+}
+
 inline bool
 InFreeList(ArenaHeader *aheader, void *thing)
 {
     if (!aheader->hasFreeThings())
         return false;
 
     FreeSpan firstSpan(aheader->getFirstFreeSpan());
     uintptr_t addr = reinterpret_cast<uintptr_t>(thing);
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -198,16 +198,17 @@ class Nursery
     NurseryChunkLayout &chunk(int index) const {
         JS_ASSERT(index < NumNurseryChunks);
         JS_ASSERT(start());
         return reinterpret_cast<NurseryChunkLayout *>(start())[index];
     }
 
     MOZ_ALWAYS_INLINE void initChunk(int chunkno) {
         NurseryChunkLayout &c = chunk(chunkno);
+        c.trailer.storeBuffer = JS::shadow::Runtime::asShadowRuntime(runtime())->gcStoreBufferPtr();
         c.trailer.location = gc::ChunkLocationNursery;
         c.trailer.runtime = runtime();
     }
 
     MOZ_ALWAYS_INLINE void setCurrentChunk(int chunkno) {
         JS_ASSERT(chunkno < NumNurseryChunks);
         JS_ASSERT(chunkno < numActiveChunks_);
         currentChunk_ = chunkno;
--- a/js/src/gc/StoreBuffer.cpp
+++ b/js/src/gc/StoreBuffer.cpp
@@ -330,49 +330,55 @@ StoreBuffer::addSizeOfExcludingThis(mozi
     sizes->storeBufferRelocVals  += bufferRelocVal.sizeOfExcludingThis(mallocSizeOf);
     sizes->storeBufferRelocCells += bufferRelocCell.sizeOfExcludingThis(mallocSizeOf);
     sizes->storeBufferGenerics   += bufferGeneric.sizeOfExcludingThis(mallocSizeOf);
 }
 
 JS_PUBLIC_API(void)
 JS::HeapCellPostBarrier(js::gc::Cell **cellp)
 {
+    JS_ASSERT(cellp);
     JS_ASSERT(*cellp);
-    JSRuntime *runtime = (*cellp)->runtimeFromMainThread();
-    runtime->gc.storeBuffer.putRelocatableCell(cellp);
+    StoreBuffer *storeBuffer = (*cellp)->storeBuffer();
+    if (storeBuffer)
+        storeBuffer->putRelocatableCellFromAnyThread(cellp);
 }
 
 JS_PUBLIC_API(void)
 JS::HeapCellRelocate(js::gc::Cell **cellp)
 {
-    /* Called with old contents of *pp before overwriting. */
+    /* Called with old contents of *cellp before overwriting. */
+    JS_ASSERT(cellp);
     JS_ASSERT(*cellp);
     JSRuntime *runtime = (*cellp)->runtimeFromMainThread();
-    runtime->gc.storeBuffer.removeRelocatableCell(cellp);
+    runtime->gc.storeBuffer.removeRelocatableCellFromAnyThread(cellp);
 }
 
 JS_PUBLIC_API(void)
 JS::HeapValuePostBarrier(JS::Value *valuep)
 {
+    JS_ASSERT(valuep);
     JS_ASSERT(valuep->isMarkable());
-    if (valuep->isString() && StringIsPermanentAtom(valuep->toString()))
-        return;
-    JSRuntime *runtime = static_cast<js::gc::Cell *>(valuep->toGCThing())->runtimeFromMainThread();
-    runtime->gc.storeBuffer.putRelocatableValue(valuep);
+    if (valuep->isObject()) {
+        StoreBuffer *storeBuffer = valuep->toObject().storeBuffer();
+        if (storeBuffer)
+            storeBuffer->putRelocatableValueFromAnyThread(valuep);
+    }
 }
 
 JS_PUBLIC_API(void)
 JS::HeapValueRelocate(JS::Value *valuep)
 {
     /* Called with old contents of *valuep before overwriting. */
+    JS_ASSERT(valuep);
     JS_ASSERT(valuep->isMarkable());
-    if (valuep->isString() && StringIsPermanentAtom(valuep->toString()))
+    if (valuep->isString() && valuep->toString()->isPermanentAtom())
         return;
     JSRuntime *runtime = static_cast<js::gc::Cell *>(valuep->toGCThing())->runtimeFromMainThread();
-    runtime->gc.storeBuffer.removeRelocatableValue(valuep);
+    runtime->gc.storeBuffer.removeRelocatableValueFromAnyThread(valuep);
 }
 
 template class StoreBuffer::MonoTypeBuffer<StoreBuffer::ValueEdge>;
 template class StoreBuffer::MonoTypeBuffer<StoreBuffer::CellPtrEdge>;
 template class StoreBuffer::MonoTypeBuffer<StoreBuffer::SlotsEdge>;
 template class StoreBuffer::MonoTypeBuffer<StoreBuffer::WholeCellEdges>;
 template class StoreBuffer::RelocatableMonoTypeBuffer<StoreBuffer::ValueEdge>;
 template class StoreBuffer::RelocatableMonoTypeBuffer<StoreBuffer::CellPtrEdge>;
--- a/js/src/gc/StoreBuffer.h
+++ b/js/src/gc/StoreBuffer.h
@@ -239,17 +239,18 @@ class StoreBuffer
     {
         Cell **edge;
 
         explicit CellPtrEdge(Cell **v) : edge(v) {}
         bool operator==(const CellPtrEdge &other) const { return edge == other.edge; }
         bool operator!=(const CellPtrEdge &other) const { return edge != other.edge; }
 
         bool maybeInRememberedSet(const Nursery &nursery) const {
-            return !nursery.isInside(edge) && nursery.isInside(*edge);
+            JS_ASSERT(nursery.isInside(*edge));
+            return !nursery.isInside(edge);
         }
 
         void mark(JSTracer *trc);
 
         CellPtrEdge tagged() const { return CellPtrEdge((Cell **)(uintptr_t(edge) | 1)); }
         CellPtrEdge untagged() const { return CellPtrEdge((Cell **)(uintptr_t(edge) & ~1)); }
         bool isTagged() const { return bool(uintptr_t(edge) & 1); }
 
@@ -262,17 +263,18 @@ class StoreBuffer
 
         explicit ValueEdge(JS::Value *v) : edge(v) {}
         bool operator==(const ValueEdge &other) const { return edge == other.edge; }
         bool operator!=(const ValueEdge &other) const { return edge != other.edge; }
 
         void *deref() const { return edge->isGCThing() ? edge->toGCThing() : nullptr; }
 
         bool maybeInRememberedSet(const Nursery &nursery) const {
-            return !nursery.isInside(edge) && nursery.isInside(deref());
+            JS_ASSERT(nursery.isInside(deref()));
+            return !nursery.isInside(edge);
         }
 
         void mark(JSTracer *trc);
 
         ValueEdge tagged() const { return ValueEdge((JS::Value *)(uintptr_t(edge) | 1)); }
         ValueEdge untagged() const { return ValueEdge((JS::Value *)(uintptr_t(edge) & ~1)); }
         bool isTagged() const { return bool(uintptr_t(edge) & 1); }
 
@@ -357,18 +359,17 @@ class StoreBuffer
         }
 
       private:
         MarkCallback callback;
         Key *key;
         void *data;
     };
 
-    template <typename Edge>
-    bool isOkayToUseBuffer(const Edge &edge) const {
+    bool isOkayToUseBuffer() const {
         /*
          * Disabled store buffers may not have a valid state; e.g. when stored
          * inline in the ChunkTrailer.
          */
         if (!isEnabled())
             return false;
 
         /*
@@ -377,27 +378,27 @@ class StoreBuffer
          */
         if (!CurrentThreadCanAccessRuntime(runtime_))
             return false;
 
         return true;
     }
 
     template <typename Buffer, typename Edge>
-    void put(Buffer &buffer, const Edge &edge) {
-        if (!isOkayToUseBuffer(edge))
+    void putFromAnyThread(Buffer &buffer, const Edge &edge) {
+        if (!isOkayToUseBuffer())
             return;
         mozilla::ReentrancyGuard g(*this);
         if (edge.maybeInRememberedSet(nursery_))
             buffer.put(this, edge);
     }
 
     template <typename Buffer, typename Edge>
-    void unput(Buffer &buffer, const Edge &edge) {
-        if (!isOkayToUseBuffer(edge))
+    void unputFromAnyThread(Buffer &buffer, const Edge &edge) {
+        if (!isOkayToUseBuffer())
             return;
         mozilla::ReentrancyGuard g(*this);
         buffer.unput(this, edge);
     }
 
     MonoTypeBuffer<ValueEdge> bufferVal;
     MonoTypeBuffer<CellPtrEdge> bufferCell;
     MonoTypeBuffer<SlotsEdge> bufferSlot;
@@ -427,40 +428,48 @@ class StoreBuffer
     bool isEnabled() const { return enabled_; }
 
     bool clear();
 
     /* Get the overflowed status. */
     bool isAboutToOverflow() const { return aboutToOverflow_; }
 
     /* Insert a single edge into the buffer/remembered set. */
-    void putValue(JS::Value *valuep) { put(bufferVal, ValueEdge(valuep)); }
-    void putCell(Cell **cellp) { put(bufferCell, CellPtrEdge(cellp)); }
-    void putSlot(JSObject *obj, int kind, int32_t start, int32_t count) {
-        put(bufferSlot, SlotsEdge(obj, kind, start, count));
+    void putValueFromAnyThread(JS::Value *valuep) { putFromAnyThread(bufferVal, ValueEdge(valuep)); }
+    void putCellFromAnyThread(Cell **cellp) { putFromAnyThread(bufferCell, CellPtrEdge(cellp)); }
+    void putSlotFromAnyThread(JSObject *obj, int kind, int32_t start, int32_t count) {
+        putFromAnyThread(bufferSlot, SlotsEdge(obj, kind, start, count));
     }
     void putWholeCell(Cell *cell) {
         JS_ASSERT(cell->isTenured());
-        put(bufferWholeCell, WholeCellEdges(cell));
+        putFromAnyThread(bufferWholeCell, WholeCellEdges(cell));
     }
 
     /* Insert or update a single edge in the Relocatable buffer. */
-    void putRelocatableValue(JS::Value *valuep) { put(bufferRelocVal, ValueEdge(valuep)); }
-    void putRelocatableCell(Cell **cellp) { put(bufferRelocCell, CellPtrEdge(cellp)); }
-    void removeRelocatableValue(JS::Value *valuep) { unput(bufferRelocVal, ValueEdge(valuep)); }
-    void removeRelocatableCell(Cell **cellp) { unput(bufferRelocCell, CellPtrEdge(cellp)); }
+    void putRelocatableValueFromAnyThread(JS::Value *valuep) {
+        putFromAnyThread(bufferRelocVal, ValueEdge(valuep));
+    }
+    void removeRelocatableValueFromAnyThread(JS::Value *valuep) {
+        unputFromAnyThread(bufferRelocVal, ValueEdge(valuep));
+    }
+    void putRelocatableCellFromAnyThread(Cell **cellp) {
+        putFromAnyThread(bufferRelocCell, CellPtrEdge(cellp));
+    }
+    void removeRelocatableCellFromAnyThread(Cell **cellp) {
+        unputFromAnyThread(bufferRelocCell, CellPtrEdge(cellp));
+    }
 
     /* Insert an entry into the generic buffer. */
     template <typename T>
-    void putGeneric(const T &t) { put(bufferGeneric, t);}
+    void putGeneric(const T &t) { putFromAnyThread(bufferGeneric, t);}
 
     /* Insert or update a callback entry. */
     template <typename Key>
     void putCallback(void (*callback)(JSTracer *trc, Key *key, void *data), Key *key, void *data) {
-        put(bufferGeneric, CallbackRef<Key>(callback, key, data));
+        putFromAnyThread(bufferGeneric, CallbackRef<Key>(callback, key, data));
     }
 
     /* Methods to mark the source of all edges in the store buffer. */
     void markAll(JSTracer *trc);
     void markValues(JSTracer *trc)            { bufferVal.mark(this, trc); }
     void markCells(JSTracer *trc)             { bufferCell.mark(this, trc); }
     void markSlots(JSTracer *trc)             { bufferSlot.mark(this, trc); }
     void markWholeCells(JSTracer *trc)        { bufferWholeCell.mark(this, trc); }
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -784,16 +784,17 @@ Chunk::init(JSRuntime *rt)
     /*
      * Decommit the arenas. We do this after poisoning so that if the OS does
      * not have to recycle the pages, we still get the benefit of poisoning.
      */
     decommitAllArenas(rt);
 
     /* Initialize the chunk info. */
     info.age = 0;
+    info.trailer.storeBuffer = nullptr;
     info.trailer.location = ChunkLocationTenuredHeap;
     info.trailer.runtime = rt;
 
     /* The rest of info fields are initialized in PickChunk. */
 }
 
 static inline Chunk **
 GetAvailableChunkList(Zone *zone)
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2288,18 +2288,18 @@ JSObject::TradeGuts(JSContext *cx, JSObj
         js_memcpy(b, tmp, size);
 
 #ifdef JSGC_GENERATIONAL
         /*
          * Trigger post barriers for fixed slots. JSObject bits are barriered
          * below, in common with the other case.
          */
         for (size_t i = 0; i < a->numFixedSlots(); ++i) {
-            HeapSlot::writeBarrierPost(cx->runtime(), a, HeapSlot::Slot, i, a->getSlot(i));
-            HeapSlot::writeBarrierPost(cx->runtime(), b, HeapSlot::Slot, i, b->getSlot(i));
+            HeapSlot::writeBarrierPost(a, HeapSlot::Slot, i, a->getSlot(i));
+            HeapSlot::writeBarrierPost(b, HeapSlot::Slot, i, b->getSlot(i));
         }
 #endif
     } else {
         /*
          * If the objects are of differing sizes, use the space we reserved
          * earlier to save the slots from each object and then copy them into
          * the new layout for the other object.
          */
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -182,17 +182,17 @@ class StrictArgumentsObject;
  * single step.
  */
 inline void
 DenseRangeWriteBarrierPost(JSRuntime *rt, JSObject *obj, uint32_t start, uint32_t count)
 {
 #ifdef JSGC_GENERATIONAL
     if (count > 0) {
         JS::shadow::Runtime *shadowRuntime = JS::shadow::Runtime::asShadowRuntime(rt);
-        shadowRuntime->gcStoreBufferPtr()->putSlot(obj, HeapSlot::Element, start, count);
+        shadowRuntime->gcStoreBufferPtr()->putSlotFromAnyThread(obj, HeapSlot::Element, start, count);
     }
 #endif
 }
 
 }  /* namespace js */
 
 /*
  * The public interface for an object.
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -218,22 +218,23 @@ JSObject::ensureDenseInitializedLengthNo
      * Ensure that the array's contents have been initialized up to index, and
      * mark the elements through 'index + extra' as initialized in preparation
      * for a write.
      */
     JS_ASSERT(index + extra <= getDenseCapacity());
     uint32_t &initlen = getElementsHeader()->initializedLength;
 
     if (initlen < index + extra) {
-        JSRuntime *rt = runtimeFromAnyThread();
         size_t offset = initlen;
         for (js::HeapSlot *sp = elements + initlen;
              sp != elements + (index + extra);
              sp++, offset++)
-            sp->init(rt, this, js::HeapSlot::Element, offset, js::MagicValue(JS_ELEMENTS_HOLE));
+        {
+            sp->init(this, js::HeapSlot::Element, offset, js::MagicValue(JS_ELEMENTS_HOLE));
+        }
         initlen = index + extra;
     }
 }
 
 inline void
 JSObject::ensureDenseInitializedLength(js::ExclusiveContext *cx, uint32_t index, uint32_t extra)
 {
     if (writeToIndexWouldMarkNotPacked(index))
--- a/js/src/vm/ObjectImpl.cpp
+++ b/js/src/vm/ObjectImpl.cpp
@@ -259,34 +259,32 @@ js::ObjectImpl::initializeSlotRange(uint
 {
     /*
      * No bounds check, as this is used when the object's shape does not
      * reflect its allocated slots (updateSlotsForSpan).
      */
     HeapSlot *fixedStart, *fixedEnd, *slotsStart, *slotsEnd;
     getSlotRangeUnchecked(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
 
-    JSRuntime *rt = runtimeFromAnyThread();
     uint32_t offset = start;
     for (HeapSlot *sp = fixedStart; sp < fixedEnd; sp++)
-        sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, offset++, UndefinedValue());
+        sp->init(this->asObjectPtr(), HeapSlot::Slot, offset++, UndefinedValue());
     for (HeapSlot *sp = slotsStart; sp < slotsEnd; sp++)
-        sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, offset++, UndefinedValue());
+        sp->init(this->asObjectPtr(), HeapSlot::Slot, offset++, UndefinedValue());
 }
 
 void
 js::ObjectImpl::initSlotRange(uint32_t start, const Value *vector, uint32_t length)
 {
-    JSRuntime *rt = runtimeFromAnyThread();
     HeapSlot *fixedStart, *fixedEnd, *slotsStart, *slotsEnd;
     getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
     for (HeapSlot *sp = fixedStart; sp < fixedEnd; sp++)
-        sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, start++, *vector++);
+        sp->init(this->asObjectPtr(), HeapSlot::Slot, start++, *vector++);
     for (HeapSlot *sp = slotsStart; sp < slotsEnd; sp++)
-        sp->init(rt, this->asObjectPtr(), HeapSlot::Slot, start++, *vector++);
+        sp->init(this->asObjectPtr(), HeapSlot::Slot, start++, *vector++);
 }
 
 void
 js::ObjectImpl::copySlotRange(uint32_t start, const Value *vector, uint32_t length)
 {
     JS::Zone *zone = this->zone();
     HeapSlot *fixedStart, *fixedEnd, *slotsStart, *slotsEnd;
     getSlotRange(start, length, &fixedStart, &fixedEnd, &slotsStart, &slotsEnd);
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -835,17 +835,22 @@ class ObjectImpl : public gc::BarrieredC
 
     /* GC support. */
     static ThingRootKind rootKind() { return THING_ROOT_OBJECT; }
 
     inline void privateWriteBarrierPre(void **oldval);
 
     void privateWriteBarrierPost(void **pprivate) {
 #ifdef JSGC_GENERATIONAL
-        shadowRuntimeFromAnyThread()->gcStoreBufferPtr()->putCell(reinterpret_cast<js::gc::Cell **>(pprivate));
+        js::gc::Cell **cellp = reinterpret_cast<js::gc::Cell **>(pprivate);
+        JS_ASSERT(cellp);
+        JS_ASSERT(*cellp);
+        js::gc::StoreBuffer *storeBuffer = (*cellp)->storeBuffer();
+        if (storeBuffer)
+            storeBuffer->putCellFromAnyThread(cellp);
 #endif
     }
 
     void markChildren(JSTracer *trc);
 
     /* Private data accessors. */
 
     inline void *&privateRef(uint32_t nfixed) const { /* XXX should be private, not protected! */
@@ -939,40 +944,53 @@ template<>
 /* static */ inline bool
 BarrieredCell<ObjectImpl>::isNullLike(ObjectImpl *obj)
 {
     return IsNullTaggedPointer(obj);
 }
 
 template<>
 /* static */ inline void
-BarrieredCell<ObjectImpl>::writeBarrierPost(ObjectImpl *obj, void *addr)
+BarrieredCell<ObjectImpl>::writeBarrierPost(ObjectImpl *obj, void *cellp)
 {
+    JS_ASSERT(cellp);
 #ifdef JSGC_GENERATIONAL
     if (IsNullTaggedPointer(obj))
         return;
-    obj->shadowRuntimeFromAnyThread()->gcStoreBufferPtr()->putCell((Cell **)addr);
+    JS_ASSERT(obj == *static_cast<ObjectImpl **>(cellp));
+    gc::StoreBuffer *storeBuffer = obj->storeBuffer();
+    if (storeBuffer)
+        storeBuffer->putCellFromAnyThread(static_cast<Cell **>(cellp));
 #endif
 }
 
 template<>
 /* static */ inline void
-BarrieredCell<ObjectImpl>::writeBarrierPostRelocate(ObjectImpl *obj, void *addr)
+BarrieredCell<ObjectImpl>::writeBarrierPostRelocate(ObjectImpl *obj, void *cellp)
 {
+    JS_ASSERT(cellp);
+    JS_ASSERT(obj);
+    JS_ASSERT(obj == *static_cast<ObjectImpl **>(cellp));
 #ifdef JSGC_GENERATIONAL
-    obj->shadowRuntimeFromAnyThread()->gcStoreBufferPtr()->putRelocatableCell((Cell **)addr);
+    gc::StoreBuffer *storeBuffer = obj->storeBuffer();
+    if (storeBuffer)
+        storeBuffer->putRelocatableCellFromAnyThread(static_cast<Cell **>(cellp));
 #endif
 }
 
 template<>
 /* static */ inline void
-BarrieredCell<ObjectImpl>::writeBarrierPostRemove(ObjectImpl *obj, void *addr)
+BarrieredCell<ObjectImpl>::writeBarrierPostRemove(ObjectImpl *obj, void *cellp)
 {
+    JS_ASSERT(cellp);
+    JS_ASSERT(obj);
+    JS_ASSERT(obj == *static_cast<ObjectImpl **>(cellp));
 #ifdef JSGC_GENERATIONAL
-    obj->shadowRuntimeFromAnyThread()->gcStoreBufferPtr()->removeRelocatableCell((Cell **)addr);
+    obj->shadowRuntimeFromAnyThread()->gcStoreBufferPtr()->removeRelocatableCellFromAnyThread(
+        static_cast<Cell **>(cellp));
 #endif
 }
 
 } // namespace gc
 
 inline void
 ObjectImpl::privateWriteBarrierPre(void **oldval)
 {
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -520,27 +520,31 @@ struct StackBaseShape;
 namespace gc {
 void MergeCompartments(JSCompartment *source, JSCompartment *target);
 }
 
 static inline void
 GetterSetterWriteBarrierPost(JSRuntime *rt, JSObject **objp)
 {
 #ifdef JSGC_GENERATIONAL
-    JS::shadow::Runtime *shadowRuntime = JS::shadow::Runtime::asShadowRuntime(rt);
-    shadowRuntime->gcStoreBufferPtr()->putRelocatableCell(reinterpret_cast<gc::Cell **>(objp));
+    JS_ASSERT(objp);
+    JS_ASSERT(*objp);
+    gc::Cell **cellp = reinterpret_cast<gc::Cell **>(objp);
+    gc::StoreBuffer *storeBuffer = (*cellp)->storeBuffer();
+    if (storeBuffer)
+        storeBuffer->putRelocatableCellFromAnyThread(cellp);
 #endif
 }
 
 static inline void
 GetterSetterWriteBarrierPostRemove(JSRuntime *rt, JSObject **objp)
 {
 #ifdef JSGC_GENERATIONAL
     JS::shadow::Runtime *shadowRuntime = JS::shadow::Runtime::asShadowRuntime(rt);
-    shadowRuntime->gcStoreBufferPtr()->removeRelocatableCell(reinterpret_cast<gc::Cell **>(objp));
+    shadowRuntime->gcStoreBufferPtr()->removeRelocatableCellFromAnyThread(reinterpret_cast<gc::Cell **>(objp));
 #endif
 }
 
 class BaseShape : public gc::BarrieredCell<BaseShape>
 {
   public:
     friend class Shape;
     friend struct StackBaseShape;
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -134,25 +134,25 @@ template
 void InterpreterFrame::copyFrameAndValues<InterpreterFrame::DoPostBarrier>(
                                     JSContext *, Value *, InterpreterFrame *, const Value *, Value *);
 
 void
 InterpreterFrame::writeBarrierPost()
 {
     /* This needs to follow the same rules as in InterpreterFrame::mark. */
     if (scopeChain_)
-        JSObject::writeBarrierPost(scopeChain_, (void *)&scopeChain_);
+        JSObject::writeBarrierPost(scopeChain_, &scopeChain_);
     if (flags_ & HAS_ARGS_OBJ)
-        JSObject::writeBarrierPost(argsObj_, (void *)&argsObj_);
+        JSObject::writeBarrierPost(argsObj_, &argsObj_);
     if (isFunctionFrame()) {
-        JSFunction::writeBarrierPost(exec.fun, (void *)&exec.fun);
+        JSFunction::writeBarrierPost(exec.fun, &exec.fun);
         if (isEvalFrame())
-            JSScript::writeBarrierPost(u.evalScript, (void *)&u.evalScript);
+            JSScript::writeBarrierPost(u.evalScript, &u.evalScript);
     } else {
-        JSScript::writeBarrierPost(exec.script, (void *)&exec.script);
+        JSScript::writeBarrierPost(exec.script, &exec.script);
     }
     if (hasReturnValue())
         HeapValue::writeBarrierPost(rval_, &rval_);
 }
 
 bool
 InterpreterFrame::copyRawFrameSlots(AutoValueVector *vec)
 {