Bug 903519 - Strings in the nursery: allocation, r=jonco
☠☠ backed out by 65e92478e09d ☠ ☠
authorSteve Fink <sfink@mozilla.com>
Mon, 13 Nov 2017 16:57:27 -0800
changeset 450638 fdb6431ea4ffbbc254e051f3f08eeef23d502062
parent 450637 6d7d15b254896c00ef8e00a6b1d921980d661ac2
child 450639 2bc9d427f427b7dec19d7c27a44361338ad3f88d
push id8531
push userryanvm@gmail.com
push dateFri, 12 Jan 2018 16:47:01 +0000
treeherdermozilla-beta@0bc627ade5a0 [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: allocation, r=jonco
js/src/gc/Allocator.cpp
js/src/gc/Allocator.h
js/src/gc/GCRuntime.h
js/src/gc/GCTrace.cpp
js/src/gc/GCTrace.h
js/src/gc/Nursery.cpp
js/src/gc/Nursery.h
js/src/jit-test/tests/heap-analysis/byteSize-of-string.js
js/src/jit/CodeGenerator.cpp
js/src/vm/String.cpp
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -70,18 +70,19 @@ js::Allocate(JSContext* cx, AllocKind ki
 }
 template JSObject* js::Allocate<JSObject, NoGC>(JSContext* cx, gc::AllocKind kind,
                                                 size_t nDynamicSlots, gc::InitialHeap heap,
                                                 const Class* clasp);
 template JSObject* js::Allocate<JSObject, CanGC>(JSContext* cx, gc::AllocKind kind,
                                                  size_t nDynamicSlots, gc::InitialHeap heap,
                                                  const Class* clasp);
 
-// Attempt to allocate a new GC thing out of the nursery. If there is not enough
-// room in the nursery or there is an OOM, this method will return nullptr.
+// Attempt to allocate a new JSObject out of the nursery. If there is not
+// enough room in the nursery or there is an OOM, this method will return
+// nullptr.
 template <AllowGC allowGC>
 JSObject*
 GCRuntime::tryNewNurseryObject(JSContext* cx, size_t thingSize, size_t nDynamicSlots, const Class* clasp)
 {
     MOZ_ASSERT(cx->isNurseryAllocAllowed());
     MOZ_ASSERT(!cx->helperThread());
     MOZ_ASSERT(!IsAtomsCompartment(cx->compartment()));
     JSObject* obj = cx->nursery().allocateObject(cx, thingSize, nDynamicSlots, clasp);
@@ -122,16 +123,90 @@ GCRuntime::tryNewTenuredObject(JSContext
     if (obj)
         obj->setInitialSlotsMaybeNonNative(slots);
     else
         js_free(slots);
 
     return obj;
 }
 
+// Attempt to allocate a new string out of the nursery. If there is not enough
+// room in the nursery or there is an OOM, this method will return nullptr.
+template <AllowGC allowGC>
+JSString*
+GCRuntime::tryNewNurseryString(JSContext* cx, size_t thingSize, AllocKind kind)
+{
+    MOZ_ASSERT(IsNurseryAllocable(kind));
+    MOZ_ASSERT(cx->isNurseryAllocAllowed());
+    MOZ_ASSERT(!cx->helperThread());
+    MOZ_ASSERT(!IsAtomsCompartment(cx->compartment()));
+
+    Cell* cell = cx->nursery().allocateString(cx, cx->zone(), thingSize, kind);
+    if (cell)
+        return static_cast<JSString*>(cell);
+
+    if (allowGC && !cx->suppressGC) {
+        cx->runtime()->gc.minorGC(JS::gcreason::OUT_OF_NURSERY);
+
+        // Exceeding gcMaxBytes while tenuring can disable the Nursery.
+        if (cx->nursery().isEnabled()) {
+            cell = cx->nursery().allocateString(cx, cx->zone(), thingSize, kind);
+            MOZ_ASSERT(cell);
+            return static_cast<JSString*>(cell);
+        }
+    }
+    return nullptr;
+}
+
+template <typename StringAllocT, AllowGC allowGC /* = CanGC */>
+StringAllocT*
+js::AllocateString(JSContext* cx, InitialHeap heap)
+{
+    static_assert(mozilla::IsConvertible<StringAllocT*, JSString*>::value, "must be JSString derived");
+
+    AllocKind kind = MapTypeToFinalizeKind<StringAllocT>::kind;
+    size_t size = sizeof(StringAllocT);
+    MOZ_ASSERT(size == Arena::thingSize(kind));
+    MOZ_ASSERT(size == sizeof(JSString) || size == sizeof(JSFatInlineString));
+
+    // Off-thread alloc cannot trigger GC or make runtime assertions.
+    if (cx->helperThread()) {
+        StringAllocT* str = GCRuntime::tryNewTenuredThing<StringAllocT, NoGC>(cx, kind, size);
+        if (MOZ_UNLIKELY(allowGC && !str))
+            ReportOutOfMemory(cx);
+        return str;
+    }
+
+    JSRuntime* rt = cx->runtime();
+    if (!rt->gc.checkAllocatorState<allowGC>(cx, kind))
+        return nullptr;
+
+    if (cx->nursery().isEnabled() && heap != TenuredHeap) {
+        auto str = static_cast<StringAllocT*>(rt->gc.tryNewNurseryString<allowGC>(cx, size, kind));
+        if (str)
+            return str;
+
+        // Our most common non-jit allocation path is NoGC; thus, if we fail the
+        // alloc and cannot GC, we *must* return nullptr here so that the caller
+        // will do a CanGC allocation to clear the nursery. Failing to do so will
+        // cause all allocations on this path to land in Tenured, and we will not
+        // get the benefit of the nursery.
+        if (!allowGC)
+            return nullptr;
+    }
+
+    return GCRuntime::tryNewTenuredThing<StringAllocT, allowGC>(cx, kind, size);
+}
+
+#define DECL_ALLOCATOR_INSTANCES(allocKind, traceKind, type, sizedType, bgfinal, nursery) \
+    template type* js::AllocateString<type, NoGC>(JSContext* cx, InitialHeap heap);\
+    template type* js::AllocateString<type, CanGC>(JSContext* cx, InitialHeap heap);
+FOR_EACH_NURSERY_STRING_ALLOCKIND(DECL_ALLOCATOR_INSTANCES)
+#undef DECL_ALLOCATOR_INSTANCES
+
 template <typename T, AllowGC allowGC /* = CanGC */>
 T*
 js::Allocate(JSContext* cx)
 {
     static_assert(!mozilla::IsConvertible<T*, JSObject*>::value, "must not be JSObject derived");
     static_assert(sizeof(T) >= MinCellSize,
                   "All allocations must be at least the allocator-imposed minimum size.");
 
@@ -145,17 +220,17 @@ js::Allocate(JSContext* cx)
     }
 
     return GCRuntime::tryNewTenuredThing<T, allowGC>(cx, kind, thingSize);
 }
 
 #define DECL_ALLOCATOR_INSTANCES(allocKind, traceKind, type, sizedType, bgFinal, nursery) \
     template type* js::Allocate<type, NoGC>(JSContext* cx);\
     template type* js::Allocate<type, CanGC>(JSContext* cx);
-FOR_EACH_NONOBJECT_ALLOCKIND(DECL_ALLOCATOR_INSTANCES)
+FOR_EACH_NONOBJECT_NONNURSERY_ALLOCKIND(DECL_ALLOCATOR_INSTANCES)
 #undef DECL_ALLOCATOR_INSTANCES
 
 template <typename T, AllowGC allowGC>
 /* static */ T*
 GCRuntime::tryNewTenuredThing(JSContext* cx, AllocKind kind, size_t thingSize)
 {
     // Bump allocate in the arena's current free-list span.
     T* t = reinterpret_cast<T*>(cx->arenas()->allocateFromFreeList(kind, thingSize));
--- a/js/src/gc/Allocator.h
+++ b/js/src/gc/Allocator.h
@@ -27,40 +27,42 @@ Allocate(JSContext* cx);
 
 // Use for JSObject. A longer signature that includes additional information in
 // support of various optimizations.
 template <typename, AllowGC allowGC = CanGC>
 JSObject*
 Allocate(JSContext* cx, gc::AllocKind kind, size_t nDynamicSlots, gc::InitialHeap heap,
          const Class* clasp);
 
+// Internal function used for nursery-allocatable strings.
+template <typename StringAllocT, AllowGC allowGC = CanGC>
+StringAllocT*
+AllocateString(JSContext* cx, gc::InitialHeap heap);
+
 // Use for nursery-allocatable strings. Returns a value cast to the correct
 // type.
 template <typename StringT, AllowGC allowGC = CanGC>
 StringT*
 Allocate(JSContext* cx, gc::InitialHeap heap)
 {
-    MOZ_ASSERT(heap == gc::TenuredHeap);
-    return static_cast<StringT*>(js::Allocate<JSString, allowGC>(cx));
+    return static_cast<StringT*>(js::AllocateString<JSString, allowGC>(cx, heap));
 }
 
 // Specialization for JSFatInlineString that must use a different allocation
 // type. Note that we have to explicitly specialize for both values of AllowGC
 // because partial function specialization is not allowed.
 template <>
 inline JSFatInlineString*
 Allocate<JSFatInlineString, CanGC>(JSContext* cx, gc::InitialHeap heap)
 {
-    MOZ_ASSERT(heap == gc::TenuredHeap);
-    return static_cast<JSFatInlineString*>(js::Allocate<JSFatInlineString, CanGC>(cx));
+    return static_cast<JSFatInlineString*>(js::AllocateString<JSFatInlineString, CanGC>(cx, heap));
 }
 
 template <>
 inline JSFatInlineString*
 Allocate<JSFatInlineString, NoGC>(JSContext* cx, gc::InitialHeap heap)
 {
-    MOZ_ASSERT(heap == gc::TenuredHeap);
-    return static_cast<JSFatInlineString*>(js::Allocate<JSFatInlineString, NoGC>(cx));
+    return static_cast<JSFatInlineString*>(js::AllocateString<JSFatInlineString, NoGC>(cx, heap));
 }
 
 } // namespace js
 
 #endif // gc_Allocator_h
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -993,16 +993,18 @@ class GCRuntime
     template <AllowGC allowGC>
     JSObject* tryNewNurseryObject(JSContext* cx, size_t thingSize, size_t nDynamicSlots,
                                   const Class* clasp);
     template <AllowGC allowGC>
     static JSObject* tryNewTenuredObject(JSContext* cx, AllocKind kind, size_t thingSize,
                                          size_t nDynamicSlots);
     template <typename T, AllowGC allowGC>
     static T* tryNewTenuredThing(JSContext* cx, AllocKind kind, size_t thingSize);
+    template <AllowGC allowGC>
+    JSString* tryNewNurseryString(JSContext* cx, size_t thingSize, AllocKind kind);
     static TenuredCell* refillFreeListInGC(Zone* zone, AllocKind thingKind);
 
     void bufferGrayRoots();
 
     /*
      * Concurrent sweep infrastructure.
      */
     void startTask(GCParallelTask& task, gcstats::PhaseKind phase, AutoLockHelperThreadState& locked);
--- a/js/src/gc/GCTrace.cpp
+++ b/js/src/gc/GCTrace.cpp
@@ -129,16 +129,23 @@ js::gc::TraceNurseryAlloc(Cell* thing, s
         /* We don't have AllocKind here, but we can work it out from size. */
         unsigned slots = (size - sizeof(JSObject)) / sizeof(JS::Value);
         AllocKind kind = GetBackgroundAllocKind(GetGCObjectKind(slots));
         TraceEvent(TraceEventNurseryAlloc, uint64_t(thing), kind);
     }
 }
 
 void
+js::gc::TraceNurseryAlloc(Cell* thing, AllocKind kind)
+{
+    if (thing)
+        TraceEvent(TraceEventNurseryAlloc, uint64_t(thing), kind);
+}
+
+void
 js::gc::TraceTenuredAlloc(Cell* thing, AllocKind kind)
 {
     if (thing)
         TraceEvent(TraceEventTenuredAlloc, uint64_t(thing), kind);
 }
 
 static void
 MaybeTraceClass(const Class* clasp)
--- a/js/src/gc/GCTrace.h
+++ b/js/src/gc/GCTrace.h
@@ -16,32 +16,34 @@ class ObjectGroup;
 namespace gc {
 
 #ifdef JS_GC_TRACE
 
 extern MOZ_MUST_USE bool InitTrace(GCRuntime& gc);
 extern void FinishTrace();
 extern bool TraceEnabled();
 extern void TraceNurseryAlloc(Cell* thing, size_t size);
+extern void TraceNurseryAlloc(Cell* thing, AllocKind kind);
 extern void TraceTenuredAlloc(Cell* thing, AllocKind kind);
 extern void TraceCreateObject(JSObject* object);
 extern void TraceMinorGCStart();
 extern void TracePromoteToTenured(Cell* src, Cell* dst);
 extern void TraceMinorGCEnd();
 extern void TraceMajorGCStart();
 extern void TraceTenuredFinalize(Cell* thing);
 extern void TraceMajorGCEnd();
 extern void TraceTypeNewScript(js::ObjectGroup* group);
 
 #else
 
 inline MOZ_MUST_USE bool InitTrace(GCRuntime& gc) { return true; }
 inline void FinishTrace() {}
 inline bool TraceEnabled() { return false; }
 inline void TraceNurseryAlloc(Cell* thing, size_t size) {}
+inline void TraceNurseryAlloc(Cell* thing, AllocKind kind) {}
 inline void TraceTenuredAlloc(Cell* thing, AllocKind kind) {}
 inline void TraceCreateObject(JSObject* object) {}
 inline void TraceMinorGCStart() {}
 inline void TracePromoteToTenured(Cell* src, Cell* dst) {}
 inline void TraceMinorGCEnd() {}
 inline void TraceMajorGCStart() {}
 inline void TraceTenuredFinalize(Cell* thing) {}
 inline void TraceMajorGCEnd() {}
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -294,16 +294,33 @@ js::Nursery::allocateObject(JSContext* c
 
     /* Always initialize the slots field to match the JIT behavior. */
     obj->setInitialSlotsMaybeNonNative(slots);
 
     TraceNurseryAlloc(obj, size);
     return obj;
 }
 
+Cell*
+js::Nursery::allocateString(JSContext* cx, Zone* zone, size_t size, AllocKind kind)
+{
+    /* Ensure there's enough space to replace the contents with a RelocationOverlay. */
+    MOZ_ASSERT(size >= sizeof(RelocationOverlay));
+
+    size_t allocSize = JS_ROUNDUP(sizeof(StringLayout) - 1 + size, CellAlignBytes);
+    auto header = static_cast<StringLayout*>(allocate(allocSize));
+    if (!header)
+        return nullptr;
+    header->zone = zone;
+
+    auto cell = reinterpret_cast<Cell*>(&header->cell);
+    TraceNurseryAlloc(cell, kind);
+    return cell;
+}
+
 void*
 js::Nursery::allocate(size_t size)
 {
     MOZ_ASSERT(isEnabled());
     MOZ_ASSERT(!JS::CurrentThreadIsHeapBusy());
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime()));
     MOZ_ASSERT_IF(currentChunk_ == currentStartChunk_, position() >= currentStartPosition_);
     MOZ_ASSERT(position() % CellAlignBytes == 0);
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -86,17 +86,16 @@ class TenuringTracer : public JSTracer
     gc::RelocationOverlay* head;
     gc::RelocationOverlay** tail;
 
     TenuringTracer(JSRuntime* rt, Nursery* nursery);
 
   public:
     const Nursery& nursery() const { return nursery_; }
 
-    // Returns true if the pointer was updated.
     template <typename T> void traverse(T** thingp);
     template <typename T> void traverse(T* thingp);
 
     // The store buffers need to be able to call these directly.
     void traceObject(JSObject* src);
     void traceObjectSlots(NativeObject* nobj, uint32_t start, uint32_t length);
     void traceSlots(JS::Value* vp, uint32_t nslots);
 
@@ -130,16 +129,25 @@ CanNurseryAllocateFinalizedClass(const j
 }
 
 class Nursery
 {
   public:
     static const size_t Alignment = gc::ChunkSize;
     static const size_t ChunkShift = gc::ChunkShift;
 
+    struct alignas(gc::CellAlignBytes) CellAlignedByte {
+        char byte;
+    };
+
+    struct StringLayout {
+        JS::Zone* zone;
+        CellAlignedByte cell;
+    };
+
     explicit Nursery(JSRuntime* rt);
     ~Nursery();
 
     MOZ_MUST_USE bool init(uint32_t maxNurseryBytes, AutoLockGCBgAlloc& lock);
 
     unsigned chunkCountLimit() const { return chunkCountLimit_; }
 
     // Number of allocated (ready to use) chunks.
@@ -177,16 +185,39 @@ class Nursery
     inline bool isInside(const SharedMem<T>& p) const;
 
     /*
      * Allocate and return a pointer to a new GC object with its |slots|
      * pointer pre-filled. Returns nullptr if the Nursery is full.
      */
     JSObject* allocateObject(JSContext* cx, size_t size, size_t numDynamic, const js::Class* clasp);
 
+    /*
+     * Allocate and return a pointer to a new string. Returns nullptr if the
+     * Nursery is full.
+     */
+    gc::Cell* allocateString(JSContext* cx, JS::Zone* zone, size_t size, gc::AllocKind kind);
+
+    /*
+     * String zones are stored just before the string in nursery memory.
+     */
+    static JS::Zone* getStringZone(const JSString* str) {
+#ifdef DEBUG
+        auto cell = reinterpret_cast<const js::gc::Cell*>(str); // JSString type is incomplete here
+        MOZ_ASSERT(js::gc::IsInsideNursery(cell), "getStringZone must be passed a nursery string");
+#endif
+
+        auto layout = reinterpret_cast<const uint8_t*>(str) - offsetof(StringLayout, cell);
+        return reinterpret_cast<const StringLayout*>(layout)->zone;
+    }
+
+    static size_t stringHeaderSize() {
+        return offsetof(StringLayout, cell);
+    }
+
     /* Allocate a buffer for a given zone, using the nursery if possible. */
     void* allocateBuffer(JS::Zone* zone, size_t nbytes);
 
     /*
      * Allocate a buffer for a given object, using the nursery if possible and
      * obj is in the nursery.
      */
     void* allocateBuffer(JSObject* obj, size_t nbytes);
--- a/js/src/jit-test/tests/heap-analysis/byteSize-of-string.js
+++ b/js/src/jit-test/tests/heap-analysis/byteSize-of-string.js
@@ -8,41 +8,51 @@
 
 // Run this test only if we're using jemalloc. Other malloc implementations
 // exhibit surprising behaviors. For example, 32-bit Fedora builds have
 // non-deterministic allocation sizes.
 var config = getBuildConfiguration();
 if (!config['moz-memory'])
   quit(0);
 
+gczeal(0); // Need to control when tenuring happens
+
+// Ion eager runs much of this code in Ion, and Ion nursery-allocates more
+// aggressively than other modes.
+if (getJitCompilerOptions()["ion.warmup.trigger"] <= 100)
+    setJitCompilerOption("ion.warmup.trigger", 100);
+
 if (config['pointer-byte-size'] == 4)
   var s = (s32, s64) => s32
 else
   var s = (s32, s64) => s64
 
 // Convert an input string, which is probably an atom because it's a literal in
-// the source text, to a regular (non-rope) string with the same contents.
+// the source text, to a nursery-allocated string with the same contents.
 function copyString(str) {
   if (str.length == 0)
     return str; // Nothing we can do here
   return ensureFlatString(str.substr(0, 1) + str.substr(1));
 }
 
+// Return the nursery byte size of |str|.
+function nByteSize(str) {
+  // Strings that appear in the source will always be atomized and therefore
+  // will never be in the nursery.
+  return byteSize(copyString(str));
+}
+
 // Return the tenured byte size of |str|.
 function tByteSize(str) {
+  // Strings that appear in the source will always be atomized and therefore
+  // will never be in the nursery. But we'll make them get tenured instead of
+  // using the atom.
   str = copyString(str);
-  let nurserySize = byteSize(str);
   minorgc();
-  var tenuredSize = byteSize(str);
-  if (nurserySize != tenuredSize) {
-    print("nursery size: " + nurserySize + "  tenured size: " + tenuredSize);
-    return -1; // make the stack trace point at the real test
-  }
-
-  return tenuredSize;
+  return byteSize(str);
 }
 
 // There are four representations of flat strings, with the following capacities
 // (excluding a terminating null character):
 //
 //                      32-bit                  64-bit                test
 // representation       Latin-1   char16_t      Latin-1   char16_t    label
 // ========================================================================
@@ -50,38 +60,57 @@ function tByteSize(str) {
 // JSThinInlineString   7         3             15        7           T
 // JSFatInlineString    23        11            23        11          F
 // JSExtensibleString          - limited by available memory -        X
 // JSUndependedString          - same as JSExtensibleString -
 
 // Notes:
 //  - labels are suffixed with A for atoms and N for non-atoms
 //  - atoms are 8 bytes larger than non-atoms, to store the atom's hash code.
+//  - Nursery-allocated strings require a header that stores the zone.
 
 // Expected sizes based on type of string
 const m32 = (config['pointer-byte-size'] == 4);
 const TA = m32 ? 24 : 32; // ThinInlineString atom, includes a hash value
 const TN = m32 ? 16 : 24; // ThinInlineString
 const FN = m32 ? 32 : 32; // FatInlineString
 const XN = m32 ? 16 : 24; // ExtensibleString, has additional storage buffer
 const RN = m32 ? 16 : 24; // Rope
 const DN = m32 ? 16 : 24; // DependentString
 
+// A function that pads out a tenured size to the nursery size. We store a zone
+// pointer in the nursery just before the string (4 bytes on 32-bit, 8 bytes on
+// 64-bit), and the string struct itself must be 8-byte aligned (resulting in
+// +4 bytes on 32-bit, +0 bytes on 64-bit). The end result? Nursery strings are
+// 8 bytes larger.
+const Nursery = m32 ? s => s + 4 + 4 : s => s + 8 + 0;
+
 // Latin-1
 assertEq(tByteSize(""),                                               s(TA, TA));
 assertEq(tByteSize("1"),                                              s(TA, TA));
 assertEq(tByteSize("1234567"),                                        s(TN, TN));
 assertEq(tByteSize("12345678"),                                       s(FN, TN));
 assertEq(tByteSize("123456789.12345"),                                s(FN, TN));
 assertEq(tByteSize("123456789.123456"),                               s(FN, FN));
 assertEq(tByteSize("123456789.123456789.123"),                        s(FN, FN));
 assertEq(tByteSize("123456789.123456789.1234"),                       s(XN+32, XN+32));
 assertEq(tByteSize("123456789.123456789.123456789.1"),                s(XN+32, XN+32));
 assertEq(tByteSize("123456789.123456789.123456789.12"),               s(XN+64, XN+64));
 
+assertEq(nByteSize(""),                                               s(TA, TA));
+assertEq(nByteSize("1"),                                              s(TA, TA));
+assertEq(nByteSize("1234567"),                                        s(Nursery(TN), Nursery(TN)));
+assertEq(nByteSize("12345678"),                                       s(Nursery(FN), Nursery(TN)));
+assertEq(nByteSize("123456789.12345"),                                s(Nursery(FN), Nursery(TN)));
+assertEq(nByteSize("123456789.123456"),                               s(Nursery(FN), Nursery(FN)));
+assertEq(nByteSize("123456789.123456789.123"),                        s(Nursery(FN), Nursery(FN)));
+assertEq(nByteSize("123456789.123456789.1234"),                       s(Nursery(XN)+32,Nursery(XN)+32));
+assertEq(nByteSize("123456789.123456789.123456789.1"),                s(Nursery(XN)+32,Nursery(XN)+32));
+assertEq(nByteSize("123456789.123456789.123456789.12"),               s(Nursery(XN)+64,Nursery(XN)+64));
+
 // Inline char16_t atoms.
 // "Impassionate gods have never seen the red that is the Tatsuta River."
 //   - Ariwara no Narihira
 assertEq(tByteSize("千"),						s(TA, TA));
 assertEq(tByteSize("千早"),						s(TN, TN));
 assertEq(tByteSize("千早ぶ"),						s(TN, TN));
 assertEq(tByteSize("千早ぶる"),						s(FN, TN));
 assertEq(tByteSize("千早ぶる神"),						s(FN, TN));
@@ -91,63 +120,81 @@ assertEq(tByteSize("千早ぶる神代もき"),					s(FN, FN));
 assertEq(tByteSize("千早ぶる神代もきかず龍"),				s(FN, FN));
 assertEq(tByteSize("千早ぶる神代もきかず龍田"),				s(XN+32, XN+32));
 assertEq(tByteSize("千早ぶる神代もきかず龍田川 か"),				s(XN+32, XN+32));
 assertEq(tByteSize("千早ぶる神代もきかず龍田川 から"),			s(XN+64, XN+64));
 assertEq(tByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水く"),		s(XN+64, XN+64));
 assertEq(tByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水くく"),		s(XN+64, XN+64));
 assertEq(tByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水くくるとは"),	s(XN+64, XN+64));
 
+assertEq(nByteSize("千"),						s(TA, TA));
+assertEq(nByteSize("千早"),						s(Nursery(TN), Nursery(TN)));
+assertEq(nByteSize("千早ぶ"),						s(Nursery(TN), Nursery(TN)));
+assertEq(nByteSize("千早ぶる"),						s(Nursery(FN), Nursery(TN)));
+assertEq(nByteSize("千早ぶる神"),						s(Nursery(FN), Nursery(TN)));
+assertEq(nByteSize("千早ぶる神代"),					s(Nursery(FN), Nursery(TN)));
+assertEq(nByteSize("千早ぶる神代も"),					s(Nursery(FN), Nursery(TN)));
+assertEq(nByteSize("千早ぶる神代もき"),					s(Nursery(FN), Nursery(FN)));
+assertEq(nByteSize("千早ぶる神代もきかず龍"),				s(Nursery(FN), Nursery(FN)));
+assertEq(nByteSize("千早ぶる神代もきかず龍田"),				s(Nursery(XN)+32, Nursery(XN)+32));
+assertEq(nByteSize("千早ぶる神代もきかず龍田川 か"),				s(Nursery(XN)+32, Nursery(XN)+32));
+assertEq(nByteSize("千早ぶる神代もきかず龍田川 から"),			s(Nursery(XN)+64, Nursery(XN)+64));
+assertEq(nByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水く"),		s(Nursery(XN)+64, Nursery(XN)+64));
+assertEq(nByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水くく"),		s(Nursery(XN)+64, Nursery(XN)+64));
+assertEq(nByteSize("千早ぶる神代もきかず龍田川 からくれなゐに水くくるとは"),	s(Nursery(XN)+64, Nursery(XN)+64));
+
 // A Latin-1 rope. This changes size when flattened.
 // "In a village of La Mancha, the name of which I have no desire to call to mind"
 //   - Miguel de Cervantes, Don Quixote
 var fragment8 = "En un lugar de la Mancha, de cuyo nombre no quiero acordarme"; // 60 characters
 var rope8 = fragment8;
 for (var i = 0; i < 10; i++) // 1024 repetitions
   rope8 = rope8 + rope8;
 
-assertEq(byteSize(rope8),						s(RN, RN));
+assertEq(byteSize(rope8),                                               s(Nursery(RN), Nursery(RN)));
+minorgc();
+assertEq(byteSize(rope8),                                               s(RN, RN));
 var matches8 = rope8.match(/(de cuyo nombre no quiero acordarme)/);
-assertEq(byteSize(rope8),						s(XN + 65536, XN + 65536));
+assertEq(byteSize(rope8),                                               s(XN + 65536, XN + 65536));
 
 // Test extensible strings.
 //
 // Appending another copy of the fragment should yield another rope.
 //
 // Flatting that should turn the original rope into a dependent string, and
 // yield a new linear string, of the same size as the original.
 rope8a = rope8 + fragment8;
-assertEq(byteSize(rope8a),						s(RN, RN));
+assertEq(byteSize(rope8a),                                              s(Nursery(RN), Nursery(RN)));
 rope8a.match(/x/, function() { assertEq(true, false); });
-assertEq(byteSize(rope8a),						s(XN + 65536, XN + 65536));
-assertEq(byteSize(rope8),						s(RN, RN));
+assertEq(byteSize(rope8a),                                              s(Nursery(XN) + 65536, Nursery(XN) + 65536));
+assertEq(byteSize(rope8),                                               s(RN, RN));
 
 
 // A char16_t rope. This changes size when flattened.
 // "From the Heliconian Muses let us begin to sing"
 //   --- Hesiod, Theogony
 var fragment16 = "μουσάων Ἑλικωνιάδων ἀρχώμεθ᾽ ἀείδειν";
 var rope16 = fragment16;
 for (var i = 0; i < 10; i++) // 1024 repetitions
   rope16 = rope16 + rope16;
-assertEq(byteSize(rope16),						s(RN, RN));
+assertEq(byteSize(rope16),                                              s(Nursery(RN), Nursery(RN)));
 let matches16 = rope16.match(/(Ἑλικωνιάδων ἀρχώμεθ᾽)/);
-assertEq(byteSize(rope16),						s(RN + 131072, RN + 131072));
+assertEq(byteSize(rope16),                                              s(Nursery(RN) + 131072, Nursery(RN) + 131072));
 
 // Latin-1 and char16_t dependent strings.
-assertEq(byteSize(rope8.substr(1000, 2000)),				s(DN, DN));
-assertEq(byteSize(rope16.substr(1000, 2000)),				s(DN, DN));
-assertEq(byteSize(matches8[0]),						s(DN, DN));
-assertEq(byteSize(matches8[1]),						s(DN, DN));
-assertEq(byteSize(matches16[0]),					s(DN, DN));
-assertEq(byteSize(matches16[1]),					s(DN, DN));
+assertEq(byteSize(rope8.substr(1000, 2000)),                            s(Nursery(DN), Nursery(DN)));
+assertEq(byteSize(rope16.substr(1000, 2000)),                           s(Nursery(DN), Nursery(DN)));
+assertEq(byteSize(matches8[0]),                                         s(Nursery(DN), Nursery(DN)));
+assertEq(byteSize(matches8[1]),                                         s(Nursery(DN), Nursery(DN)));
+assertEq(byteSize(matches16[0]),                                        s(Nursery(DN), Nursery(DN)));
+assertEq(byteSize(matches16[1]),                                        s(Nursery(DN), Nursery(DN)));
 
 // Test extensible strings.
 //
 // Appending another copy of the fragment should yield another rope.
 //
 // Flatting that should turn the original rope into a dependent string, and
 // yield a new linear string, of the some size as the original.
 rope16a = rope16 + fragment16;
-assertEq(byteSize(rope16a),						s(RN, RN));
+assertEq(byteSize(rope16a),                                             s(Nursery(RN), Nursery(RN)));
 rope16a.match(/x/, function() { assertEq(true, false); });
-assertEq(byteSize(rope16a),						s(XN + 131072, XN + 131072));
-assertEq(byteSize(rope16),						s(XN, XN));
+assertEq(byteSize(rope16a),                                             s(Nursery(XN) + 131072, Nursery(XN) + 131072));
+assertEq(byteSize(rope16),                                              s(Nursery(XN), Nursery(XN)));
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -1609,24 +1609,24 @@ CreateDependentString::generate(MacroAss
 
     masm.bind(&done);
 }
 
 static void*
 AllocateString(JSContext* cx)
 {
     AutoUnsafeCallWithABI unsafe;
-    return js::Allocate<JSString, NoGC>(cx);
+    return js::Allocate<JSString, NoGC>(cx, js::gc::TenuredHeap);
 }
 
 static void*
 AllocateFatInlineString(JSContext* cx)
 {
     AutoUnsafeCallWithABI unsafe;
-    return js::Allocate<JSFatInlineString, NoGC>(cx);
+    return js::Allocate<JSFatInlineString, NoGC>(cx, js::gc::TenuredHeap);
 }
 
 void
 CreateDependentString::generateFallback(MacroAssembler& masm, LiveRegisterSet regsToSave)
 {
     regsToSave.take(string_);
     regsToSave.take(temp_);
     for (FallbackKind kind : mozilla::MakeEnumeratedRange(FallbackKind::Count)) {
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -207,16 +207,17 @@ JSString::dumpRepresentationHeader(js::G
     out.printf("((%s*) %p) length: %zu  flags: 0x%x", subclass, this, length(), flags);
     if (flags & FLAT_BIT)               out.put(" FLAT");
     if (flags & HAS_BASE_BIT)           out.put(" HAS_BASE");
     if (flags & INLINE_CHARS_BIT)       out.put(" INLINE_CHARS");
     if (flags & NON_ATOM_BIT)           out.put(" NON_ATOM");
     if (isPermanentAtom())              out.put(" PERMANENT");
     if (flags & LATIN1_CHARS_BIT)       out.put(" LATIN1");
     if (flags & INDEX_VALUE_BIT)        out.put(" INDEX_VALUE(%u)", getIndexValue());
+    if (!isTenured())                   out.put(" NURSERY");
     out.putChar('\n');
 }
 
 void
 JSLinearString::dumpRepresentationChars(js::GenericPrinter& out, int indent) const
 {
     if (hasLatin1Chars()) {
         out.printf("%*schars: ((Latin1Char*) %p) ", indent, "", rawLatin1Chars());