Bug 1339411 - Rewrite and optimize object allocation paths. r=bhackett
authorJan de Mooij <jdemooij@mozilla.com>
Tue, 14 Feb 2017 18:19:12 +0100
changeset 342829 12667d6bc2083c860f26921c3deb9598fcae7b7e
parent 342828 6f43298f5271ebed750b18366ceb044a96814387
child 342830 e719b89ea8f22b00fb6a52fb962090bbe5c7ac6a
push id31363
push userkwierso@gmail.com
push dateTue, 14 Feb 2017 21:12:30 +0000
treeherdermozilla-central@1060668405a9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs1339411
milestone54.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 1339411 - Rewrite and optimize object allocation paths. r=bhackett
js/src/builtin/TypedObject.cpp
js/src/builtin/TypedObject.h
js/src/jsiter.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/vm/ArgumentsObject.cpp
js/src/vm/EnvironmentObject.cpp
js/src/vm/NativeObject-inl.h
js/src/vm/NativeObject.h
js/src/vm/ProxyObject.cpp
js/src/vm/ProxyObject.h
js/src/vm/UnboxedObject.cpp
js/src/vm/UnboxedObject.h
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -2377,16 +2377,45 @@ TypedObject::construct(JSContext* cx, un
         return true;
     }
 
     // Something bogus.
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_BAD_ARGS);
     return false;
 }
 
+/* static */ JS::Result<TypedObject*, JS::OOM&>
+TypedObject::create(JSContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
+                    js::HandleShape shape, js::HandleObjectGroup group)
+{
+    debugCheckNewObject(group, shape, kind, heap);
+
+    const js::Class* clasp = group->clasp();
+    MOZ_ASSERT(::IsTypedObjectClass(clasp));
+
+    JSObject* obj = js::Allocate<JSObject>(cx, kind, /* nDynamicSlots = */ 0, heap, clasp);
+    if (!obj)
+        return cx->alreadyReportedOOM();
+
+    TypedObject* tobj = static_cast<TypedObject*>(obj);
+    tobj->group_.init(group);
+    tobj->initShape(shape);
+
+    tobj->setInitialElementsMaybeNonNative(js::emptyObjectElements);
+
+    if (clasp->shouldDelayMetadataBuilder())
+        cx->compartment()->setObjectPendingMetadata(cx, tobj);
+    else
+        tobj = SetNewObjectMetadata(cx, tobj);
+
+    js::gc::TraceCreateObject(tobj);
+
+    return tobj;
+}
+
 /******************************************************************************
  * Intrinsics
  */
 
 bool
 js::NewOpaqueTypedObject(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -548,16 +548,20 @@ class TypedObject : public ShapedObject
         // Typed objects' prototypes can't be modified.
         return staticPrototype()->as<TypedProto>();
     }
 
     TypeDescr& typeDescr() const {
         return group()->typeDescr();
     }
 
+    static JS::Result<TypedObject*, JS::OOM&>
+    create(JSContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
+           js::HandleShape shape, js::HandleObjectGroup group);
+
     uint32_t offset() const;
     uint32_t length() const;
     uint8_t* typedMem(const JS::AutoRequireNoGC&) const { return typedMem(); }
     bool isAttached() const;
 
     uint32_t size() const {
         return typeDescr().size();
     }
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -547,19 +547,19 @@ NewPropertyIteratorObject(JSContext* cx,
 
         const Class* clasp = &PropertyIteratorObject::class_;
         RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(nullptr),
                                                           ITERATOR_FINALIZE_KIND));
         if (!shape)
             return nullptr;
 
         JSObject* obj;
-        JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, ITERATOR_FINALIZE_KIND,
-                                                            GetInitialHeap(GenericObject, clasp),
-                                                            shape, group));
+        JS_TRY_VAR_OR_RETURN_NULL(cx, obj, NativeObject::create(cx, ITERATOR_FINALIZE_KIND,
+                                                                GetInitialHeap(GenericObject, clasp),
+                                                                shape, group));
 
         PropertyIteratorObject* res = &obj->as<PropertyIteratorObject>();
 
         MOZ_ASSERT(res->numFixedSlots() == JSObject::ITER_CLASS_NFIXED_SLOTS);
         return res;
     }
 
     Rooted<PropertyIteratorObject*> res(cx, NewBuiltinClassInstance<PropertyIteratorObject>(cx));
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -641,18 +641,24 @@ NewObject(JSContext* cx, HandleObjectGro
                     : GetGCKindSlots(kind, clasp);
 
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, group->proto(), nfixed,
                                                       initialShapeFlags));
     if (!shape)
         return nullptr;
 
     gc::InitialHeap heap = GetInitialHeap(newKind, clasp);
+
     JSObject* obj;
-    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, kind, heap, shape, group));
+    if (MOZ_LIKELY(clasp->isNative())) {
+        JS_TRY_VAR_OR_RETURN_NULL(cx, obj, NativeObject::create(cx, kind, heap, shape, group));
+    } else {
+        MOZ_ASSERT(IsTypedObjectClass(clasp));
+        JS_TRY_VAR_OR_RETURN_NULL(cx, obj, TypedObject::create(cx, kind, heap, shape, group));
+    }
 
     if (newKind == SingletonObject) {
         RootedObject nobj(cx, obj);
         if (!JSObject::setSingleton(cx, nobj))
             return nullptr;
         obj = nobj;
     }
 
@@ -3976,8 +3982,59 @@ js::Unbox(JSContext* cx, HandleObject ob
         vp.setString(obj->as<StringObject>().unbox());
     else if (obj->is<DateObject>())
         vp.set(obj->as<DateObject>().UTCTime());
     else
         vp.setUndefined();
 
     return true;
 }
+
+#ifdef DEBUG
+/* static */ void
+JSObject::debugCheckNewObject(ObjectGroup* group, Shape* shape, js::gc::AllocKind allocKind,
+                              js::gc::InitialHeap heap)
+{
+    const js::Class* clasp = group->clasp();
+    MOZ_ASSERT(clasp != &ArrayObject::class_);
+
+    if (shape)
+        MOZ_ASSERT(clasp == shape->getObjectClass());
+    else
+        MOZ_ASSERT(clasp == &UnboxedPlainObject::class_ || clasp == &UnboxedArrayObject::class_);
+
+    if (!ClassCanHaveFixedData(clasp)) {
+        MOZ_ASSERT(shape);
+        MOZ_ASSERT(gc::GetGCKindSlots(allocKind, clasp) == shape->numFixedSlots());
+    }
+
+    // Classes with a finalizer must specify whether instances will be finalized
+    // on the active thread or in the background, except proxies whose behaviour
+    // depends on the target object.
+    static const uint32_t FinalizeMask = JSCLASS_FOREGROUND_FINALIZE | JSCLASS_BACKGROUND_FINALIZE;
+    uint32_t flags = clasp->flags;
+    uint32_t finalizeFlags = flags & FinalizeMask;
+    if (clasp->hasFinalize() && !clasp->isProxy()) {
+        MOZ_ASSERT(finalizeFlags == JSCLASS_FOREGROUND_FINALIZE ||
+                   finalizeFlags == JSCLASS_BACKGROUND_FINALIZE);
+        MOZ_ASSERT((finalizeFlags == JSCLASS_BACKGROUND_FINALIZE) == IsBackgroundFinalized(allocKind));
+    } else {
+        MOZ_ASSERT(finalizeFlags == 0);
+    }
+
+    MOZ_ASSERT_IF(clasp->hasFinalize(), heap == gc::TenuredHeap ||
+                                        CanNurseryAllocateFinalizedClass(clasp) ||
+                                        clasp->isProxy());
+    MOZ_ASSERT_IF(group->hasUnanalyzedPreliminaryObjects(), heap == gc::TenuredHeap);
+
+    MOZ_ASSERT(!group->compartment()->hasObjectPendingMetadata());
+
+    // Non-native classes cannot have reserved slots or private data, and the
+    // objects can't have any fixed slots, for compatibility with
+    // GetReservedOrProxyPrivateSlot.
+    if (!clasp->isNative()) {
+        MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(clasp) == 0);
+        MOZ_ASSERT(!clasp->hasPrivate());
+        MOZ_ASSERT_IF(shape, shape->numFixedSlots() == 0);
+        MOZ_ASSERT_IF(shape, shape->slotSpan() == 0);
+    }
+}
+#endif
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -173,24 +173,16 @@ class JSObject : public js::gc::Cell
     }
 
     JSCompartment* compartment() const { return group_->compartment(); }
     JSCompartment* maybeCompartment() const { return compartment(); }
 
     inline js::Shape* maybeShape() const;
     inline js::Shape* ensureShape(JSContext* cx);
 
-    /*
-     * Make a non-array object with the specified initial state. This method
-     * takes ownership of any extantSlots it is passed.
-     */
-    static inline JS::Result<JSObject*, JS::OOM&>
-    create(JSContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
-           js::HandleShape shape, js::HandleObjectGroup group);
-
     // Set the initial slots and elements of an object. These pointers are only
     // valid for native objects, but during initialization are set for all
     // objects. For non-native objects, these must not be dynamically allocated
     // pointers which leak when the non-native object finishes initialization.
     inline void setInitialSlotsMaybeNonNative(js::HeapSlot* slots);
     inline void setInitialElementsMaybeNonNative(js::HeapSlot* elements);
 
     enum GenerateShape {
@@ -348,16 +340,25 @@ class JSObject : public js::gc::Cell
 
     static inline js::ObjectGroup* getGroup(JSContext* cx, js::HandleObject obj);
 
     const js::GCPtrObjectGroup& groupFromGC() const {
         /* Direct field access for use by GC. */
         return group_;
     }
 
+#ifdef DEBUG
+    static void debugCheckNewObject(js::ObjectGroup* group, js::Shape* shape,
+                                    js::gc::AllocKind allocKind, js::gc::InitialHeap heap);
+#else
+    static void debugCheckNewObject(js::ObjectGroup* group, js::Shape* shape,
+                                    js::gc::AllocKind allocKind, js::gc::InitialHeap heap)
+    {}
+#endif
+
     /*
      * We permit proxies to dynamically compute their prototype if desired.
      * (Not all proxies will so desire: in particular, most DOM proxies can
      * track their prototype with a single, nullable JSObject*.)  If a proxy
      * so desires, we store (JSObject*)0x1 in the proto field of the object's
      * group.
      *
      * We offer three ways to get an object's prototype:
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -289,141 +289,42 @@ ClassCanHaveFixedData(const Class* clasp
 // If we do have an allocation metadata builder, it can cause a GC, so the object
 // must be rooted. The usual way to do this would be to make our callers pass a
 // HandleObject, but that would require them to pay the cost of rooting the
 // object unconditionally, even though collecting metadata is rare. Instead,
 // SetNewObjectMetadata's contract is that the caller must use the pointer
 // returned in place of the pointer passed. If a GC occurs, the returned pointer
 // may be the passed pointer, relocated by GC. If no GC could occur, it's just
 // passed through. We root nothing unless necessary.
-static MOZ_ALWAYS_INLINE MOZ_MUST_USE JSObject*
-SetNewObjectMetadata(JSContext* cx, JSObject* obj)
+template <typename T>
+static MOZ_ALWAYS_INLINE MOZ_MUST_USE T*
+SetNewObjectMetadata(JSContext* cx, T* obj)
 {
     MOZ_ASSERT(!cx->compartment()->hasObjectPendingMetadata());
 
     // The metadata builder is invoked for each object created on the active
     // thread, except when analysis/compilation is active, to avoid recursion.
     if (!cx->helperThread()) {
         if (MOZ_UNLIKELY((size_t)cx->compartment()->hasAllocationMetadataBuilder()) &&
             !cx->zone()->suppressAllocationMetadataBuilder)
         {
             // Don't collect metadata on objects that represent metadata.
             AutoSuppressAllocationMetadataBuilder suppressMetadata(cx);
 
-            RootedObject rooted(cx, obj);
+            Rooted<T*> rooted(cx, obj);
             cx->compartment()->setNewObjectMetadata(cx, rooted);
             return rooted;
         }
     }
 
     return obj;
 }
 
 } // namespace js
 
-/* static */ inline JS::Result<JSObject*, JS::OOM&>
-JSObject::create(JSContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
-                 js::HandleShape shape, js::HandleObjectGroup group)
-{
-    const js::Class* clasp = group->clasp();
-
-    MOZ_ASSERT(shape && group);
-    MOZ_ASSERT(clasp == shape->getObjectClass());
-    MOZ_ASSERT(clasp != &js::ArrayObject::class_);
-    MOZ_ASSERT_IF(!js::ClassCanHaveFixedData(clasp),
-                  js::gc::GetGCKindSlots(kind, clasp) == shape->numFixedSlots());
-
-#ifdef DEBUG
-    static const uint32_t FinalizeMask = JSCLASS_FOREGROUND_FINALIZE | JSCLASS_BACKGROUND_FINALIZE;
-    uint32_t flags = clasp->flags;
-    uint32_t finalizeFlags = flags & FinalizeMask;
-
-    // Classes with a finalizer must specify whether instances will be finalized
-    // on the active thread or in the background, except proxies whose behaviour
-    // depends on the target object.
-    if (clasp->hasFinalize() && !clasp->isProxy()) {
-        MOZ_ASSERT(finalizeFlags == JSCLASS_FOREGROUND_FINALIZE ||
-                   finalizeFlags == JSCLASS_BACKGROUND_FINALIZE);
-        MOZ_ASSERT((finalizeFlags == JSCLASS_BACKGROUND_FINALIZE) == IsBackgroundFinalized(kind));
-    } else {
-        MOZ_ASSERT(finalizeFlags == 0);
-    }
-
-    MOZ_ASSERT_IF(clasp->hasFinalize(), heap == js::gc::TenuredHeap ||
-                                        CanNurseryAllocateFinalizedClass(clasp) ||
-                                        clasp->isProxy());
-    MOZ_ASSERT_IF(group->hasUnanalyzedPreliminaryObjects(), heap == js::gc::TenuredHeap);
-#endif
-
-    MOZ_ASSERT(!cx->compartment()->hasObjectPendingMetadata());
-
-    // 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(!clasp->isNative(), JSCLASS_RESERVED_SLOTS(clasp) == 0);
-    MOZ_ASSERT_IF(!clasp->isNative(), !clasp->hasPrivate());
-    MOZ_ASSERT_IF(!clasp->isNative(), shape->numFixedSlots() == 0);
-    MOZ_ASSERT_IF(!clasp->isNative(), shape->slotSpan() == 0);
-
-    size_t nDynamicSlots = 0;
-    if (clasp->isNative()) {
-        nDynamicSlots = js::NativeObject::dynamicSlotsCount(shape->numFixedSlots(),
-                                                            shape->slotSpan(), clasp);
-    } else if (clasp->isProxy()) {
-        // Proxy objects overlay the |slots| field with a ProxyValueArray.
-        MOZ_ASSERT(sizeof(js::detail::ProxyValueArray) % sizeof(js::HeapSlot) == 0);
-        nDynamicSlots = sizeof(js::detail::ProxyValueArray) / sizeof(js::HeapSlot);
-    }
-
-    JSObject* obj = js::Allocate<JSObject>(cx, kind, nDynamicSlots, heap, clasp);
-    if (!obj)
-        return cx->alreadyReportedOOM();
-
-    obj->group_.init(group);
-
-    // This function allocates normal objects and proxies and typed objects
-    // (all with shapes), *and* it allocates objects without shapes (various
-    // unboxed object classes).  Setting shape is naturally only valid for the
-    // former class of objects.
-    if (obj->is<js::ShapedObject>())
-        obj->as<js::ShapedObject>().initShape(shape);
-
-    // Note: slots are created and assigned internally by Allocate<JSObject>.
-    obj->setInitialElementsMaybeNonNative(js::emptyObjectElements);
-
-    if (clasp->hasPrivate())
-        obj->as<js::NativeObject>().privateRef(shape->numFixedSlots()) = nullptr;
-
-    if (size_t span = shape->slotSpan())
-        obj->as<js::NativeObject>().initializeSlotRange(0, span);
-
-    // JSFunction's fixed slots expect POD-style initialization.
-    if (clasp->isJSFunction()) {
-        MOZ_ASSERT(kind == js::gc::AllocKind::FUNCTION ||
-                   kind == js::gc::AllocKind::FUNCTION_EXTENDED);
-        size_t size =
-            kind == js::gc::AllocKind::FUNCTION ? sizeof(JSFunction) : sizeof(js::FunctionExtended);
-        memset(obj->as<JSFunction>().fixedSlots(), 0, size - sizeof(js::NativeObject));
-        if (kind == js::gc::AllocKind::FUNCTION_EXTENDED) {
-            // SetNewObjectMetadata may gc, which will be unhappy if flags &
-            // EXTENDED doesn't match the arena's AllocKind.
-            obj->as<JSFunction>().setFlags(JSFunction::EXTENDED);
-        }
-    }
-
-    if (clasp->shouldDelayMetadataBuilder())
-        cx->compartment()->setObjectPendingMetadata(cx, obj);
-    else
-        obj = SetNewObjectMetadata(cx, obj);
-
-    js::gc::TraceCreateObject(obj);
-
-    return obj;
-}
-
 inline void
 JSObject::setInitialSlotsMaybeNonNative(js::HeapSlot* slots)
 {
     static_cast<js::NativeObject*>(this)->slots_ = slots;
 }
 
 inline void
 JSObject::setInitialElementsMaybeNonNative(js::HeapSlot* elements)
--- a/js/src/vm/ArgumentsObject.cpp
+++ b/js/src/vm/ArgumentsObject.cpp
@@ -224,17 +224,18 @@ ArgumentsObject::createTemplateObject(JS
 
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, TaggedProto(proto),
                                                       FINALIZE_KIND, BaseShape::INDEXED));
     if (!shape)
         return nullptr;
 
     AutoSetNewObjectMetadata metadata(cx);
     JSObject* base;
-    JS_TRY_VAR_OR_RETURN_NULL(cx, base, JSObject::create(cx, FINALIZE_KIND, gc::TenuredHeap, shape, group));
+    JS_TRY_VAR_OR_RETURN_NULL(cx, base, NativeObject::create(cx, FINALIZE_KIND, gc::TenuredHeap,
+                                                             shape, group));
 
     ArgumentsObject* obj = &base->as<js::ArgumentsObject>();
     obj->initFixedSlot(ArgumentsObject::DATA_SLOT, PrivateValue(nullptr));
     return obj;
 }
 
 ArgumentsObject*
 JSCompartment::maybeArgumentsTemplateObject(bool mapped) const
@@ -279,17 +280,18 @@ ArgumentsObject::create(JSContext* cx, H
     Rooted<ArgumentsObject*> obj(cx);
     ArgumentsData* data = nullptr;
     {
         // The copyArgs call below can allocate objects, so add this block scope
         // to make sure we set the metadata for this arguments object first.
         AutoSetNewObjectMetadata metadata(cx);
 
         JSObject* base;
-        JS_TRY_VAR_OR_RETURN_NULL(cx, base, JSObject::create(cx, FINALIZE_KIND, gc::DefaultHeap, shape, group));
+        JS_TRY_VAR_OR_RETURN_NULL(cx, base, NativeObject::create(cx, FINALIZE_KIND,
+                                                                 gc::DefaultHeap, shape, group));
         obj = &base->as<ArgumentsObject>();
 
         data =
             reinterpret_cast<ArgumentsData*>(AllocateObjectBuffer<uint8_t>(cx, obj, numBytes));
         if (!data) {
             // Make the object safe for GC.
             obj->initFixedSlot(DATA_SLOT, PrivateValue(nullptr));
             return nullptr;
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -139,34 +139,34 @@ CallObject::create(JSContext* cx, Handle
                "passed a singleton group to create() (use createSingleton() "
                "instead)");
 
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
     JSObject* obj;
-    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, kind, gc::DefaultHeap, shape, group));
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, NativeObject::create(cx, kind, gc::DefaultHeap, shape, group));
 
     return &obj->as<CallObject>();
 }
 
 CallObject*
 CallObject::createSingleton(JSContext* cx, HandleShape shape)
 {
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
     RootedObjectGroup group(cx, ObjectGroup::lazySingletonGroup(cx, &class_, TaggedProto(nullptr)));
     if (!group)
         return nullptr;
 
     JSObject* obj;
-    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, kind, gc::TenuredHeap, shape, group));
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, NativeObject::create(cx, kind, gc::TenuredHeap, shape, group));
 
     MOZ_ASSERT(obj->isSingleton(),
                "group created inline above must be a singleton");
 
     return &obj->as<CallObject>();
 }
 
 /*
@@ -186,17 +186,17 @@ CallObject::createTemplateObject(JSConte
     if (!group)
         return nullptr;
 
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
     JSObject* obj;
-    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, kind, heap, shape, group));
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, NativeObject::create(cx, kind, heap, shape, group));
 
     CallObject* callObj = &obj->as<CallObject>();
     callObj->initEnclosingEnvironment(enclosing);
 
     if (scope->hasParameterExprs()) {
         // If there are parameter expressions, all parameters are lexical and
         // have TDZ.
         for (BindingIter bi(script->bodyScope()); bi; bi++) {
@@ -317,17 +317,17 @@ VarEnvironmentObject::create(JSContext* 
     if (!group)
         return nullptr;
 
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
     JSObject* obj;
-    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, kind, heap, shape, group));
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, NativeObject::create(cx, kind, heap, shape, group));
 
     VarEnvironmentObject* env = &obj->as<VarEnvironmentObject>();
     MOZ_ASSERT(!env->inDictionaryMode());
     MOZ_ASSERT(env->isDelegate());
 
     env->initEnclosingEnvironment(enclosing);
 
     return env;
@@ -433,17 +433,17 @@ ModuleEnvironmentObject::create(JSContex
     if (!group)
         return nullptr;
 
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
     JSObject* obj;
-    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, kind, TenuredHeap, shape, group));
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, NativeObject::create(cx, kind, TenuredHeap, shape, group));
 
     RootedModuleEnvironmentObject env(cx, &obj->as<ModuleEnvironmentObject>());
 
     env->initReservedSlot(MODULE_SLOT, ObjectValue(*module));
     if (!JSObject::setSingleton(cx, env))
         return nullptr;
 
     // Initialize this early so that we can manipulate the env object without
@@ -645,17 +645,17 @@ WasmFunctionCallObject::createHollowForD
     if (!shape)
         return nullptr;
 
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
     JSObject* obj;
-    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, kind, gc::DefaultHeap, shape, group));
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, NativeObject::create(cx, kind, gc::DefaultHeap, shape, group));
 
     Rooted<WasmFunctionCallObject*> callobj(cx, &obj->as<WasmFunctionCallObject>());
     callobj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
     callobj->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope));
 
     return callobj;
 }
 
@@ -871,17 +871,17 @@ LexicalEnvironmentObject::createTemplate
     if (!group)
         return nullptr;
 
     gc::AllocKind allocKind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(allocKind, &LexicalEnvironmentObject::class_));
     allocKind = GetBackgroundAllocKind(allocKind);
 
     JSObject* obj;
-    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, JSObject::create(cx, allocKind, heap, shape, group));
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, NativeObject::create(cx, allocKind, heap, shape, group));
 
     LexicalEnvironmentObject* env = &obj->as<LexicalEnvironmentObject>();
     MOZ_ASSERT(!env->inDictionaryMode());
     MOZ_ASSERT(env->isDelegate());
 
     if (enclosing)
         env->initEnclosingEnvironment(enclosing);
 
--- a/js/src/vm/NativeObject-inl.h
+++ b/js/src/vm/NativeObject-inl.h
@@ -399,16 +399,68 @@ NativeObject::updateShapeAfterMovingGC()
 inline bool
 NativeObject::isInWholeCellBuffer() const
 {
     const gc::TenuredCell* cell = &asTenured();
     gc::ArenaCellSet* cells = cell->arena()->bufferedCells();
     return cells && cells->hasCell(cell);
 }
 
+/* static */ inline JS::Result<NativeObject*, JS::OOM&>
+NativeObject::create(JSContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
+                     js::HandleShape shape, js::HandleObjectGroup group)
+{
+    debugCheckNewObject(group, shape, kind, heap);
+
+    const js::Class* clasp = group->clasp();
+    MOZ_ASSERT(clasp->isNative());
+
+    size_t nDynamicSlots = dynamicSlotsCount(shape->numFixedSlots(), shape->slotSpan(), clasp);
+
+    JSObject* obj = js::Allocate<JSObject>(cx, kind, nDynamicSlots, heap, clasp);
+    if (!obj)
+        return cx->alreadyReportedOOM();
+
+    NativeObject* nobj = static_cast<NativeObject*>(obj);
+    nobj->group_.init(group);
+    nobj->initShape(shape);
+
+    // Note: slots are created and assigned internally by Allocate<JSObject>.
+    nobj->setInitialElementsMaybeNonNative(js::emptyObjectElements);
+
+    if (clasp->hasPrivate())
+        nobj->privateRef(shape->numFixedSlots()) = nullptr;
+
+    if (size_t span = shape->slotSpan())
+        nobj->initializeSlotRange(0, span);
+
+    // JSFunction's fixed slots expect POD-style initialization.
+    if (clasp->isJSFunction()) {
+        MOZ_ASSERT(kind == js::gc::AllocKind::FUNCTION ||
+                   kind == js::gc::AllocKind::FUNCTION_EXTENDED);
+        size_t size =
+            kind == js::gc::AllocKind::FUNCTION ? sizeof(JSFunction) : sizeof(js::FunctionExtended);
+        memset(nobj->as<JSFunction>().fixedSlots(), 0, size - sizeof(js::NativeObject));
+        if (kind == js::gc::AllocKind::FUNCTION_EXTENDED) {
+            // SetNewObjectMetadata may gc, which will be unhappy if flags &
+            // EXTENDED doesn't match the arena's AllocKind.
+            nobj->as<JSFunction>().setFlags(JSFunction::EXTENDED);
+        }
+    }
+
+    if (clasp->shouldDelayMetadataBuilder())
+        cx->compartment()->setObjectPendingMetadata(cx, nobj);
+    else
+        nobj = SetNewObjectMetadata(cx, nobj);
+
+    js::gc::TraceCreateObject(nobj);
+
+    return nobj;
+}
+
 /* Make an object with pregenerated shape from a NEWOBJECT bytecode. */
 static inline PlainObject*
 CopyInitializerObject(JSContext* cx, HandlePlainObject baseobj, NewObjectKind newKind = GenericObject)
 {
     MOZ_ASSERT(!baseobj->inDictionaryMode());
 
     gc::AllocKind allocKind = gc::GetGCObjectFixedSlotsKind(baseobj->numFixedSlots());
     allocKind = gc::GetBackgroundAllocKind(allocKind);
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -472,16 +472,20 @@ class NativeObject : public ShapedObject
     // ObjectElements::SHARED_MEMORY flag set.
     void setIsSharedMemory() {
         MOZ_ASSERT(elements_ == emptyObjectElements);
         elements_ = emptyObjectElementsShared;
     }
 
     inline bool isInWholeCellBuffer() const;
 
+    static inline JS::Result<NativeObject*, JS::OOM&>
+    create(JSContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
+           js::HandleShape shape, js::HandleObjectGroup group);
+
   protected:
 #ifdef DEBUG
     void checkShapeConsistency();
 #else
     void checkShapeConsistency() { }
 #endif
 
     static Shape*
--- a/js/src/vm/ProxyObject.cpp
+++ b/js/src/vm/ProxyObject.cpp
@@ -55,22 +55,19 @@ ProxyObject::New(JSContext* cx, const Ba
 
     gc::AllocKind allocKind = gc::GetGCObjectKind(clasp);
     if (handler->finalizeInBackground(priv))
         allocKind = GetBackgroundAllocKind(allocKind);
 
     AutoSetNewObjectMetadata metadata(cx);
     // Note: this will initialize the object's |data| to strange values, but we
     // will immediately overwrite those below.
-    RootedObject obj(cx, NewObjectWithGivenTaggedProto(cx, clasp, proto, allocKind,
-                                                       newKind));
-    if (!obj)
-        return nullptr;
+    ProxyObject* proxy;
+    JS_TRY_VAR_OR_RETURN_NULL(cx, proxy, create(cx, clasp, proto, allocKind, newKind));
 
-    Rooted<ProxyObject*> proxy(cx, &obj->as<ProxyObject>());
     new (proxy->data.values) detail::ProxyValueArray;
     proxy->data.handler = handler;
     proxy->setCrossCompartmentPrivate(priv);
 
     /* Don't track types of properties of non-DOM and non-singleton proxies. */
     if (newKind != SingletonObject && !clasp->isDOMClass())
         MarkObjectGroupUnknownProperties(cx, proxy->group());
 
@@ -126,15 +123,60 @@ ProxyObject::nuke()
     // The proxy's extra slots are not cleared and will continue to be
     // traced. This avoids the possibility of triggering write barriers while
     // nuking proxies in dead compartments which could otherwise cause those
     // compartments to be kept alive. Note that these are slots cannot hold
     // cross compartment pointers, so this cannot cause the target compartment
     // to leak.
 }
 
+/* static */ JS::Result<ProxyObject*, JS::OOM&>
+ProxyObject::create(JSContext* cx, const Class* clasp, Handle<TaggedProto> proto,
+                    gc::AllocKind allocKind, NewObjectKind newKind)
+{
+    MOZ_ASSERT(clasp->isProxy());
+
+    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, clasp, proto, nullptr));
+    if (!group)
+        return cx->alreadyReportedOOM();
+
+    RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, proto, /* nfixed = */ 0));
+    if (!shape)
+        return cx->alreadyReportedOOM();
+
+    gc::InitialHeap heap = GetInitialHeap(newKind, clasp);
+    debugCheckNewObject(group, shape, allocKind, heap);
+
+    // Proxy objects overlay the |slots| field with a ProxyValueArray.
+    static_assert(sizeof(js::detail::ProxyValueArray) % sizeof(js::HeapSlot) == 0,
+                  "ProxyValueArray must be a multiple of HeapSlot");
+    static const size_t NumDynamicSlots = sizeof(js::detail::ProxyValueArray) / sizeof(HeapSlot);
+
+    JSObject* obj = js::Allocate<JSObject>(cx, allocKind, NumDynamicSlots, heap, clasp);
+    if (!obj)
+        return cx->alreadyReportedOOM();
+
+    ProxyObject* pobj = static_cast<ProxyObject*>(obj);
+    pobj->group_.init(group);
+    pobj->initShape(shape);
+
+    MOZ_ASSERT(clasp->shouldDelayMetadataBuilder());
+    cx->compartment()->setObjectPendingMetadata(cx, pobj);
+
+    js::gc::TraceCreateObject(pobj);
+
+    if (newKind == SingletonObject) {
+        Rooted<ProxyObject*> pobjRoot(cx, pobj);
+        if (!JSObject::setSingleton(cx, pobjRoot))
+            return cx->alreadyReportedOOM();
+        pobj = pobjRoot;
+    }
+
+    return pobj;
+}
+
 JS_FRIEND_API(void)
 js::SetValueInProxy(Value* slot, const Value& value)
 {
     // Slots in proxies are not GCPtrValues, so do a cast whenever assigning
     // values to them which might trigger a barrier.
     *reinterpret_cast<GCPtrValue*>(slot) = value;
 }
--- a/js/src/vm/ProxyObject.h
+++ b/js/src/vm/ProxyObject.h
@@ -26,16 +26,20 @@ class ProxyObject : public ShapedObject
 
     void static_asserts() {
         static_assert(sizeof(ProxyObject) == sizeof(JSObject_Slots0),
                       "proxy object size must match GC thing size");
         static_assert(offsetof(ProxyObject, data) == detail::ProxyDataOffset,
                       "proxy object layout must match shadow interface");
     }
 
+    static JS::Result<ProxyObject*, JS::OOM&>
+    create(JSContext* cx, const js::Class* clasp, Handle<TaggedProto> proto,
+           js::gc::AllocKind allocKind, js::NewObjectKind newKind);
+
   public:
     static ProxyObject* New(JSContext* cx, const BaseProxyHandler* handler, HandleValue priv,
                             TaggedProto proto_, const ProxyOptions& options);
 
     const Value& private_() {
         return GetProxyPrivate(this);
     }
 
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -622,29 +622,60 @@ UnboxedPlainObject::convertToNative(JSCo
                 return false;
             MOZ_ASSERT(result.ok());
         }
     }
 
     return true;
 }
 
+/* static */ JS::Result<UnboxedObject*, JS::OOM&>
+UnboxedObject::createInternal(JSContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
+                              js::HandleObjectGroup group)
+{
+    const js::Class* clasp = group->clasp();
+    MOZ_ASSERT(clasp == &UnboxedPlainObject::class_ || clasp == &UnboxedArrayObject::class_);
+
+    MOZ_ASSERT(CanBeFinalizedInBackground(kind, clasp));
+    kind = GetBackgroundAllocKind(kind);
+
+    debugCheckNewObject(group, /* shape = */ nullptr, kind, heap);
+
+    JSObject* obj = js::Allocate<JSObject>(cx, kind, /* nDynamicSlots = */ 0, heap, clasp);
+    if (!obj)
+        return cx->alreadyReportedOOM();
+
+    UnboxedObject* uobj = static_cast<UnboxedObject*>(obj);
+    uobj->group_.init(group);
+
+    if (clasp->shouldDelayMetadataBuilder())
+        cx->compartment()->setObjectPendingMetadata(cx, uobj);
+    else
+        uobj = SetNewObjectMetadata(cx, uobj);
+
+    js::gc::TraceCreateObject(uobj);
+
+    return uobj;
+}
+
 /* static */
 UnboxedPlainObject*
 UnboxedPlainObject::create(JSContext* cx, HandleObjectGroup group, NewObjectKind newKind)
 {
     AutoSetNewObjectMetadata metadata(cx);
 
     MOZ_ASSERT(group->clasp() == &class_);
     gc::AllocKind allocKind = group->unboxedLayout().getAllocKind();
+    gc::InitialHeap heap = GetInitialHeap(newKind, &class_);
 
-    UnboxedPlainObject* res =
-        NewObjectWithGroup<UnboxedPlainObject>(cx, group, allocKind, newKind);
-    if (!res)
-        return nullptr;
+    MOZ_ASSERT(newKind != SingletonObject);
+
+    UnboxedObject* res_;
+    JS_TRY_VAR_OR_RETURN_NULL(cx, res_, createInternal(cx, allocKind, heap, group));
+    UnboxedPlainObject* res = &res_->as<UnboxedPlainObject>();
 
     // Overwrite the dummy shape which was written to the object's expando field.
     res->initExpando();
 
     // Initialize reference fields of the object. All fields in the object will
     // be overwritten shortly, but references need to be safe for the GC.
     const int32_t* list = res->layout().traceList();
     if (list) {
@@ -1056,38 +1087,43 @@ UnboxedArrayObject::create(JSContext* cx
 {
     MOZ_ASSERT(length <= MaximumCapacity);
 
     MOZ_ASSERT(group->clasp() == &class_);
     uint32_t elementSize = UnboxedTypeSize(group->unboxedLayout().elementType());
     uint32_t capacity = Min(length, maxLength);
     uint32_t nbytes = offsetOfInlineElements() + elementSize * capacity;
 
+    gc::InitialHeap heap = GetInitialHeap(newKind, &class_);
+
     UnboxedArrayObject* res;
     if (nbytes <= JSObject::MAX_BYTE_SIZE) {
         gc::AllocKind allocKind = gc::GetGCObjectKindForBytes(nbytes);
 
         // If there was no provided length information, pick an allocation kind
         // to accommodate small arrays (as is done for normal native arrays).
         if (capacity == 0)
             allocKind = gc::AllocKind::OBJECT8;
 
-        res = NewObjectWithGroup<UnboxedArrayObject>(cx, group, allocKind, newKind);
-        if (!res)
-            return nullptr;
+        UnboxedObject* res_;
+        JS_TRY_VAR_OR_RETURN_NULL(cx, res_, createInternal(cx, allocKind, heap, group));
+        res = &res_->as<UnboxedArrayObject>();
+
         res->setInitializedLengthNoBarrier(0);
         res->setInlineElements();
 
         size_t actualCapacity = (GetGCKindBytes(allocKind) - offsetOfInlineElements()) / elementSize;
         MOZ_ASSERT(actualCapacity >= capacity);
         res->setCapacityIndex(exactCapacityIndex(actualCapacity));
     } else {
-        res = NewObjectWithGroup<UnboxedArrayObject>(cx, group, gc::AllocKind::OBJECT0, newKind);
-        if (!res)
-            return nullptr;
+        UnboxedObject* res_;
+        JS_TRY_VAR_OR_RETURN_NULL(cx, res_,
+                                  createInternal(cx, gc::AllocKind::OBJECT0, heap, group));
+        res = &res_->as<UnboxedArrayObject>();
+
         res->setInitializedLengthNoBarrier(0);
 
         uint32_t capacityIndex = (capacity == length)
                                  ? CapacityMatchesLengthIndex
                                  : chooseCapacityIndex(capacity, length);
         uint32_t actualCapacity = computeCapacity(capacityIndex, length);
 
         res->elements_ = AllocateObjectBuffer<uint8_t>(cx, res, actualCapacity * elementSize);
--- a/js/src/vm/UnboxedObject.h
+++ b/js/src/vm/UnboxedObject.h
@@ -215,30 +215,38 @@ class UnboxedLayout : public mozilla::Li
     void trace(JSTracer* trc);
 
     size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
     static bool makeNativeGroup(JSContext* cx, ObjectGroup* group);
     static bool makeConstructorCode(JSContext* cx, HandleObjectGroup group);
 };
 
+class UnboxedObject : public JSObject
+{
+  protected:
+    static JS::Result<UnboxedObject*, JS::OOM&>
+    createInternal(JSContext* cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
+                   js::HandleObjectGroup group);
+};
+
 // Class for expando objects holding extra properties given to an unboxed plain
 // object. These objects behave identically to normal native plain objects, and
 // have a separate Class to distinguish them for memory usage reporting.
 class UnboxedExpandoObject : public NativeObject
 {
   public:
     static const Class class_;
 };
 
 // Class for a plain object using an unboxed representation. The physical
 // layout of these objects is identical to that of an InlineTypedObject, though
 // these objects use an UnboxedLayout instead of a TypeDescr to keep track of
 // how their properties are stored.
-class UnboxedPlainObject : public JSObject
+class UnboxedPlainObject : public UnboxedObject
 {
     // Optional object which stores extra properties on this object. This is
     // not automatically barriered to avoid problems if the object is converted
     // to a native. See ensureExpando().
     UnboxedExpandoObject* expando_;
 
     // Start of the inline data, which immediately follows the group and extra properties.
     uint8_t data_[1];
@@ -332,17 +340,17 @@ TryConvertToUnboxedLayout(JSContext* cx,
 inline gc::AllocKind
 UnboxedLayout::getAllocKind() const
 {
     MOZ_ASSERT(size());
     return gc::GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + size());
 }
 
 // Class for an array object using an unboxed representation.
-class UnboxedArrayObject : public JSObject
+class UnboxedArrayObject : public UnboxedObject
 {
     // Elements pointer for the object.
     uint8_t* elements_;
 
     // The nominal array length. This always fits in an int32_t.
     uint32_t length_;
 
     // Value indicating the allocated capacity and initialized length of the