Bug 1085597 - Allow objects with finalization and move ops to be nursery allocated; r=jonco
authorTerrence Cole <terrence@mozilla.com>
Thu, 23 Oct 2014 13:49:17 -0700
changeset 248895 f0503a20819c61910864f7c3d62113db37053e85
parent 248894 cc46a830c58daee491985d863741eee781221904
child 248896 311c6349e630d72a4eb2396db21a1a0afb6b3b42
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1085597
milestone37.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 1085597 - Allow objects with finalization and move ops to be nursery allocated; r=jonco
js/public/Class.h
js/src/gc/Nursery.cpp
js/src/gc/Nursery.h
js/src/jit/CodeGenerator.cpp
js/src/jit/VMFunctions.cpp
js/src/jit/VMFunctions.h
js/src/jsapi-tests/moz.build
js/src/jsapi-tests/testGCNursery.cpp
js/src/jsgc.cpp
js/src/jsgcinlines.h
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/vm/ArrayObject-inl.h
js/src/vm/Runtime-inl.h
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -433,17 +433,17 @@ struct JSClass {
 
 #define JSCLASS_IS_ANONYMOUS            (1<<(JSCLASS_HIGH_FLAGS_SHIFT+0))
 #define JSCLASS_IS_GLOBAL               (1<<(JSCLASS_HIGH_FLAGS_SHIFT+1))
 #define JSCLASS_INTERNAL_FLAG2          (1<<(JSCLASS_HIGH_FLAGS_SHIFT+2))
 #define JSCLASS_INTERNAL_FLAG3          (1<<(JSCLASS_HIGH_FLAGS_SHIFT+3))
 
 #define JSCLASS_IS_PROXY                (1<<(JSCLASS_HIGH_FLAGS_SHIFT+4))
 
-// Bit 22 unused.
+#define JSCLASS_FINALIZE_FROM_NURSERY   (1<<(JSCLASS_HIGH_FLAGS_SHIFT+5))
 
 // Reserved for embeddings.
 #define JSCLASS_USERBIT2                (1<<(JSCLASS_HIGH_FLAGS_SHIFT+6))
 #define JSCLASS_USERBIT3                (1<<(JSCLASS_HIGH_FLAGS_SHIFT+7))
 
 #define JSCLASS_BACKGROUND_FINALIZE     (1<<(JSCLASS_HIGH_FLAGS_SHIFT+8))
 
 // Bits 26 through 31 are reserved for the CACHED_PROTO_KEY mechanism, see
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -149,21 +149,48 @@ js::Nursery::leaveZealMode() {
     if (isEnabled()) {
         MOZ_ASSERT(isEmpty());
         setCurrentChunk(0);
         currentStart_ = start();
     }
 }
 #endif // JS_GC_ZEAL
 
+void
+js::Nursery::verifyFinalizerList()
+{
+#ifdef DEBUG
+    for (ListItem *current = finalizers_; current; current = current->next()) {
+        JSObject *obj = current->get();
+        RelocationOverlay *overlay = RelocationOverlay::fromCell(obj);
+        if (overlay->isForwarded())
+            obj = static_cast<JSObject *>(overlay->forwardingAddress());
+        MOZ_ASSERT(obj);
+        MOZ_ASSERT(obj->type());
+        MOZ_ASSERT(obj->type()->clasp());
+        MOZ_ASSERT(obj->type()->clasp()->finalize);
+        MOZ_ASSERT(obj->type()->clasp()->flags & JSCLASS_FINALIZE_FROM_NURSERY);
+    }
+#endif // DEBUG
+}
+
 JSObject *
-js::Nursery::allocateObject(JSContext *cx, size_t size, size_t numDynamic)
+js::Nursery::allocateObject(JSContext *cx, size_t size, size_t numDynamic, const js::Class *clasp)
 {
     /* Ensure there's enough space to replace the contents with a RelocationOverlay. */
     MOZ_ASSERT(size >= sizeof(RelocationOverlay));
+    verifyFinalizerList();
+
+    /* If we have a finalizer, get space for the list entry. */
+    ListItem *listEntry = nullptr;
+    if (clasp->finalize) {
+        listEntry = static_cast<ListItem *>(allocate(sizeof(ListItem)));
+        if (!listEntry)
+            return nullptr;
+    }
 
     /* Make the object allocation. */
     JSObject *obj = static_cast<JSObject *>(allocate(size));
     if (!obj)
         return nullptr;
 
     /* If we want external slots, add them. */
     HeapSlot *slots = nullptr;
@@ -180,16 +207,23 @@ js::Nursery::allocateObject(JSContext *c
          * not visit unallocated things. */
         if (!slots)
             return nullptr;
     }
 
     /* Always initialize the slots field to match the JIT behavior. */
     obj->setInitialSlotsMaybeNonNative(slots);
 
+    /* If we have a finalizer, link it into the finalizer list. */
+    if (clasp->finalize) {
+        MOZ_ASSERT(listEntry);
+        new (listEntry) ListItem(finalizers_, obj);
+        finalizers_ = listEntry;
+    }
+
     TraceNurseryAlloc(obj, size);
     return obj;
 }
 
 void *
 js::Nursery::allocate(size_t size)
 {
     MOZ_ASSERT(isEnabled());
@@ -401,17 +435,18 @@ GetObjectAllocKindForCopy(const Nursery 
     if (obj->is<OutlineTypedObject>())
         return FINALIZE_OBJECT0;
 
     // The only non-native objects in existence are proxies and typed objects.
     MOZ_ASSERT(obj->isNative());
 
     AllocKind kind = GetGCObjectFixedSlotsKind(obj->as<NativeObject>().numFixedSlots());
     MOZ_ASSERT(!IsBackgroundFinalized(kind));
-    MOZ_ASSERT(CanBeFinalizedInBackground(kind, obj->getClass()));
+    if (!CanBeFinalizedInBackground(kind, obj->getClass()))
+        return kind;
     return GetBackgroundAllocKind(kind);
 }
 
 MOZ_ALWAYS_INLINE TenuredCell *
 js::Nursery::allocateFromTenured(Zone *zone, AllocKind thingKind)
 {
     TenuredCell *t =
         zone->allocator.arenas.allocateFromFreeList(thingKind, Arena::thingSize(thingKind));
@@ -585,16 +620,17 @@ js::Nursery::markSlot(MinorCollectionTra
 
     JSObject *tenured = static_cast<JSObject*>(moveToTenured(trc, obj));
     slotp->unsafeGet()->setObject(*tenured);
 }
 
 void *
 js::Nursery::moveToTenured(MinorCollectionTracer *trc, JSObject *src)
 {
+
     AllocKind dstKind = GetObjectAllocKindForCopy(*this, src);
     Zone *zone = src->zone();
     JSObject *dst = reinterpret_cast<JSObject *>(allocateFromTenured(zone, dstKind));
     if (!dst)
         CrashAtUnhandlableOOM("Failed to allocate object while tenuring.");
 
     trc->tenuredSize += moveObjectToTenured(trc, dst, src, dstKind);
 
@@ -823,16 +859,33 @@ js::Nursery::collect(JSRuntime *rt, JS::
     TIME_END(sweepArrayBufferViewList);
 
     // Update any slot or element pointers whose destination has been tenured.
     TIME_START(updateJitActivations);
     js::jit::UpdateJitActivationsForMinorGC(&rt->mainThread, &trc);
     forwardedBuffers.finish();
     TIME_END(updateJitActivations);
 
+    // Sweep.
+    TIME_START(runFinalizers);
+    runFinalizers();
+    TIME_END(runFinalizers);
+
+    TIME_START(freeHugeSlots);
+    freeHugeSlots();
+    TIME_END(freeHugeSlots);
+
+    TIME_START(sweep);
+    sweep();
+    TIME_END(sweep);
+
+    TIME_START(clearStoreBuffer);
+    rt->gc.storeBuffer.clear();
+    TIME_END(clearStoreBuffer);
+
     // Resize the nursery.
     TIME_START(resize);
     double promotionRate = trc.tenuredSize / double(allocationEnd() - start());
     if (promotionRate > 0.05)
         growAllocableSpace();
     else if (promotionRate < 0.01)
         shrinkAllocableSpace();
     TIME_END(resize);
@@ -846,51 +899,38 @@ js::Nursery::collect(JSRuntime *rt, JS::
         for (size_t i = 0; i < ArrayLength(tenureCounts.entries); i++) {
             const TenureCount &entry = tenureCounts.entries[i];
             if (entry.count >= 3000)
                 pretenureTypes->append(entry.type); // ignore alloc failure
         }
     }
     TIME_END(pretenure);
 
-    // Sweep.
-    TIME_START(freeHugeSlots);
-    freeHugeSlots();
-    TIME_END(freeHugeSlots);
-
-    TIME_START(sweep);
-    sweep();
-    TIME_END(sweep);
-
-    TIME_START(clearStoreBuffer);
-    rt->gc.storeBuffer.clear();
-    TIME_END(clearStoreBuffer);
-
     // We ignore gcMaxBytes when allocating for minor collection. However, if we
     // overflowed, we disable the nursery. The next time we allocate, we'll fail
     // because gcBytes >= gcMaxBytes.
     if (rt->gc.usage.gcBytes() >= rt->gc.tunables.gcMaxBytes())
         disable();
 
     TIME_END(total);
 
     TraceMinorGCEnd();
 
     int64_t totalTime = TIME_TOTAL(total);
     if (enableProfiling_ && totalTime >= profileThreshold_) {
         static bool printedHeader = false;
         if (!printedHeader) {
             fprintf(stderr,
-                    "MinorGC: Reason               PRate  Size Time   mkVals mkClls mkSlts mkWCll mkRVal mkRCll mkGnrc ckTbls mkRntm mkDbgr clrNOC collct swpABO updtIn resize pretnr frSlts clrSB  sweep\n");
+                    "MinorGC: Reason               PRate  Size Time   mkVals mkClls mkSlts mkWCll mkRVal mkRCll mkGnrc ckTbls mkRntm mkDbgr clrNOC collct swpABO updtIn runFin frSlts clrSB  sweep resize pretnr\n");
             printedHeader = true;
         }
 
 #define FMT " %6" PRIu64
         fprintf(stderr,
-                "MinorGC: %20s %5.1f%% %4d" FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT "\n",
+                "MinorGC: %20s %5.1f%% %4d" FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT "\n",
                 js::gcstats::ExplainReason(reason),
                 promotionRate * 100,
                 numActiveChunks_,
                 totalTime,
                 TIME_TOTAL(markValues),
                 TIME_TOTAL(markCells),
                 TIME_TOTAL(markSlots),
                 TIME_TOTAL(markWholeCells),
@@ -899,21 +939,22 @@ js::Nursery::collect(JSRuntime *rt, JS::
                 TIME_TOTAL(markGenericEntries),
                 TIME_TOTAL(checkHashTables),
                 TIME_TOTAL(markRuntime),
                 TIME_TOTAL(markDebugger),
                 TIME_TOTAL(clearNewObjectCache),
                 TIME_TOTAL(collectToFP),
                 TIME_TOTAL(sweepArrayBufferViewList),
                 TIME_TOTAL(updateJitActivations),
-                TIME_TOTAL(resize),
-                TIME_TOTAL(pretenure),
+                TIME_TOTAL(runFinalizers),
                 TIME_TOTAL(freeHugeSlots),
                 TIME_TOTAL(clearStoreBuffer),
-                TIME_TOTAL(sweep));
+                TIME_TOTAL(sweep),
+                TIME_TOTAL(resize),
+                TIME_TOTAL(pretenure));
 #undef FMT
     }
 }
 
 #undef TIME_START
 #undef TIME_END
 #undef TIME_TOTAL
 
@@ -922,16 +963,31 @@ js::Nursery::freeHugeSlots()
 {
     FreeOp *fop = runtime()->defaultFreeOp();
     for (HugeSlotsSet::Range r = hugeSlots.all(); !r.empty(); r.popFront())
         fop->free_(r.front());
     hugeSlots.clear();
 }
 
 void
+js::Nursery::runFinalizers()
+{
+    verifyFinalizerList();
+
+    FreeOp *fop = runtime()->defaultFreeOp();
+    for (ListItem *current = finalizers_; current; current = current->next()) {
+        JSObject *obj = current->get();
+        RelocationOverlay *overlay = RelocationOverlay::fromCell(obj);
+        if (!overlay->isForwarded())
+            obj->getClass()->finalize(fop, obj);
+    }
+    finalizers_ = nullptr;
+}
+
+void
 js::Nursery::sweep()
 {
 #ifdef JS_GC_ZEAL
     /* Poison the nursery contents so touching a freed object will crash. */
     JS_POISON((void *)start(), JS_SWEPT_NURSERY_PATTERN, nurserySize());
     for (int i = 0; i < numNurseryChunks_; ++i)
         initChunk(i);
 
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -9,16 +9,17 @@
 #define gc_Nursery_h
 
 #include "jsalloc.h"
 #include "jspubtd.h"
 
 #include "ds/BitArray.h"
 #include "gc/Heap.h"
 #include "gc/Memory.h"
+#include "js/Class.h"
 #include "js/GCAPI.h"
 #include "js/HashTable.h"
 #include "js/HeapAPI.h"
 #include "js/Value.h"
 #include "js/Vector.h"
 
 namespace JS {
 struct Zone;
@@ -60,16 +61,17 @@ class Nursery
         position_(0),
         currentStart_(0),
         currentEnd_(0),
         heapStart_(0),
         heapEnd_(0),
         currentChunk_(0),
         numActiveChunks_(0),
         numNurseryChunks_(0),
+        finalizers_(nullptr),
         profileThreshold_(0),
         enableProfiling_(false)
     {}
     ~Nursery();
 
     bool init(uint32_t maxNurseryBytes);
 
     bool exists() const { return numNurseryChunks_ != 0; }
@@ -91,17 +93,17 @@ class Nursery
     MOZ_ALWAYS_INLINE bool isInside(const void *p) const {
         return uintptr_t(p) >= heapStart_ && uintptr_t(p) < heapEnd_;
     }
 
     /*
      * 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);
+    JSObject *allocateObject(JSContext *cx, size_t size, size_t numDynamic, const js::Class *clasp);
 
     /* Allocate a slots array for the given object. */
     HeapSlot *allocateSlots(JSObject *obj, uint32_t nslots);
 
     /* Allocate an elements vector for the given object. */
     ObjectElements *allocateElements(JSObject *obj, uint32_t nelems);
 
     /* Resize an existing slots array. */
@@ -195,16 +197,26 @@ class Nursery
     int currentChunk_;
 
     /* The index after the last chunk that we will allocate from. */
     int numActiveChunks_;
 
     /* Number of chunks allocated for the nursery. */
     int numNurseryChunks_;
 
+    /* Keep track of objects that need finalization. */
+    class ListItem {
+        ListItem *next_;
+        JSObject *object_;
+      public:
+        ListItem(ListItem *tail, JSObject *obj) : next_(tail), object_(obj) {}
+        ListItem *next() const { return next_; }
+        JSObject *get() { return object_; }
+    } *finalizers_;
+
     /* Report minor collections taking more than this many us, if enabled. */
     int64_t profileThreshold_;
     bool enableProfiling_;
 
     /*
      * The set of externally malloced slots potentially kept live by objects
      * stored in the nursery. Any external slots that do not belong to a
      * tenured thing at the end of a minor GC must be freed.
@@ -285,16 +297,17 @@ class Nursery
 
     /* Allocates a new GC thing from the tenured generation during minor GC. */
     gc::TenuredCell *allocateFromTenured(JS::Zone *zone, gc::AllocKind thingKind);
 
     struct TenureCountCache;
 
     /* Common internal allocator function. */
     void *allocate(size_t size);
+    void verifyFinalizerList();
 
     /*
      * Move the object at |src| in the Nursery to an already-allocated cell
      * |dst| in Tenured.
      */
     void collectToFixedPoint(gc::MinorCollectionTracer *trc, TenureCountCache &tenureCounts);
     MOZ_ALWAYS_INLINE void traceObject(gc::MinorCollectionTracer *trc, JSObject *src);
     MOZ_ALWAYS_INLINE void markSlots(gc::MinorCollectionTracer *trc, HeapSlot *vp, uint32_t nslots);
@@ -308,16 +321,19 @@ class Nursery
 
     /* Handle relocation of slots/elements pointers stored in Ion frames. */
     void setForwardingPointer(void *oldData, void *newData, bool direct);
 
     void setSlotsForwardingPointer(HeapSlot *oldSlots, HeapSlot *newSlots, uint32_t nslots);
     void setElementsForwardingPointer(ObjectElements *oldHeader, ObjectElements *newHeader,
                                       uint32_t nelems);
 
+    /* Run finalizers on all finalizable things in the nursery. */
+    void runFinalizers();
+
     /* Free malloced pointers owned by freed things in the nursery. */
     void freeHugeSlots();
 
     /*
      * Frees all non-live nursery-allocated things at the end of a minor
      * collection.
      */
     void sweep();
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -4425,31 +4425,33 @@ CodeGenerator::visitCreateThisWithProto(
         pushArg(ImmGCPtr(&callee->toConstant()->toObject()));
     else
         pushArg(ToRegister(callee));
 
     callVM(CreateThisWithProtoInfo, lir);
 }
 
 typedef JSObject *(*NewGCObjectFn)(JSContext *cx, gc::AllocKind allocKind,
-                                   gc::InitialHeap initialHeap);
+                                   gc::InitialHeap initialHeap, const js::Class *clasp);
 static const VMFunction NewGCObjectInfo =
     FunctionInfo<NewGCObjectFn>(js::jit::NewGCObject);
 
 void
 CodeGenerator::visitCreateThisWithTemplate(LCreateThisWithTemplate *lir)
 {
     PlainObject *templateObject = lir->mir()->templateObject();
     gc::AllocKind allocKind = templateObject->asTenured().getAllocKind();
     gc::InitialHeap initialHeap = lir->mir()->initialHeap();
+    const js::Class *clasp = templateObject->type()->clasp();
     Register objReg = ToRegister(lir->output());
     Register tempReg = ToRegister(lir->temp());
 
     OutOfLineCode *ool = oolCallVM(NewGCObjectInfo, lir,
-                                   (ArgList(), Imm32(allocKind), Imm32(initialHeap)),
+                                   (ArgList(), Imm32(allocKind), Imm32(initialHeap),
+                                    ImmPtr(clasp)),
                                    StoreRegisterTo(objReg));
 
     // Allocate. If the FreeList is empty, call to VM, which may GC.
     masm.newGCThing(objReg, tempReg, templateObject, lir->mir()->initialHeap(), ool->entry());
 
     // Initialize based on the templateObject.
     masm.bind(ool->rejoin());
 
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -100,19 +100,20 @@ InvokeFunction(JSContext *cx, HandleObje
         types::TypeScript::Monitor(cx, script, pc, rv.get());
     }
 
     *rval = rv;
     return true;
 }
 
 JSObject *
-NewGCObject(JSContext *cx, gc::AllocKind allocKind, gc::InitialHeap initialHeap)
+NewGCObject(JSContext *cx, gc::AllocKind allocKind, gc::InitialHeap initialHeap,
+            const js::Class *clasp)
 {
-    return js::NewGCObject<CanGC>(cx, allocKind, 0, initialHeap);
+    return js::NewGCObject<CanGC>(cx, allocKind, 0, initialHeap, clasp);
 }
 
 bool
 CheckOverRecursed(JSContext *cx)
 {
     // We just failed the jitStackLimit check. There are two possible reasons:
     //  - jitStackLimit was the real stack limit and we're over-recursed
     //  - jitStackLimit was set to UINTPTR_MAX by JSRuntime::requestInterrupt
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -655,17 +655,18 @@ class AutoDetectInvalidation
 
     ~AutoDetectInvalidation() {
         if (!disabled_ && ionScript_->invalidated())
             setReturnOverride();
     }
 };
 
 bool InvokeFunction(JSContext *cx, HandleObject obj0, uint32_t argc, Value *argv, Value *rval);
-JSObject *NewGCObject(JSContext *cx, gc::AllocKind allocKind, gc::InitialHeap initialHeap);
+JSObject *NewGCObject(JSContext *cx, gc::AllocKind allocKind, gc::InitialHeap initialHeap,
+                      const js::Class *clasp);
 
 bool CheckOverRecursed(JSContext *cx);
 bool CheckOverRecursedWithExtra(JSContext *cx, BaselineFrame *frame,
                                 uint32_t extra, uint32_t earlyCheck);
 
 bool DefVarOrConst(JSContext *cx, HandlePropertyName dn, unsigned attrs, HandleObject scopeChain);
 bool SetConst(JSContext *cx, HandlePropertyName name, HandleObject scopeChain, HandleValue rval);
 bool MutatePrototype(JSContext *cx, HandlePlainObject obj, HandleValue value);
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -35,16 +35,17 @@ UNIFIED_SOURCES += [
     'testFunctionProperties.cpp',
     'testGCAllocator.cpp',
     'testGCCellPtr.cpp',
     'testGCChunkPool.cpp',
     'testGCExactRooting.cpp',
     'testGCFinalizeCallback.cpp',
     'testGCHeapPostBarriers.cpp',
     'testGCMarking.cpp',
+    'testGCNursery.cpp',
     'testGCOutOfMemory.cpp',
     'testGCStoreBufferRemoval.cpp',
     'testHashTable.cpp',
     'testHashTableInit.cpp',
     'testIndexToString.cpp',
     'testIntern.cpp',
     'testIntString.cpp',
     'testIntTypesABI.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/jsapi-tests/testGCNursery.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+* vim: set ts=8 sts=4 et sw=4 tw=99:
+*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gc/Nursery.h"
+#include "js/GCAPI.h"
+#include "jsapi-tests/tests.h"
+
+static int ranFinalizer = 0;
+
+void
+_finalize(js::FreeOp *fop, JSObject *obj)
+{
+    JS::AutoAssertGCCallback suppress(obj);
+    ++ranFinalizer;
+}
+
+static const js::Class TenuredClass = {
+    "TenuredClass",
+    0,
+    nullptr, /* addProperty */
+    nullptr, /* delProperty */
+    nullptr, /* getProperty */
+    nullptr, /* setProperty */
+    nullptr, /* enumerate */
+    nullptr, /* resolve */
+    nullptr, /* ??? */
+    _finalize,
+    nullptr, /* call */
+    nullptr, /* hasInstance */
+    nullptr, /* construct */
+    nullptr, /* trace */
+    JS_NULL_CLASS_SPEC,
+    JS_NULL_CLASS_EXT,
+    JS_NULL_OBJECT_OPS
+};
+
+static const js::Class NurseryClass = {
+    "NurseryClass",
+    JSCLASS_FINALIZE_FROM_NURSERY | JSCLASS_HAS_RESERVED_SLOTS(1),
+    nullptr, /* addProperty */
+    nullptr, /* delProperty */
+    nullptr, /* getProperty */
+    nullptr, /* setProperty */
+    nullptr, /* enumerate */
+    nullptr, /* resolve */
+    nullptr, /* ??? */
+    _finalize,
+    nullptr, /* call */
+    nullptr, /* hasInstance */
+    nullptr, /* construct */
+    nullptr, /* trace */
+    JS_NULL_CLASS_SPEC,
+    JS_NULL_CLASS_EXT,
+    JS_NULL_OBJECT_OPS
+};
+
+BEGIN_TEST(testGCNurseryFinalizer)
+{
+    JS::RootedObject obj(cx);
+
+    obj = JS_NewObject(cx, Jsvalify(&TenuredClass), JS::NullPtr(), JS::NullPtr());
+    CHECK(!js::gc::IsInsideNursery(obj));
+
+    // Null finalization list with empty nursery.
+    rt->gc.minorGC(JS::gcreason::EVICT_NURSERY);
+    CHECK(ranFinalizer == 0);
+
+    // Null finalization list with non-empty nursery.
+    obj = JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr());
+    obj = JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr());
+    obj = JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr());
+    CHECK(js::gc::IsInsideNursery(obj));
+    obj = nullptr;
+    rt->gc.minorGC(JS::gcreason::EVICT_NURSERY);
+    CHECK(ranFinalizer == 0);
+
+    // Single finalizable nursery thing.
+    obj = JS_NewObject(cx, Jsvalify(&NurseryClass), JS::NullPtr(), JS::NullPtr());
+    CHECK(js::gc::IsInsideNursery(obj));
+    obj = nullptr;
+    rt->gc.minorGC(JS::gcreason::EVICT_NURSERY);
+    CHECK(ranFinalizer == 1);
+    ranFinalizer = 0;
+
+    // Multiple finalizable nursery things.
+    obj = JS_NewObject(cx, Jsvalify(&NurseryClass), JS::NullPtr(), JS::NullPtr());
+    obj = JS_NewObject(cx, Jsvalify(&NurseryClass), JS::NullPtr(), JS::NullPtr());
+    obj = JS_NewObject(cx, Jsvalify(&NurseryClass), JS::NullPtr(), JS::NullPtr());
+    CHECK(js::gc::IsInsideNursery(obj));
+    obj = nullptr;
+    rt->gc.minorGC(JS::gcreason::EVICT_NURSERY);
+    CHECK(ranFinalizer == 3);
+    ranFinalizer = 0;
+
+    // Interleaved finalizable things in nursery.
+    obj = JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr());
+    obj = JS_NewObject(cx, Jsvalify(&NurseryClass), JS::NullPtr(), JS::NullPtr());
+    obj = JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr());
+    obj = JS_NewObject(cx, Jsvalify(&NurseryClass), JS::NullPtr(), JS::NullPtr());
+    obj = JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr());
+    obj = JS_NewObject(cx, Jsvalify(&NurseryClass), JS::NullPtr(), JS::NullPtr());
+    obj = JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr());
+    obj = JS_NewObject(cx, Jsvalify(&NurseryClass), JS::NullPtr(), JS::NullPtr());
+    obj = JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr());
+    CHECK(js::gc::IsInsideNursery(obj));
+    obj = nullptr;
+    rt->gc.minorGC(JS::gcreason::EVICT_NURSERY);
+    CHECK(ranFinalizer == 4);
+    ranFinalizer = 0;
+
+    return true;
+}
+END_TEST(testGCNurseryFinalizer)
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -7016,17 +7016,17 @@ JS::AutoAssertNoAlloc::~AutoAssertNoAllo
     if (gc)
         gc->allowAlloc();
 }
 #endif
 
 JS::AutoAssertGCCallback::AutoAssertGCCallback(JSObject *obj)
   : AutoSuppressGCAnalysis()
 {
-    MOZ_ASSERT(obj->runtimeFromMainThread()->isHeapMajorCollecting());
+    MOZ_ASSERT(obj->runtimeFromMainThread()->isHeapCollecting());
 }
 
 JS_FRIEND_API(const char *)
 JS::GCTraceKindToAscii(JSGCTraceKind kind)
 {
     switch(kind) {
       case JSTRACE_OBJECT: return "Object";
       case JSTRACE_SCRIPT: return "Script";
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -57,19 +57,19 @@ GetGCObjectKind(const Class *clasp)
         return JSFunction::FinalizeKind;
     uint32_t nslots = JSCLASS_RESERVED_SLOTS(clasp);
     if (clasp->flags & JSCLASS_HAS_PRIVATE)
         nslots++;
     return GetGCObjectKind(nslots);
 }
 
 inline bool
-ShouldNurseryAllocate(const Nursery &nursery, AllocKind kind, InitialHeap heap)
+ShouldNurseryAllocateObject(const Nursery &nursery, InitialHeap heap)
 {
-    return nursery.isEnabled() && IsNurseryAllocable(kind) && heap != TenuredHeap;
+    return nursery.isEnabled() && heap != TenuredHeap;
 }
 
 inline JSGCTraceKind
 GetGCThingTraceKind(const void *thing)
 {
     MOZ_ASSERT(thing);
     const Cell *cell = static_cast<const Cell *>(thing);
     if (IsInsideNursery(cell))
@@ -415,30 +415,30 @@ class GCZoneGroupIter {
 typedef CompartmentsIterT<GCZoneGroupIter> GCCompartmentGroupIter;
 
 /*
  * 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.
  */
 template <AllowGC allowGC>
 inline JSObject *
-TryNewNurseryObject(JSContext *cx, size_t thingSize, size_t nDynamicSlots)
+TryNewNurseryObject(JSContext *cx, size_t thingSize, size_t nDynamicSlots, const js::Class *clasp)
 {
     MOZ_ASSERT(!IsAtomsCompartment(cx->compartment()));
     JSRuntime *rt = cx->runtime();
     Nursery &nursery = rt->gc.nursery;
-    JSObject *obj = nursery.allocateObject(cx, thingSize, nDynamicSlots);
+    JSObject *obj = nursery.allocateObject(cx, thingSize, nDynamicSlots, clasp);
     if (obj)
         return obj;
     if (allowGC && !rt->mainThread.suppressGC) {
         cx->minorGC(JS::gcreason::OUT_OF_NURSERY);
 
         /* Exceeding gcMaxBytes while tenuring can disable the Nursery. */
         if (nursery.isEnabled()) {
-            JSObject *obj = nursery.allocateObject(cx, thingSize, nDynamicSlots);
+            JSObject *obj = nursery.allocateObject(cx, thingSize, nDynamicSlots, clasp);
             MOZ_ASSERT(obj);
             return obj;
         }
     }
     return nullptr;
 }
 
 static inline bool
@@ -511,31 +511,34 @@ CheckIncrementalZoneState(ThreadSafeCont
  * Allocate a new GC thing. After a successful allocation the caller must
  * fully initialize the thing before calling any function that can potentially
  * trigger GC. This will ensure that GC tracing never sees junk values stored
  * in the partially initialized thing.
  */
 
 template <AllowGC allowGC>
 inline JSObject *
-AllocateObject(ThreadSafeContext *cx, AllocKind kind, size_t nDynamicSlots, InitialHeap heap)
+AllocateObject(ThreadSafeContext *cx, AllocKind kind, size_t nDynamicSlots, InitialHeap heap,
+               const js::Class *clasp)
 {
     size_t thingSize = Arena::thingSize(kind);
 
     MOZ_ASSERT(thingSize == Arena::thingSize(kind));
     MOZ_ASSERT(thingSize >= sizeof(JSObject));
     static_assert(sizeof(JSObject) >= CellSize,
                   "All allocations must be at least the allocator-imposed minimum size.");
 
     if (!CheckAllocatorState<allowGC>(cx, kind))
         return nullptr;
 
     if (cx->isJSContext() &&
-        ShouldNurseryAllocate(cx->asJSContext()->nursery(), kind, heap)) {
-        JSObject *obj = TryNewNurseryObject<allowGC>(cx->asJSContext(), thingSize, nDynamicSlots);
+        ShouldNurseryAllocateObject(cx->asJSContext()->nursery(), heap))
+    {
+        JSObject *obj = TryNewNurseryObject<allowGC>(cx->asJSContext(), thingSize, nDynamicSlots,
+                                                     clasp);
         if (obj)
             return obj;
     }
 
     HeapSlot *slots = nullptr;
     if (nDynamicSlots) {
         if (cx->isExclusiveContext())
             slots = cx->asExclusiveContext()->zone()->pod_malloc<HeapSlot>(nDynamicSlots);
@@ -588,34 +591,34 @@ AllocateNonObject(ThreadSafeContext *cx)
  * When allocating for initialization from a cached object copy, we will
  * potentially destroy the cache entry we want to copy if we allow GC. On the
  * other hand, since these allocations are extremely common, we don't want to
  * delay GC from these allocation sites. Instead we allow the GC, but still
  * fail the allocation, forcing the non-cached path.
  */
 template <AllowGC allowGC>
 inline JSObject *
-AllocateObjectForCacheHit(JSContext *cx, AllocKind kind, InitialHeap heap)
+AllocateObjectForCacheHit(JSContext *cx, AllocKind kind, InitialHeap heap, const js::Class *clasp)
 {
-    if (ShouldNurseryAllocate(cx->nursery(), kind, heap)) {
+    if (ShouldNurseryAllocateObject(cx->nursery(), heap)) {
         size_t thingSize = Arena::thingSize(kind);
 
         MOZ_ASSERT(thingSize == Arena::thingSize(kind));
         if (!CheckAllocatorState<NoGC>(cx, kind))
             return nullptr;
 
-        JSObject *obj = TryNewNurseryObject<NoGC>(cx, thingSize, 0);
+        JSObject *obj = TryNewNurseryObject<NoGC>(cx, thingSize, 0, clasp);
         if (!obj && allowGC) {
             cx->minorGC(JS::gcreason::OUT_OF_NURSERY);
             return nullptr;
         }
         return obj;
     }
 
-    JSObject *obj = AllocateObject<NoGC>(cx, kind, 0, heap);
+    JSObject *obj = AllocateObject<NoGC>(cx, kind, 0, heap, clasp);
     if (!obj && allowGC) {
         cx->runtime()->gc.maybeGC(cx->zone());
         return nullptr;
     }
 
     return obj;
 }
 
@@ -631,20 +634,21 @@ IsInsideGGCNursery(const js::gc::Cell *c
     MOZ_ASSERT(location != 0);
     return location & js::gc::ChunkLocationBitNursery;
 }
 
 } /* namespace gc */
 
 template <js::AllowGC allowGC>
 inline JSObject *
-NewGCObject(js::ThreadSafeContext *cx, js::gc::AllocKind kind, size_t nDynamicSlots, js::gc::InitialHeap heap)
+NewGCObject(js::ThreadSafeContext *cx, js::gc::AllocKind kind, size_t nDynamicSlots,
+            js::gc::InitialHeap heap, const js::Class *clasp)
 {
     MOZ_ASSERT(kind >= js::gc::FINALIZE_OBJECT0 && kind <= js::gc::FINALIZE_OBJECT_LAST);
-    return js::gc::AllocateObject<allowGC>(cx, kind, nDynamicSlots, heap);
+    return js::gc::AllocateObject<allowGC>(cx, kind, nDynamicSlots, heap, clasp);
 }
 
 template <js::AllowGC allowGC>
 inline jit::JitCode *
 NewJitCode(js::ThreadSafeContext *cx)
 {
     return gc::AllocateNonObject<jit::JitCode, allowGC>(cx);
 }
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1001,17 +1001,19 @@ enum NewObjectKind {
      * with this kind to place them immediately into the tenured generation.
      */
     TenuredObject
 };
 
 inline gc::InitialHeap
 GetInitialHeap(NewObjectKind newKind, const Class *clasp)
 {
-    if (clasp->finalize || newKind != GenericObject)
+    if (newKind != GenericObject)
+        return gc::TenuredHeap;
+    if (clasp->finalize && !(clasp->flags & JSCLASS_FINALIZE_FROM_NURSERY))
         return gc::TenuredHeap;
     return gc::DefaultHeap;
 }
 
 // Specialized call for constructing |this| with a known function callee,
 // and a known prototype.
 extern PlainObject *
 CreateThisForFunctionWithProto(JSContext *cx, js::HandleObject callee, JSObject *proto,
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -303,31 +303,32 @@ JSObject::create(js::ExclusiveContext *c
                  js::HandleShape shape, js::HandleTypeObject type)
 {
     MOZ_ASSERT(shape && type);
     MOZ_ASSERT(type->clasp() == shape->getObjectClass());
     MOZ_ASSERT(type->clasp() != &js::ArrayObject::class_);
     MOZ_ASSERT_IF(!js::ClassCanHaveFixedData(type->clasp()),
                   js::gc::GetGCKindSlots(kind, type->clasp()) == shape->numFixedSlots());
     MOZ_ASSERT_IF(type->clasp()->flags & JSCLASS_BACKGROUND_FINALIZE, IsBackgroundFinalized(kind));
-    MOZ_ASSERT_IF(type->clasp()->finalize, heap == js::gc::TenuredHeap);
+    MOZ_ASSERT_IF(type->clasp()->finalize, heap == js::gc::TenuredHeap ||
+                                           (type->clasp()->flags & JSCLASS_FINALIZE_FROM_NURSERY));
 
     // Non-native classes cannot have reserved slots or private data, and the
     // objects can't have any fixed slots, for compatibility with
     // GetReservedOrProxyPrivateSlot.
     MOZ_ASSERT_IF(!type->clasp()->isNative(), JSCLASS_RESERVED_SLOTS(type->clasp()) == 0);
     MOZ_ASSERT_IF(!type->clasp()->isNative(), !type->clasp()->hasPrivate());
     MOZ_ASSERT_IF(!type->clasp()->isNative(), shape->numFixedSlots() == 0);
     MOZ_ASSERT_IF(!type->clasp()->isNative(), shape->slotSpan() == 0);
 
     const js::Class *clasp = type->clasp();
     size_t nDynamicSlots =
         js::NativeObject::dynamicSlotsCount(shape->numFixedSlots(), shape->slotSpan(), clasp);
 
-    JSObject *obj = js::NewGCObject<js::CanGC>(cx, kind, nDynamicSlots, heap);
+    JSObject *obj = js::NewGCObject<js::CanGC>(cx, kind, nDynamicSlots, heap, clasp);
     if (!obj)
         return nullptr;
 
     obj->shape_.init(shape);
     obj->type_.init(type);
 
     // Note: slots are created and assigned internally by NewGCObject.
     obj->setInitialElementsMaybeNonNative(js::emptyObjectElements);
--- a/js/src/vm/ArrayObject-inl.h
+++ b/js/src/vm/ArrayObject-inl.h
@@ -38,17 +38,17 @@ ArrayObject::createArrayInternal(Exclusi
     MOZ_ASSERT(type->clasp() == &ArrayObject::class_);
     MOZ_ASSERT_IF(type->clasp()->finalize, heap == gc::TenuredHeap);
 
     // Arrays can use their fixed slots to store elements, so can't have shapes
     // which allow named properties to be stored in the fixed slots.
     MOZ_ASSERT(shape->numFixedSlots() == 0);
 
     size_t nDynamicSlots = dynamicSlotsCount(0, shape->slotSpan(), type->clasp());
-    JSObject *obj = NewGCObject<CanGC>(cx, kind, nDynamicSlots, heap);
+    JSObject *obj = NewGCObject<CanGC>(cx, kind, nDynamicSlots, heap, type->clasp());
     if (!obj)
         return nullptr;
 
     static_cast<ArrayObject *>(obj)->shape_.init(shape);
     static_cast<ArrayObject *>(obj)->type_.init(type);
 
     return &obj->as<ArrayObject>();
 }
--- a/js/src/vm/Runtime-inl.h
+++ b/js/src/vm/Runtime-inl.h
@@ -59,23 +59,23 @@ NewObjectCache::newObjectFromHit(JSConte
 
     if (cx->runtime()->gc.upcomingZealousGC())
         return nullptr;
 
     // Trigger an identical allocation to the one that notified us of OOM
     // so that we trigger the right kind of GC automatically.
     if (allowGC) {
         mozilla::DebugOnly<JSObject *> obj =
-            js::gc::AllocateObjectForCacheHit<allowGC>(cx, entry->kind, heap);
+            js::gc::AllocateObjectForCacheHit<allowGC>(cx, entry->kind, heap, type->clasp());
         MOZ_ASSERT(!obj);
         return nullptr;
     }
 
     MOZ_ASSERT(allowGC == NoGC);
-    JSObject *obj = js::gc::AllocateObjectForCacheHit<NoGC>(cx, entry->kind, heap);
+    JSObject *obj = js::gc::AllocateObjectForCacheHit<NoGC>(cx, entry->kind, heap, type->clasp());
     if (obj) {
         copyCachedToObject(obj, templateObj, entry->kind);
         probes::CreateObject(cx, obj);
         js::gc::TraceCreateObject(obj);
         return obj;
     }
 
     return nullptr;