Bug 903519 - Strings in the nursery: barriers, r=jonco
☠☠ backed out by 65e92478e09d ☠ ☠
authorSteve Fink <sfink@mozilla.com>
Fri, 28 Jul 2017 16:46:38 -0700
changeset 453156 7d56db66836900bc7758c6829b9235a3dd26947e
parent 453155 7c96258a64595d287eb72a54ee52a656dbb40365
child 453157 f5f72c93adf94f4ae670c38941f68c75d19f8072
push id1648
push usermtabara@mozilla.com
push dateThu, 01 Mar 2018 12:45:47 +0000
treeherdermozilla-release@cbb9688c2eeb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs903519
milestone59.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 903519 - Strings in the nursery: barriers, r=jonco
js/public/HeapAPI.h
js/public/RootingAPI.h
js/src/gc/Barrier.cpp
js/src/gc/Barrier.h
js/src/gc/Cell.h
js/src/gc/Marking.cpp
js/src/gc/NurseryAwareHashMap.h
js/src/gc/StoreBuffer.h
js/src/jsapi.cpp
js/src/jscompartment.h
js/src/jsgc.cpp
js/src/vm/MemoryMetrics.cpp
js/src/vm/NativeObject-inl.h
js/src/vm/String-inl.h
js/src/vm/String.cpp
js/src/vm/String.h
js/src/vm/UnboxedObject-inl.h
--- a/js/public/HeapAPI.h
+++ b/js/public/HeapAPI.h
@@ -455,21 +455,18 @@ namespace JS {
 
 static MOZ_ALWAYS_INLINE Zone*
 GetTenuredGCThingZone(GCCellPtr thing)
 {
     MOZ_ASSERT(!js::gc::IsInsideNursery(thing.asCell()));
     return js::gc::detail::GetGCThingZone(thing.unsafeAsUIntPtr());
 }
 
-static MOZ_ALWAYS_INLINE Zone*
-GetStringZone(JSString* str)
-{
-    return js::gc::detail::GetGCThingZone(uintptr_t(str));
-}
+extern JS_PUBLIC_API(Zone*)
+GetStringZone(JSString* str);
 
 extern JS_PUBLIC_API(Zone*)
 GetObjectZone(JSObject* obj);
 
 extern JS_PUBLIC_API(Zone*)
 GetValueZone(const Value& value);
 
 static MOZ_ALWAYS_INLINE bool
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -194,31 +194,32 @@ namespace JS {
 
 template <typename T> class Rooted;
 template <typename T> class PersistentRooted;
 
 /* This is exposing internal state of the GC for inlining purposes. */
 JS_FRIEND_API(bool) isGCEnabled();
 
 JS_FRIEND_API(void) HeapObjectPostBarrier(JSObject** objp, JSObject* prev, JSObject* next);
+JS_FRIEND_API(void) HeapStringPostBarrier(JSString** objp, JSString* prev, JSString* next);
 
 #ifdef JS_DEBUG
 /**
  * For generational GC, assert that an object is in the tenured generation as
  * opposed to being in the nursery.
  */
 extern JS_FRIEND_API(void)
 AssertGCThingMustBeTenured(JSObject* obj);
 extern JS_FRIEND_API(void)
-AssertGCThingIsNotAnObjectSubclass(js::gc::Cell* cell);
+AssertGCThingIsNotNurseryAllocable(js::gc::Cell* cell);
 #else
 inline void
 AssertGCThingMustBeTenured(JSObject* obj) {}
 inline void
-AssertGCThingIsNotAnObjectSubclass(js::gc::Cell* cell) {}
+AssertGCThingIsNotNurseryAllocable(js::gc::Cell* cell) {}
 #endif
 
 /**
  * The Heap<T> class is a heap-stored reference to a JS GC thing. All members of
  * heap classes that refer to GC things should use Heap<T> (or possibly
  * TenuredHeap<T>, described below).
  *
  * Heap<T> is an abstraction that hides some of the complexity required to
@@ -619,17 +620,17 @@ struct BarrierMethods<T*>
     static gc::Cell* asGCThingOrNull(T* v) {
         if (!v)
             return nullptr;
         MOZ_ASSERT(uintptr_t(v) > 32);
         return reinterpret_cast<gc::Cell*>(v);
     }
     static void postBarrier(T** vp, T* prev, T* next) {
         if (next)
-            JS::AssertGCThingIsNotAnObjectSubclass(reinterpret_cast<js::gc::Cell*>(next));
+            JS::AssertGCThingIsNotNurseryAllocable(reinterpret_cast<js::gc::Cell*>(next));
     }
     static void exposeToJS(T* t) {
         if (t)
             js::gc::ExposeGCThingToActiveJS(JS::GCCellPtr(t));
     }
 };
 
 template <>
@@ -667,16 +668,31 @@ struct BarrierMethods<JSFunction*>
                                   reinterpret_cast<JSObject*>(next));
     }
     static void exposeToJS(JSFunction* fun) {
         if (fun)
             JS::ExposeObjectToActiveJS(reinterpret_cast<JSObject*>(fun));
     }
 };
 
+template <>
+struct BarrierMethods<JSString*>
+{
+    static JSString* initial() { return nullptr; }
+    static gc::Cell* asGCThingOrNull(JSString* v) {
+        if (!v)
+            return nullptr;
+        MOZ_ASSERT(uintptr_t(v) > 32);
+        return reinterpret_cast<gc::Cell*>(v);
+    }
+    static void postBarrier(JSString** vp, JSString* prev, JSString* next) {
+        JS::HeapStringPostBarrier(vp, prev, next);
+    }
+};
+
 // Provide hash codes for Cell kinds that may be relocated and, thus, not have
 // a stable address to use as the base for a hash code. Instead of the address,
 // this hasher uses Cell::getUniqueId to provide exact matches and as a base
 // for generating hash codes.
 //
 // Note: this hasher, like PointerHasher can "hash" a nullptr. While a nullptr
 // would not likely be a useful key, there are some cases where being able to
 // hash a nullptr is useful, either on purpose or because of bugs:
--- a/js/src/gc/Barrier.cpp
+++ b/js/src/gc/Barrier.cpp
@@ -216,13 +216,20 @@ template struct JS_PUBLIC_API(MovableCel
 JS_PUBLIC_API(void)
 JS::HeapObjectPostBarrier(JSObject** objp, JSObject* prev, JSObject* next)
 {
     MOZ_ASSERT(objp);
     js::InternalBarrierMethods<JSObject*>::postBarrier(objp, prev, next);
 }
 
 JS_PUBLIC_API(void)
+JS::HeapStringPostBarrier(JSString** strp, JSString* prev, JSString* next)
+{
+    MOZ_ASSERT(strp);
+    js::InternalBarrierMethods<JSString*>::postBarrier(strp, prev, next);
+}
+
+JS_PUBLIC_API(void)
 JS::HeapValuePostBarrier(JS::Value* valuep, const Value& prev, const Value& next)
 {
     MOZ_ASSERT(valuep);
     js::InternalBarrierMethods<JS::Value>::postBarrier(valuep, prev, next);
 }
--- a/js/src/gc/Barrier.h
+++ b/js/src/gc/Barrier.h
@@ -284,28 +284,28 @@ struct InternalBarrierMethods<Value>
     }
 
     static MOZ_ALWAYS_INLINE void postBarrier(Value* vp, const Value& prev, const Value& next) {
         MOZ_ASSERT(!CurrentThreadIsIonCompiling());
         MOZ_ASSERT(vp);
 
         // If the target needs an entry, add it.
         js::gc::StoreBuffer* sb;
-        if (next.isObject() && (sb = reinterpret_cast<gc::Cell*>(&next.toObject())->storeBuffer())) {
+        if ((next.isObject() || next.isString()) && (sb = next.toGCThing()->storeBuffer())) {
             // If we know that the prev has already inserted an entry, we can
             // skip doing the lookup to add the new entry. Note that we cannot
             // safely assert the presence of the entry because it may have been
             // added via a different store buffer.
-            if (prev.isObject() && reinterpret_cast<gc::Cell*>(&prev.toObject())->storeBuffer())
+            if ((prev.isObject() || prev.isString()) && prev.toGCThing()->storeBuffer())
                 return;
             sb->putValue(vp);
             return;
         }
         // Remove the prev entry if the new value does not need it.
-        if (prev.isObject() && (sb = reinterpret_cast<gc::Cell*>(&prev.toObject())->storeBuffer()))
+        if ((prev.isObject() || prev.isString()) && (sb = prev.toGCThing()->storeBuffer()))
             sb->unputValue(vp);
     }
 
     static void readBarrier(const Value& v) {
         DispatchTyped(ReadBarrierFunctor<Value>(), v);
     }
 };
 
@@ -682,18 +682,18 @@ class HeapSlot : public WriteBarrieredBa
         post(owner, kind, slot, v);
     }
 
   private:
     void post(NativeObject* owner, Kind kind, uint32_t slot, const Value& target) {
 #ifdef DEBUG
         assertPreconditionForWriteBarrierPost(owner, kind, slot, target);
 #endif
-        if (this->value.isObject()) {
-            gc::Cell* cell = reinterpret_cast<gc::Cell*>(&this->value.toObject());
+        if (this->value.isObject() || this->value.isString()) {
+            gc::Cell* cell = this->value.toGCThing();
             if (cell->storeBuffer())
                 cell->storeBuffer()->putSlot(owner, kind, slot, 1);
         }
     }
 };
 
 class HeapSlotArray
 {
--- a/js/src/gc/Cell.h
+++ b/js/src/gc/Cell.h
@@ -237,17 +237,21 @@ inline StoreBuffer*
 Cell::storeBuffer() const
 {
     return chunk()->trailer.storeBuffer;
 }
 
 inline JS::TraceKind
 Cell::getTraceKind() const
 {
-    return isTenured() ? asTenured().getTraceKind() : JS::TraceKind::Object;
+    if (isTenured())
+        return asTenured().getTraceKind();
+    if (js::shadow::String::nurseryCellIsString(this))
+        return JS::TraceKind::String;
+    return JS::TraceKind::Object;
 }
 
 /* static */ MOZ_ALWAYS_INLINE bool
 Cell::needWriteBarrierPre(JS::Zone* zone) {
     return JS::shadow::Zone::asShadowZone(zone)->needsIncrementalBarrier();
 }
 
 /* static */ MOZ_ALWAYS_INLINE TenuredCell*
@@ -411,17 +415,18 @@ TenuredCell::writeBarrierPre(TenuredCell
         MOZ_ASSERT(tmp == thing);
     }
 }
 
 static MOZ_ALWAYS_INLINE void
 AssertValidToSkipBarrier(TenuredCell* thing)
 {
     MOZ_ASSERT(!IsInsideNursery(thing));
-    MOZ_ASSERT_IF(thing, MapAllocToTraceKind(thing->getAllocKind()) != JS::TraceKind::Object);
+    MOZ_ASSERT_IF(thing, MapAllocToTraceKind(thing->getAllocKind()) != JS::TraceKind::Object &&
+                         MapAllocToTraceKind(thing->getAllocKind()) != JS::TraceKind::String);
 }
 
 /* static */ MOZ_ALWAYS_INLINE void
 TenuredCell::writeBarrierPost(void* cellp, TenuredCell* prior, TenuredCell* next)
 {
     AssertValidToSkipBarrier(next);
 }
 
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -2805,16 +2805,22 @@ TraceWholeCell(TenuringTracer& mover, JS
 
     if (object->is<UnboxedPlainObject>()) {
         if (UnboxedExpandoObject* expando = object->as<UnboxedPlainObject>().maybeExpando())
             expando->traceChildren(&mover);
     }
 }
 
 static inline void
+TraceWholeCell(TenuringTracer& mover, JSString* str)
+{
+    str->traceChildren(&mover);
+}
+
+static inline void
 TraceWholeCell(TenuringTracer& mover, JSScript* script)
 {
     script->traceChildren(&mover);
 }
 
 static inline void
 TraceWholeCell(TenuringTracer& mover, jit::JitCode* jitcode)
 {
@@ -2843,16 +2849,19 @@ js::gc::StoreBuffer::traceWholeCells(Ten
         MOZ_ASSERT(arena->bufferedCells() == cells);
         arena->bufferedCells() = &ArenaCellSet::Empty;
 
         JS::TraceKind kind = MapAllocToTraceKind(arena->getAllocKind());
         switch (kind) {
           case JS::TraceKind::Object:
             TraceBufferedCells<JSObject>(mover, arena, cells);
             break;
+          case JS::TraceKind::String:
+            TraceBufferedCells<JSString>(mover, arena, cells);
+            break;
           case JS::TraceKind::Script:
             TraceBufferedCells<JSScript>(mover, arena, cells);
             break;
           case JS::TraceKind::JitCode:
             TraceBufferedCells<jit::JitCode>(mover, arena, cells);
             break;
           default:
             MOZ_CRASH("Unexpected trace kind");
--- a/js/src/gc/NurseryAwareHashMap.h
+++ b/js/src/gc/NurseryAwareHashMap.h
@@ -143,24 +143,28 @@ class NurseryAwareHashMap
             // Drop the entry if the value is not marked.
             if (JS::GCPolicy<BarrieredValue>::needsSweep(&p->value())) {
                 map.remove(key);
                 continue;
             }
 
             // Update and relocate the key, if the value is still needed.
             //
-            // Note that this currently assumes that all Value will contain a
-            // strong reference to Key, as per its use as the
-            // CrossCompartmentWrapperMap. We may need to make the following
-            // behavior more dynamic if we use this map in other nursery-aware
-            // contexts.
+            // Non-string Values will contain a strong reference to Key, as per
+            // its use in the CrossCompartmentWrapperMap, so the key will never
+            // be dying here. Strings do *not* have any sort of pointer from
+            // wrapper to wrappee, as they are just copies. The wrapper map
+            // entry is merely used as a cache to avoid re-copying the string,
+            // and currently that entire cache is flushed on major GC.
             Key copy(key);
-            mozilla::DebugOnly<bool> sweepKey = JS::GCPolicy<Key>::needsSweep(&copy);
-            MOZ_ASSERT(!sweepKey);
+            bool sweepKey = JS::GCPolicy<Key>::needsSweep(&copy);
+            if (sweepKey) {
+                map.remove(key);
+                continue;
+            }
             map.rekeyIfMoved(key, copy);
         }
         nurseryEntries.clear();
     }
 
     void sweep() {
         MOZ_ASSERT(nurseryEntries.empty());
         map.sweep();
--- a/js/src/gc/StoreBuffer.h
+++ b/js/src/gc/StoreBuffer.h
@@ -400,16 +400,18 @@ class StoreBuffer
     }
 
     MOZ_MUST_USE bool enable();
     void disable();
     bool isEnabled() const { return enabled_; }
 
     void clear();
 
+    const Nursery& nursery() const { return nursery_; }
+
     /* Get the overflowed status. */
     bool isAboutToOverflow() const { return aboutToOverflow_; }
 
     bool cancelIonCompilations() const { return cancelIonCompilations_; }
 
     /* Insert a single edge into the buffer/remembered set. */
     void putValue(JS::Value* vp) { put(bufferVal, ValueEdge(vp)); }
     void unputValue(JS::Value* vp) { unput(bufferVal, ValueEdge(vp)); }
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -7708,16 +7708,22 @@ JS::CopyAsyncStack(JSContext* cx, JS::Ha
 }
 
 JS_PUBLIC_API(Zone*)
 JS::GetObjectZone(JSObject* obj)
 {
     return obj->zone();
 }
 
+JS_PUBLIC_API(Zone*)
+JS::GetStringZone(JSString* str)
+{
+    return str->zone();
+}
+
 JS_PUBLIC_API(JS::TraceKind)
 JS::GCThingTraceKind(void* thing)
 {
     MOZ_ASSERT(thing);
     return static_cast<js::gc::Cell*>(thing)->getTraceKind();
 }
 
 JS_PUBLIC_API(void)
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -221,17 +221,17 @@ class CrossCompartmentKey
         }
     };
 
     bool isTenured() const {
         struct IsTenuredFunctor {
             using ReturnType = bool;
             ReturnType operator()(JSObject** tp) { return !IsInsideNursery(*tp); }
             ReturnType operator()(JSScript** tp) { return true; }
-            ReturnType operator()(JSString** tp) { return true; }
+            ReturnType operator()(JSString** tp) { return !IsInsideNursery(*tp); }
         };
         return const_cast<CrossCompartmentKey*>(this)->applyToWrapped(IsTenuredFunctor());
     }
 
     void trace(JSTracer* trc);
     bool needsSweep();
 
   private:
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -8221,34 +8221,35 @@ JS_FRIEND_API(void)
 JS::AssertGCThingMustBeTenured(JSObject* obj)
 {
     MOZ_ASSERT(obj->isTenured() &&
                (!IsNurseryAllocable(obj->asTenured().getAllocKind()) ||
                 obj->getClass()->hasFinalize()));
 }
 
 JS_FRIEND_API(void)
-JS::AssertGCThingIsNotAnObjectSubclass(Cell* cell)
+JS::AssertGCThingIsNotNurseryAllocable(Cell* cell)
 {
     MOZ_ASSERT(cell);
-    MOZ_ASSERT(!cell->is<JSObject>());
+    MOZ_ASSERT(!cell->is<JSObject>() && !cell->is<JSString>());
 }
 
 JS_FRIEND_API(void)
 js::gc::AssertGCThingHasType(js::gc::Cell* cell, JS::TraceKind kind)
 {
     if (!cell) {
         MOZ_ASSERT(kind == JS::TraceKind::Null);
         return;
     }
 
     MOZ_ASSERT(IsCellPointerValid(cell));
 
     if (IsInsideNursery(cell)) {
-        MOZ_ASSERT(kind == JS::TraceKind::Object);
+        MOZ_ASSERT(kind == (JSString::nurseryCellIsString(cell) ? JS::TraceKind::String
+                                                                : JS::TraceKind::Object));
         return;
     }
 
     MOZ_ASSERT(MapAllocToTraceKind(cell->asTenured().getAllocKind()) == kind);
 }
 #endif
 
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
--- a/js/src/vm/MemoryMetrics.cpp
+++ b/js/src/vm/MemoryMetrics.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/DebugOnly.h"
 
 #include "jscompartment.h"
 #include "jsgc.h"
 #include "jsobj.h"
 #include "jsscript.h"
 
 #include "gc/Heap.h"
+#include "gc/Nursery.h"
 #include "jit/BaselineJIT.h"
 #include "jit/Ion.h"
 #include "vm/ArrayObject.h"
 #include "vm/Runtime.h"
 #include "vm/Shape.h"
 #include "vm/String.h"
 #include "vm/Symbol.h"
 #include "vm/WrapperObject.h"
@@ -516,23 +517,26 @@ StatsCellCallback(JSRuntime* rt, void* d
                                    &cStats.baselineStubsFallback);
         cStats.ionData += jit::SizeOfIonData(script, rtStats->mallocSizeOf_);
         CollectScriptSourceStats<granularity>(closure, script->scriptSource());
         break;
       }
 
       case JS::TraceKind::String: {
         JSString* str = static_cast<JSString*>(thing);
+        size_t size = thingSize;
+        if (!str->isTenured())
+            size += Nursery::stringHeaderSize();
 
         JS::StringInfo info;
         if (str->hasLatin1Chars()) {
-            info.gcHeapLatin1 = thingSize;
+            info.gcHeapLatin1 = size;
             info.mallocHeapLatin1 = str->sizeOfExcludingThis(rtStats->mallocSizeOf_);
         } else {
-            info.gcHeapTwoByte = thingSize;
+            info.gcHeapTwoByte = size;
             info.mallocHeapTwoByte = str->sizeOfExcludingThis(rtStats->mallocSizeOf_);
         }
         info.numCopies = 1;
 
         zStats->stringInfo.add(info);
 
         // The primary use case for anonymization is automated crash submission
         // (to help detect OOM crashes). In that case, we don't want to pay the
--- a/js/src/vm/NativeObject-inl.h
+++ b/js/src/vm/NativeObject-inl.h
@@ -120,17 +120,17 @@ NativeObject::markDenseElementsNotPacked
     MarkObjectGroupFlags(cx, this, OBJECT_FLAG_NON_PACKED);
 }
 
 inline void
 NativeObject::elementsRangeWriteBarrierPost(uint32_t start, uint32_t count)
 {
     for (size_t i = 0; i < count; i++) {
         const Value& v = elements_[start + i];
-        if (v.isObject() && IsInsideNursery(&v.toObject())) {
+        if ((v.isObject() || v.isString()) && IsInsideNursery(v.toGCThing())) {
             zone()->group()->storeBuffer().putSlot(this, HeapSlot::Element,
                                                    unshiftedIndex(start + i),
                                                    count - i);
             return;
         }
     }
 }
 
--- a/js/src/vm/String-inl.h
+++ b/js/src/vm/String-inl.h
@@ -77,23 +77,19 @@ NewInlineString(JSContext* cx, HandleLin
 
     JS::AutoCheckCannotGC nogc;
     mozilla::PodCopy(chars, base->chars<CharT>(nogc) + start, length);
     chars[length] = 0;
     return s;
 }
 
 static inline void
-StringWriteBarrierPost(JSContext* maybecx, JSString** strp)
+StringWriteBarrierPost(JSContext* maybecx, JSString** strp, JSString* prev, JSString* next)
 {
-}
-
-static inline void
-StringWriteBarrierPostRemove(JSContext* maybecx, JSString** strp)
-{
+    js::BarrierMethods<JSString*>::postBarrier(strp, prev, next);
 }
 
 } /* namespace js */
 
 MOZ_ALWAYS_INLINE bool
 JSString::validateLength(JSContext* maybecx, size_t length)
 {
     if (MOZ_UNLIKELY(length > JSString::MAX_LENGTH)) {
@@ -108,18 +104,18 @@ MOZ_ALWAYS_INLINE void
 JSRope::init(JSContext* cx, JSString* left, JSString* right, size_t length)
 {
     d.u1.length = length;
     d.u1.flags = ROPE_FLAGS;
     if (left->hasLatin1Chars() && right->hasLatin1Chars())
         d.u1.flags |= LATIN1_CHARS_BIT;
     d.s.u2.left = left;
     d.s.u3.right = right;
-    js::StringWriteBarrierPost(cx, &d.s.u2.left);
-    js::StringWriteBarrierPost(cx, &d.s.u3.right);
+    js::BarrierMethods<JSString*>::postBarrier(&d.s.u2.left, nullptr, left);
+    js::BarrierMethods<JSString*>::postBarrier(&d.s.u3.right, nullptr, right);
 }
 
 template <js::AllowGC allowGC>
 MOZ_ALWAYS_INLINE JSRope*
 JSRope::new_(JSContext* cx,
              typename js::MaybeRooted<JSString*, allowGC>::HandleType left,
              typename js::MaybeRooted<JSString*, allowGC>::HandleType right,
              size_t length)
@@ -143,17 +139,17 @@ JSDependentString::init(JSContext* cx, J
     if (base->hasLatin1Chars()) {
         d.u1.flags = DEPENDENT_FLAGS | LATIN1_CHARS_BIT;
         d.s.u2.nonInlineCharsLatin1 = base->latin1Chars(nogc) + start;
     } else {
         d.u1.flags = DEPENDENT_FLAGS;
         d.s.u2.nonInlineCharsTwoByte = base->twoByteChars(nogc) + start;
     }
     d.s.u3.base = base;
-    js::StringWriteBarrierPost(cx, reinterpret_cast<JSString**>(&d.s.u3.base));
+    js::BarrierMethods<JSString*>::postBarrier(reinterpret_cast<JSString**>(&d.s.u3.base), nullptr, base);
 }
 
 MOZ_ALWAYS_INLINE JSLinearString*
 JSDependentString::new_(JSContext* cx, JSLinearString* baseArg, size_t start,
                         size_t length)
 {
     /*
      * Try to avoid long chains of dependent strings. We can't avoid these
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/RangedPtr.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/Unused.h"
 
 #include "gc/Marking.h"
+#include "gc/Nursery.h"
 #include "js/UbiNode.h"
 #include "vm/GeckoProfiler.h"
 
 #include "jscntxtinlines.h"
 #include "jscompartmentinlines.h"
 
 #include "vm/GeckoProfiler-inl.h"
 
@@ -85,19 +86,19 @@ JS::ubi::Concrete<JSString>::size(mozill
 {
     JSString& str = get();
     size_t size;
     if (str.isAtom())
         size = str.isFatInline() ? sizeof(js::FatInlineAtom) : sizeof(js::NormalAtom);
     else
         size = str.isFatInline() ? sizeof(JSFatInlineString) : sizeof(JSString);
 
-    // We can't use mallocSizeof on things in the nursery. At the moment,
-    // strings are never in the nursery, but that may change.
-    MOZ_ASSERT(!IsInsideNursery(&str));
+    if (IsInsideNursery(&str))
+        size += Nursery::stringHeaderSize();
+
     size += str.sizeOfExcludingThis(mallocSizeOf);
 
     return size;
 }
 
 const char16_t JS::ubi::Concrete<JSString>::concreteTypeName[] = u"JSString";
 
 #ifdef DEBUG
@@ -480,16 +481,17 @@ JSRope::flattenInternal(JSContext* maybe
              */
             MOZ_ASSERT(str->isRope());
             while (str != leftMostRope) {
                 if (b == WithIncrementalBarrier) {
                     JSString::writeBarrierPre(str->d.s.u2.left);
                     JSString::writeBarrierPre(str->d.s.u3.right);
                 }
                 JSString* child = str->d.s.u2.left;
+                js::BarrierMethods<JSString*>::postBarrier(&str->d.s.u2.left, child, nullptr);
                 MOZ_ASSERT(child->isRope());
                 str->setNonInlineChars(left.nonInlineChars<CharT>(nogc));
                 child->d.u1.flattenData = uintptr_t(str) | Tag_VisitRightChild;
                 str = child;
             }
             if (b == WithIncrementalBarrier) {
                 JSString::writeBarrierPre(str->d.s.u2.left);
                 JSString::writeBarrierPre(str->d.s.u3.right);
@@ -500,21 +502,20 @@ JSRope::flattenInternal(JSContext* maybe
             pos = wholeChars + left.d.u1.length;
             static_assert((EXTENSIBLE_FLAGS & DEPENDENT_FLAGS) == NON_ATOM_BIT,
                           "extensible and dependent flags must only overlap on NON_ATOM_BIT");
             left.d.u1.flags ^= (EXTENSIBLE_FLAGS | DEPENDENT_FLAGS) & ~NON_ATOM_BIT;
             left.d.s.u3.base = (JSLinearString*)this;  /* will be true on exit */
             MOZ_ASSERT(!static_cast<JSString&>(left).isExtensible());
             MOZ_ASSERT(left.isDependent());
             MOZ_ASSERT(!left.isAtom());
-            StringWriteBarrierPostRemove(maybecx, &left.d.s.u2.left);
-            StringWriteBarrierPost(maybecx, (JSString**)&left.d.s.u3.base);
+            BarrierMethods<JSString*>::postBarrier((JSString**)&left.d.s.u3.base, nullptr, this);
             goto visit_right_child;
         }
-    }
+   }
 
     if (!AllocChars(this, wholeLength, &wholeChars, &wholeCapacity)) {
         if (maybecx)
             ReportOutOfMemory(maybecx);
         return nullptr;
     }
 
     if (!isTenured() && maybecx) {
@@ -529,61 +530,61 @@ JSRope::flattenInternal(JSContext* maybe
     pos = wholeChars;
     first_visit_node: {
         if (b == WithIncrementalBarrier) {
             JSString::writeBarrierPre(str->d.s.u2.left);
             JSString::writeBarrierPre(str->d.s.u3.right);
         }
 
         JSString& left = *str->d.s.u2.left;
+        js::BarrierMethods<JSString*>::postBarrier(&str->d.s.u2.left, &left, nullptr);
         str->setNonInlineChars(pos);
-        StringWriteBarrierPostRemove(maybecx, &str->d.s.u2.left);
         if (left.isRope()) {
             /* Return to this node when 'left' done, then goto visit_right_child. */
             left.d.u1.flattenData = uintptr_t(str) | Tag_VisitRightChild;
             str = &left;
             goto first_visit_node;
         }
         CopyChars(pos, left.asLinear());
         pos += left.length();
     }
     visit_right_child: {
         JSString& right = *str->d.s.u3.right;
+        BarrierMethods<JSString*>::postBarrier(&str->d.s.u3.right, &right, nullptr);
         if (right.isRope()) {
             /* Return to this node when 'right' done, then goto finish_node. */
             right.d.u1.flattenData = uintptr_t(str) | Tag_FinishNode;
             str = &right;
             goto first_visit_node;
         }
         CopyChars(pos, right.asLinear());
         pos += right.length();
     }
+
     finish_node: {
         if (str == this) {
             MOZ_ASSERT(pos == wholeChars + wholeLength);
             *pos = '\0';
             str->d.u1.length = wholeLength;
             if (IsSame<CharT, char16_t>::value)
                 str->d.u1.flags = EXTENSIBLE_FLAGS;
             else
                 str->d.u1.flags = EXTENSIBLE_FLAGS | LATIN1_CHARS_BIT;
             str->setNonInlineChars(wholeChars);
             str->d.s.u3.capacity = wholeCapacity;
-            StringWriteBarrierPostRemove(maybecx, &str->d.s.u2.left);
-            StringWriteBarrierPostRemove(maybecx, &str->d.s.u3.right);
             return &this->asFlat();
         }
         uintptr_t flattenData = str->d.u1.flattenData;
         if (IsSame<CharT, char16_t>::value)
             str->d.u1.flags = DEPENDENT_FLAGS;
         else
             str->d.u1.flags = DEPENDENT_FLAGS | LATIN1_CHARS_BIT;
         str->d.u1.length = pos - str->asLinear().nonInlineChars<CharT>(nogc);
         str->d.s.u3.base = (JSLinearString*)this;       /* will be true on exit */
-        StringWriteBarrierPost(maybecx, (JSString**)&str->d.s.u3.base);
+        BarrierMethods<JSString*>::postBarrier((JSString**)&str->d.s.u3.base, nullptr, this);
         str = (JSString*)(flattenData & ~Tag_Mask);
         if ((flattenData & Tag_Mask) == Tag_VisitRightChild)
             goto visit_right_child;
         MOZ_ASSERT((flattenData & Tag_Mask) == Tag_FinishNode);
         goto finish_node;
     }
 }
 
--- a/js/src/vm/String.h
+++ b/js/src/vm/String.h
@@ -11,18 +11,19 @@
 #include "mozilla/PodOperations.h"
 #include "mozilla/Range.h"
 
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "jsstr.h"
 
 #include "gc/Barrier.h"
+#include "gc/Cell.h"
 #include "gc/Heap.h"
-#include "gc/Cell.h"
+#include "gc/Nursery.h"
 #include "gc/Rooting.h"
 #include "js/CharacterEncoding.h"
 #include "js/RootingAPI.h"
 
 #include "vm/Printer.h"
 
 class JSDependentString;
 class JSExtensibleString;
@@ -540,23 +541,27 @@ class JSString : public js::gc::Cell
                       offsetof(JSString, d.s.u2.nonInlineCharsLatin1),
                       "nonInlineCharsTwoByte and nonInlineCharsLatin1 must have same offset");
         return offsetof(JSString, d.s.u2.nonInlineCharsTwoByte);
     }
 
     static const JS::TraceKind TraceKind = JS::TraceKind::String;
 
     JS::Zone* zone() const {
+        if (isTenured())
             return asTenured().zone();
+        return js::Nursery::getStringZone(this);
     }
 
     // Implement TenuredZone members needed for template instantiations.
 
     JS::Zone* zoneFromAnyThread() const {
+        if (isTenured())
             return asTenured().zoneFromAnyThread();
+        return js::Nursery::getStringZone(this);
     }
 
     void fixupAfterMovingGC() {}
 
     js::gc::AllocKind getAllocKind() const {
         using js::gc::AllocKind;
         AllocKind kind;
         if (isAtom())
@@ -596,30 +601,43 @@ class JSString : public js::gc::Cell
     static void dumpChars(const CharT* s, size_t len, js::GenericPrinter& out);
 
     bool equals(const char* s);
 #endif
 
     void traceChildren(JSTracer* trc);
 
     static MOZ_ALWAYS_INLINE void readBarrier(JSString* thing) {
-        if (thing->isPermanentAtom())
+        if (thing->isPermanentAtom() || js::gc::IsInsideNursery(thing))
             return;
-
         js::gc::TenuredCell::readBarrier(&thing->asTenured());
     }
 
     static MOZ_ALWAYS_INLINE void writeBarrierPre(JSString* thing) {
-        if (!thing || thing->isPermanentAtom())
+        if (!thing || thing->isPermanentAtom() || js::gc::IsInsideNursery(thing))
             return;
 
         js::gc::TenuredCell::writeBarrierPre(&thing->asTenured());
     }
 
-    static void writeBarrierPost(void* ptr, JSString* prev, JSString* next) {};
+    static void writeBarrierPost(void* cellp, JSString* prev, JSString* next) {
+        // See JSObject::writeBarrierPost for a description of the logic here.
+        MOZ_ASSERT(cellp);
+
+        js::gc::StoreBuffer* buffer;
+        if (next && (buffer = next->storeBuffer())) {
+            if (prev && prev->storeBuffer())
+                return;
+            buffer->putCell(static_cast<js::gc::Cell**>(cellp));
+            return;
+        }
+
+        if (prev && (buffer = prev->storeBuffer()))
+            buffer->unputCell(static_cast<js::gc::Cell**>(cellp));
+    }
 
   private:
     JSString() = delete;
     JSString(const JSString& other) = delete;
     void operator=(const JSString& other) = delete;
 };
 
 class JSRope : public JSString
--- a/js/src/vm/UnboxedObject-inl.h
+++ b/js/src/vm/UnboxedObject-inl.h
@@ -118,18 +118,20 @@ SetUnboxedValue(JSContext* cx, JSObject*
         if (v.isNumber()) {
             *reinterpret_cast<double*>(p) = v.toNumber();
             return true;
         }
         return false;
 
       case JSVAL_TYPE_STRING:
         if (v.isString()) {
-            MOZ_ASSERT(!IsInsideNursery(v.toString()));
             JSString** np = reinterpret_cast<JSString**>(p);
+            if (IsInsideNursery(v.toString()) && !IsInsideNursery(unboxedObject))
+                unboxedObject->zone()->group()->storeBuffer().putWholeCell(unboxedObject);
+
             if (preBarrier)
                 JSString::writeBarrierPre(*np);
             *np = v.toString();
             return true;
         }
         return false;
 
       case JSVAL_TYPE_OBJECT: