Bug 1135423 - Use unboxed objects for object literals where possible, clean up object literal creation and property initialization code, r=jandem.
authorBrian Hackett <bhackett1024@gmail.com>
Sun, 01 Mar 2015 16:31:41 -0600
changeset 246748 6470d649e1bbfdd782cfdcfab28d87754ca39407
parent 246747 60a37ae57d9ccbb6d8a26a469128889f3f692b0c
child 246749 0ad2bf0582302fa92cd3f34d5a38010cb52d4b6a
push id879
push usercliu@mozilla.com
push dateTue, 03 Mar 2015 03:02:53 +0000
reviewersjandem
bugs1135423
milestone39.0a1
Bug 1135423 - Use unboxed objects for object literals where possible, clean up object literal creation and property initialization code, r=jandem.
js/src/gc/Marking.cpp
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineIC.cpp
js/src/jit/BaselineIC.h
js/src/jit/CodeGenerator.cpp
js/src/jit/Ion.cpp
js/src/jit/Ion.h
js/src/jit/IonBuilder.cpp
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/MIRGenerator.h
js/src/jit/MIRGraph.cpp
js/src/jit/Recover.cpp
js/src/jit/ScalarReplacement.cpp
js/src/jit/VMFunctions.cpp
js/src/jit/VMFunctions.h
js/src/vm/Interpreter-inl.h
js/src/vm/Interpreter.cpp
js/src/vm/Interpreter.h
js/src/vm/ObjectGroup.cpp
js/src/vm/ObjectGroup.h
js/src/vm/TypeInference-inl.h
js/src/vm/TypeInference.cpp
js/src/vm/TypeInference.h
js/src/vm/UnboxedObject.cpp
toolkit/devtools/server/tests/mochitest/test_memory_allocations_02.html
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1427,16 +1427,19 @@ ScanObjectGroup(GCMarker *gcmarker, Obje
         PushMarkStack(gcmarker, group->proto().toObject());
 
     if (group->singleton() && !group->lazy())
         PushMarkStack(gcmarker, group->singleton());
 
     if (group->newScript())
         group->newScript()->trace(gcmarker);
 
+    if (group->maybePreliminaryObjects())
+        group->maybePreliminaryObjects()->trace(gcmarker);
+
     if (group->maybeUnboxedLayout())
         group->unboxedLayout().trace(gcmarker);
 
     if (ObjectGroup *unboxedGroup = group->maybeOriginalUnboxedGroup())
         PushMarkStack(gcmarker, unboxedGroup);
 
     if (TypeDescr *descr = group->maybeTypeDescr())
         PushMarkStack(gcmarker, descr);
@@ -1458,16 +1461,19 @@ gc::MarkChildren(JSTracer *trc, ObjectGr
         MarkObject(trc, &group->protoRaw(), "group_proto");
 
     if (group->singleton() && !group->lazy())
         MarkObject(trc, &group->singletonRaw(), "group_singleton");
 
     if (group->newScript())
         group->newScript()->trace(trc);
 
+    if (group->maybePreliminaryObjects())
+        group->maybePreliminaryObjects()->trace(trc);
+
     if (group->maybeUnboxedLayout())
         group->unboxedLayout().trace(trc);
 
     if (ObjectGroup *unboxedGroup = group->maybeOriginalUnboxedGroup()) {
         MarkObjectGroupUnbarriered(trc, &unboxedGroup, "group_original_unboxed_group");
         group->setOriginalUnboxedGroup(unboxedGroup);
     }
 
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -1808,57 +1808,20 @@ BaselineCompiler::emit_JSOP_INITELEM_ARR
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_NEWOBJECT()
 {
     frame.syncStack(0);
 
-    RootedObjectGroup group(cx);
-    if (!ObjectGroup::useSingletonForAllocationSite(script, pc, JSProto_Object)) {
-        group = ObjectGroup::allocationSiteGroup(cx, script, pc, JSProto_Object);
-        if (!group)
-            return false;
-    }
-
-    RootedPlainObject baseObject(cx, &script->getObject(pc)->as<PlainObject>());
-    RootedPlainObject templateObject(cx, CopyInitializerObject(cx, baseObject, TenuredObject));
-    if (!templateObject)
-        return false;
-
-    if (group) {
-        templateObject->setGroup(group);
-    } else {
-        if (!JSObject::setSingleton(cx, templateObject))
-            return false;
-    }
-
-    // Try to do the allocation inline.
-    Label done;
-    if (group && !group->shouldPreTenure() && !templateObject->hasDynamicSlots()) {
-        Label slowPath;
-        Register objReg = R0.scratchReg();
-        Register tempReg = R1.scratchReg();
-        masm.movePtr(ImmGCPtr(group), tempReg);
-        masm.branchTest32(Assembler::NonZero, Address(tempReg, ObjectGroup::offsetOfFlags()),
-                          Imm32(OBJECT_FLAG_PRE_TENURE), &slowPath);
-        masm.branchPtr(Assembler::NotEqual, AbsoluteAddress(cx->compartment()->addressOfMetadataCallback()),
-                      ImmWord(0), &slowPath);
-        masm.createGCObject(objReg, tempReg, templateObject, gc::DefaultHeap, &slowPath);
-        masm.tagValue(JSVAL_TYPE_OBJECT, objReg, R0);
-        masm.jump(&done);
-        masm.bind(&slowPath);
-    }
-
-    ICNewObject_Fallback::Compiler stubCompiler(cx, templateObject);
+    ICNewObject_Fallback::Compiler stubCompiler(cx);
     if (!emitOpIC(stubCompiler.getStub(&stubSpace_)))
         return false;
 
-    masm.bind(&done);
     frame.push(R0);
     return true;
 }
 
 bool
 BaselineCompiler::emit_JSOP_NEWINIT()
 {
     frame.syncStack(0);
@@ -1882,29 +1845,17 @@ BaselineCompiler::emit_JSOP_NEWINIT()
         templateObject->setGroup(group);
 
         ICNewArray_Fallback::Compiler stubCompiler(cx, templateObject);
         if (!emitOpIC(stubCompiler.getStub(&stubSpace_)))
             return false;
     } else {
         MOZ_ASSERT(key == JSProto_Object);
 
-        RootedPlainObject templateObject(cx,
-            NewBuiltinClassInstance<PlainObject>(cx, TenuredObject));
-        if (!templateObject)
-            return false;
-
-        if (group) {
-            templateObject->setGroup(group);
-        } else {
-            if (!JSObject::setSingleton(cx, templateObject))
-                return false;
-        }
-
-        ICNewObject_Fallback::Compiler stubCompiler(cx, templateObject);
+        ICNewObject_Fallback::Compiler stubCompiler(cx);
         if (!emitOpIC(stubCompiler.getStub(&stubSpace_)))
             return false;
     }
 
     frame.push(R0);
     return true;
 }
 
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -485,17 +485,18 @@ ICStub::trace(JSTracer *trc)
       }
       case ICStub::NewArray_Fallback: {
         ICNewArray_Fallback *stub = toNewArray_Fallback();
         MarkObject(trc, &stub->templateObject(), "baseline-newarray-template");
         break;
       }
       case ICStub::NewObject_Fallback: {
         ICNewObject_Fallback *stub = toNewObject_Fallback();
-        MarkObject(trc, &stub->templateObject(), "baseline-newobject-template");
+        if (stub->templateObject())
+            MarkObject(trc, &stub->templateObject(), "baseline-newobject-template");
         break;
       }
       case ICStub::Rest_Fallback: {
         ICRest_Fallback *stub = toRest_Fallback();
         MarkObject(trc, &stub->templateObject(), "baseline-rest-template");
         break;
       }
       default:
@@ -1660,39 +1661,105 @@ ICNewArray_Fallback::Compiler::generateS
 
     return tailCallVM(DoNewArrayInfo, masm);
 }
 
 //
 // NewObject_Fallback
 //
 
-static bool
-DoNewObject(JSContext *cx, ICNewObject_Fallback *stub, MutableHandleValue res)
+// Unlike typical baseline IC stubs, the code for NewObject_WithTemplate is
+// specialized for the template object being allocated.
+static JitCode *
+GenerateNewObjectWithTemplateCode(JSContext *cx, JSObject *templateObject)
+{
+    JitContext jctx(cx, nullptr);
+    MacroAssembler masm;
+#ifdef JS_CODEGEN_ARM
+    masm.setSecondScratchReg(BaselineSecondScratchReg);
+#endif
+
+    Label failure;
+    Register objReg = R0.scratchReg();
+    Register tempReg = R1.scratchReg();
+    masm.movePtr(ImmGCPtr(templateObject->group()), tempReg);
+    masm.branchTest32(Assembler::NonZero, Address(tempReg, ObjectGroup::offsetOfFlags()),
+                      Imm32(OBJECT_FLAG_PRE_TENURE), &failure);
+    masm.branchPtr(Assembler::NotEqual, AbsoluteAddress(cx->compartment()->addressOfMetadataCallback()),
+                   ImmWord(0), &failure);
+    masm.createGCObject(objReg, tempReg, templateObject, gc::DefaultHeap, &failure);
+    masm.tagValue(JSVAL_TYPE_OBJECT, objReg, R0);
+
+    EmitReturnFromIC(masm);
+    masm.bind(&failure);
+    EmitStubGuardFailure(masm);
+
+    Linker linker(masm);
+    AutoFlushICache afc("GenerateNewObjectWithTemplateCode");
+    return linker.newCode<CanGC>(cx, BASELINE_CODE);
+}
+
+static bool
+DoNewObject(JSContext *cx, BaselineFrame *frame, ICNewObject_Fallback *stub, MutableHandleValue res)
 {
     FallbackICSpew(cx, stub, "NewObject");
 
-    RootedPlainObject templateObject(cx, stub->templateObject());
-    JSObject *obj = NewInitObject(cx, templateObject);
+    RootedObject obj(cx);
+
+    RootedObject templateObject(cx, stub->templateObject());
+    if (templateObject) {
+        MOZ_ASSERT(!templateObject->group()->maybePreliminaryObjects());
+        obj = NewObjectOperationWithTemplate(cx, templateObject);
+    } else {
+        RootedScript script(cx, frame->script());
+        jsbytecode *pc = stub->icEntry()->pc(script);
+        obj = NewObjectOperation(cx, script, pc);
+
+        if (obj && !obj->isSingleton() && !obj->group()->maybePreliminaryObjects()) {
+            JSObject *templateObject = NewObjectOperation(cx, script, pc, TenuredObject);
+            if (!templateObject)
+                return false;
+
+            if (templateObject->is<UnboxedPlainObject>() ||
+                !templateObject->as<PlainObject>().hasDynamicSlots())
+            {
+                JitCode *code = GenerateNewObjectWithTemplateCode(cx, templateObject);
+                if (!code)
+                    return false;
+
+                ICStubSpace *space =
+                    ICStubCompiler::StubSpaceForKind(ICStub::NewObject_WithTemplate, script);
+                ICStub *templateStub = ICStub::New<ICNewObject_WithTemplate>(space, code);
+                if (!templateStub)
+                    return false;
+
+                stub->addNewStub(templateStub);
+            }
+
+            stub->setTemplateObject(templateObject);
+        }
+    }
+
     if (!obj)
         return false;
 
     res.setObject(*obj);
     return true;
 }
 
-typedef bool(*DoNewObjectFn)(JSContext *, ICNewObject_Fallback *, MutableHandleValue);
+typedef bool(*DoNewObjectFn)(JSContext *, BaselineFrame *, ICNewObject_Fallback *, MutableHandleValue);
 static const VMFunction DoNewObjectInfo = FunctionInfo<DoNewObjectFn>(DoNewObject, TailCall);
 
 bool
 ICNewObject_Fallback::Compiler::generateStubCode(MacroAssembler &masm)
 {
     EmitRestoreTailCallReg(masm);
 
     masm.push(BaselineStubReg); // stub.
+    masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
 
     return tailCallVM(DoNewObjectInfo, masm);
 }
 
 //
 // Compare_Fallback
 //
 
@@ -8281,23 +8348,18 @@ DoSetPropFallback(JSContext *cx, Baselin
     {
         return false;
     }
 
     if (op == JSOP_INITPROP ||
         op == JSOP_INITLOCKEDPROP ||
         op == JSOP_INITHIDDENPROP)
     {
-        MOZ_ASSERT(obj->is<JSFunction>() || obj->is<PlainObject>());
-        unsigned propAttrs = GetInitDataPropAttrs(op);
-        if (!NativeDefineProperty(cx, obj.as<NativeObject>(), id, rhs,
-                                  nullptr, nullptr, propAttrs))
-        {
-            return false;
-        }
+        if (!InitPropertyOperation(cx, op, obj, id, rhs))
+            return false;
     } else if (op == JSOP_SETNAME ||
                op == JSOP_STRICTSETNAME ||
                op == JSOP_SETGNAME ||
                op == JSOP_STRICTSETGNAME)
     {
         if (!SetNameOperation(cx, script, pc, obj, rhs))
             return false;
     } else if (op == JSOP_SETALIASEDVAR || op == JSOP_INITALIASEDLEXICAL) {
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -343,16 +343,17 @@ class ICEntry
     _(TypeUpdate_SingleObject)  \
     _(TypeUpdate_ObjectGroup)   \
     _(TypeUpdate_PrimitiveSet)  \
                                 \
     _(This_Fallback)            \
                                 \
     _(NewArray_Fallback)        \
     _(NewObject_Fallback)       \
+    _(NewObject_WithTemplate)   \
                                 \
     _(Compare_Fallback)         \
     _(Compare_Int32)            \
     _(Compare_Double)           \
     _(Compare_NumberWithUndefined) \
     _(Compare_String)           \
     _(Compare_Boolean)          \
     _(Compare_Object)           \
@@ -1764,41 +1765,52 @@ class ICNewArray_Fallback : public ICFal
         return templateObject_;
     }
 };
 
 class ICNewObject_Fallback : public ICFallbackStub
 {
     friend class ICStubSpace;
 
-    HeapPtrPlainObject templateObject_;
-
-    ICNewObject_Fallback(JitCode *stubCode, PlainObject *templateObject)
-      : ICFallbackStub(ICStub::NewObject_Fallback, stubCode), templateObject_(templateObject)
+    HeapPtrObject templateObject_;
+
+    explicit ICNewObject_Fallback(JitCode *stubCode)
+      : ICFallbackStub(ICStub::NewObject_Fallback, stubCode), templateObject_(nullptr)
     {}
 
   public:
     class Compiler : public ICStubCompiler {
-        RootedPlainObject templateObject;
         bool generateStubCode(MacroAssembler &masm);
 
       public:
-        Compiler(JSContext *cx, PlainObject *templateObject)
-          : ICStubCompiler(cx, ICStub::NewObject_Fallback),
-            templateObject(cx, templateObject)
+        explicit Compiler(JSContext *cx)
+          : ICStubCompiler(cx, ICStub::NewObject_Fallback)
         {}
 
         ICStub *getStub(ICStubSpace *space) {
-            return ICStub::New<ICNewObject_Fallback>(space, getStubCode(), templateObject);
+            return ICStub::New<ICNewObject_Fallback>(space, getStubCode());
         }
     };
 
-    HeapPtrPlainObject &templateObject() {
+    HeapPtrObject &templateObject() {
         return templateObject_;
     }
+
+    void setTemplateObject(JSObject *obj) {
+        templateObject_ = obj;
+    }
+};
+
+class ICNewObject_WithTemplate : public ICStub
+{
+    friend class ICStubSpace;
+
+    explicit ICNewObject_WithTemplate(JitCode *stubCode)
+      : ICStub(ICStub::NewObject_WithTemplate, stubCode)
+    {}
 };
 
 // Compare
 //      JSOP_LT
 //      JSOP_GT
 
 class ICCompare_Fallback : public ICFallbackStub
 {
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -4201,53 +4201,70 @@ class OutOfLineNewObject : public OutOfL
         codegen->visitOutOfLineNewObject(this);
     }
 
     LNewObject *lir() const {
         return lir_;
     }
 };
 
-typedef JSObject *(*NewInitObjectFn)(JSContext *, HandlePlainObject);
-static const VMFunction NewInitObjectInfo = FunctionInfo<NewInitObjectFn>(NewInitObject);
+typedef JSObject *(*NewInitObjectWithTemplateFn)(JSContext *, HandleObject);
+static const VMFunction NewInitObjectWithTemplateInfo =
+    FunctionInfo<NewInitObjectWithTemplateFn>(NewObjectOperationWithTemplate);
+
+typedef JSObject *(*NewInitObjectFn)(JSContext *, HandleScript, jsbytecode *pc, NewObjectKind);
+static const VMFunction NewInitObjectInfo = FunctionInfo<NewInitObjectFn>(NewObjectOperation);
 
 typedef PlainObject *(*ObjectCreateWithTemplateFn)(JSContext *, HandlePlainObject);
 static const VMFunction ObjectCreateWithTemplateInfo =
     FunctionInfo<ObjectCreateWithTemplateFn>(ObjectCreateWithTemplate);
 
 void
 CodeGenerator::visitNewObjectVMCall(LNewObject *lir)
 {
     Register objReg = ToRegister(lir->output());
 
     MOZ_ASSERT(!lir->isCall());
     saveLive(lir);
 
-    pushArg(ImmGCPtr(lir->mir()->templateObject()));
+    JSObject *templateObject = lir->mir()->templateObject();
 
     // If we're making a new object with a class prototype (that is, an object
     // that derives its class from its prototype instead of being
-    // JSObject::class_'d) from self-hosted code, we need a different init
+    // PlainObject::class_'d) from self-hosted code, we need a different init
     // function.
     if (lir->mir()->mode() == MNewObject::ObjectLiteral) {
-        callVM(NewInitObjectInfo, lir);
+        if (templateObject) {
+            pushArg(ImmGCPtr(templateObject));
+            callVM(NewInitObjectWithTemplateInfo, lir);
+        } else {
+            pushArg(Imm32(GenericObject));
+            pushArg(ImmPtr(lir->mir()->resumePoint()->pc()));
+            pushArg(ImmGCPtr(lir->mir()->block()->info().script()));
+            callVM(NewInitObjectInfo, lir);
+        }
     } else {
         MOZ_ASSERT(lir->mir()->mode() == MNewObject::ObjectCreate);
+        pushArg(ImmGCPtr(templateObject));
         callVM(ObjectCreateWithTemplateInfo, lir);
     }
 
     if (ReturnReg != objReg)
         masm.movePtr(ReturnReg, objReg);
 
     restoreLive(lir);
 }
 
 static bool
-ShouldInitFixedSlots(LInstruction *lir, NativeObject *templateObj)
-{
+ShouldInitFixedSlots(LInstruction *lir, JSObject *obj)
+{
+    if (!obj->isNative())
+        return true;
+    NativeObject *templateObj = &obj->as<NativeObject>();
+
     // Look for StoreFixedSlot instructions following an object allocation
     // that write to this object before a GC is triggered or this object is
     // passed to a VM call. If all fixed slots will be initialized, the
     // allocation code doesn't need to set the slots to |undefined|.
 
     uint32_t nfixed = templateObj->numUsedFixedSlots();
     if (nfixed == 0)
         return false;
@@ -4324,17 +4341,17 @@ ShouldInitFixedSlots(LInstruction *lir, 
     MOZ_CRASH("Shouldn't get here");
 }
 
 void
 CodeGenerator::visitNewObject(LNewObject *lir)
 {
     Register objReg = ToRegister(lir->output());
     Register tempReg = ToRegister(lir->temp());
-    PlainObject *templateObject = lir->mir()->templateObject();
+    JSObject *templateObject = lir->mir()->templateObject();
 
     if (lir->mir()->shouldUseVM()) {
         visitNewObjectVMCall(lir);
         return;
     }
 
     OutOfLineNewObject *ool = new(alloc()) OutOfLineNewObject(lir);
     addOutOfLineCode(ool, lir->mir());
@@ -4624,18 +4641,17 @@ CodeGenerator::visitMutateProto(LMutateP
     Register objReg = ToRegister(lir->getObject());
 
     pushArg(ToValue(lir, LMutateProto::ValueIndex));
     pushArg(objReg);
 
     callVM(MutatePrototypeInfo, lir);
 }
 
-typedef bool(*InitPropFn)(JSContext *cx, HandleNativeObject obj,
-                          HandlePropertyName name, HandleValue value, jsbytecode *pc);
+typedef bool(*InitPropFn)(JSContext *, HandleObject, HandlePropertyName, HandleValue, jsbytecode *pc);
 static const VMFunction InitPropInfo = FunctionInfo<InitPropFn>(InitProp);
 
 void
 CodeGenerator::visitInitProp(LInitProp *lir)
 {
     Register objReg = ToRegister(lir->getObject());
 
     pushArg(ImmPtr(lir->mir()->resumePoint()->pc()));
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -1907,24 +1907,30 @@ IonCompile(JSContext *cx, JSScript *scri
     IonSpewFunction ionSpewFunction(graph, builderScript);
 #endif
 
     bool succeeded = builder->build();
     builder->clearForBackEnd();
 
     if (!succeeded) {
         AbortReason reason = builder->abortReason();
-        if (reason == AbortReason_NewScriptProperties) {
-            // Some type was accessed which needs the new script properties
-            // analysis to be performed. Do this now and we will try to build
-            // again shortly.
-            const MIRGenerator::ObjectGroupVector &groups = builder->abortedNewScriptPropertiesGroups();
+        if (reason == AbortReason_PreliminaryObjects) {
+            // Some group was accessed which has associated preliminary objects
+            // to analyze. Do this now and we will try to build again shortly.
+            const MIRGenerator::ObjectGroupVector &groups = builder->abortedPreliminaryGroups();
             for (size_t i = 0; i < groups.length(); i++) {
-                if (!groups[i]->newScript()->maybeAnalyze(cx, groups[i], nullptr, /* force = */ true))
-                    return AbortReason_Alloc;
+                ObjectGroup *group = groups[i];
+                if (group->newScript()) {
+                    if (!group->newScript()->maybeAnalyze(cx, group, nullptr, /* force = */ true))
+                        return AbortReason_Alloc;
+                } else if (group->maybePreliminaryObjects()) {
+                    group->maybePreliminaryObjects()->maybeAnalyze(cx, group, /* force = */ true);
+                } else {
+                    MOZ_CRASH("Unexpected aborted preliminary group");
+                }
             }
         }
 
         if (builder->hadActionableAbort()) {
             JSScript *abortScript;
             jsbytecode *abortPc;
             const char *abortMessage;
             builder->actionableAbortLocationAndMessage(&abortScript, &abortPc, &abortMessage);
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -26,17 +26,17 @@ enum MethodStatus
     Method_CantCompile,
     Method_Skipped,
     Method_Compiled
 };
 
 enum AbortReason {
     AbortReason_Alloc,
     AbortReason_Inlining,
-    AbortReason_NewScriptProperties,
+    AbortReason_PreliminaryObjects,
     AbortReason_Disable,
     AbortReason_Error,
     AbortReason_NoAbort
 };
 
 // A JIT context is needed to enter into either an JIT method or an instance
 // of a JIT compiler. It points to a temporary allocator and the active
 // JSContext, either of which may be nullptr, and the active compartment, which
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -870,19 +870,19 @@ IonBuilder::build()
     replaceMaybeFallbackFunctionGetter(nullptr);
 
     if (!maybeAddOsrTypeBarriers())
         return false;
 
     if (!processIterators())
         return false;
 
-    if (!abortedNewScriptPropertiesGroups().empty()) {
+    if (!abortedPreliminaryGroups().empty()) {
         MOZ_ASSERT(!info().isAnalysis());
-        abortReason_ = AbortReason_NewScriptProperties;
+        abortReason_ = AbortReason_PreliminaryObjects;
         return false;
     }
 
     if (shouldForceAbort()) {
         abortReason_ = AbortReason_Disable;
         return false;
     }
 
@@ -1025,19 +1025,19 @@ IonBuilder::buildInline(IonBuilder *call
     insertRecompileCheck();
 
     if (!traverseBytecode())
         return false;
 
     // Discard unreferenced & pre-allocated resume points.
     replaceMaybeFallbackFunctionGetter(nullptr);
 
-    if (!abortedNewScriptPropertiesGroups().empty()) {
+    if (!abortedPreliminaryGroups().empty()) {
         MOZ_ASSERT(!info().isAnalysis());
-        abortReason_ = AbortReason_NewScriptProperties;
+        abortReason_ = AbortReason_PreliminaryObjects;
         return false;
     }
 
     if (shouldForceAbort()) {
         abortReason_ = AbortReason_Disable;
         return false;
     }
 
@@ -4619,22 +4619,22 @@ IonBuilder::inlineScriptedCall(CallInfo 
 
         // Inlining the callee failed. Mark the callee as uninlineable only if
         // the inlining was aborted for a non-exception reason.
         if (inlineBuilder.abortReason_ == AbortReason_Disable) {
             calleeScript->setUninlineable();
             abortReason_ = AbortReason_Inlining;
         } else if (inlineBuilder.abortReason_ == AbortReason_Inlining) {
             abortReason_ = AbortReason_Inlining;
-        } else if (inlineBuilder.abortReason_ == AbortReason_NewScriptProperties) {
-            const ObjectGroupVector &groups = inlineBuilder.abortedNewScriptPropertiesGroups();
+        } else if (inlineBuilder.abortReason_ == AbortReason_PreliminaryObjects) {
+            const ObjectGroupVector &groups = inlineBuilder.abortedPreliminaryGroups();
             MOZ_ASSERT(!groups.empty());
             for (size_t i = 0; i < groups.length(); i++)
-                addAbortedNewScriptPropertiesGroup(groups[i]);
-            abortReason_ = AbortReason_NewScriptProperties;
+                addAbortedPreliminaryGroup(groups[i]);
+            abortReason_ = AbortReason_PreliminaryObjects;
         }
 
         return false;
     }
 
     MOZ_ASSERT(inlineBuilder.nurseryObjects_.empty(),
                "Nursery objects should be added to outer builder");
 
@@ -6375,33 +6375,29 @@ IonBuilder::jsop_newarray_copyonwrite()
 
     return true;
 }
 
 bool
 IonBuilder::jsop_newobject()
 {
     JSObject *templateObject = inspector->getTemplateObject(pc);
-    if (!templateObject) {
-        if (info().analysisMode() == Analysis_ArgumentsUsage) {
-            MUnknownValue *unknown = MUnknownValue::New(alloc());
-            current->add(unknown);
-            current->push(unknown);
-            return true;
-        }
-        return abort("No template object for NEWOBJECT");
-    }
-
-    MOZ_ASSERT(templateObject->is<PlainObject>());
-    MConstant *templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
+    gc::InitialHeap heap;
+    MConstant *templateConst;
+
+    if (templateObject) {
+        heap = templateObject->group()->initialHeap(constraints());
+        templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
+    } else {
+        heap = gc::DefaultHeap;
+        templateConst = MConstant::New(alloc(), NullValue());
+    }
+
     current->add(templateConst);
-    MNewObject *ins = MNewObject::New(alloc(), constraints(), templateConst,
-                                      templateObject->isSingleton()
-                                      ? gc::TenuredHeap
-                                      : templateObject->group()->initialHeap(constraints()),
+    MNewObject *ins = MNewObject::New(alloc(), constraints(), templateConst, heap,
                                       MNewObject::ObjectLiteral);
 
     current->add(ins);
     current->push(ins);
 
     return resumeAfter(ins);
 }
 
@@ -6493,76 +6489,63 @@ IonBuilder::jsop_mutateproto()
     MMutateProto *mutate = MMutateProto::New(alloc(), obj, value);
     current->add(mutate);
     return resumeAfter(mutate);
 }
 
 bool
 IonBuilder::jsop_initprop(PropertyName *name)
 {
-    MDefinition *value = current->pop();
-    MDefinition *obj = current->peek(-1);
-
-    NativeObject *templateObject = nullptr;
-    Shape *shape = nullptr;
-
     bool useSlowPath = false;
 
-    if (obj->isUnknownValue() || obj->isLambda()) {
+    MDefinition *value = current->peek(-1);
+    MDefinition *obj = current->peek(-2);
+    if (obj->isLambda()) {
         useSlowPath = true;
+    } else if (JSObject *templateObject = obj->toNewObject()->templateObject()) {
+        if (templateObject->is<PlainObject>()) {
+            if (!templateObject->as<PlainObject>().containsPure(name))
+                useSlowPath = true;
+        } else {
+            MOZ_ASSERT(templateObject->as<UnboxedPlainObject>().layout().lookup(name));
+        }
     } else {
-        templateObject = obj->toNewObject()->templateObject();
-        shape = templateObject->lastProperty()->searchLinear(NameToId(name));
-
-        if (!shape)
-            useSlowPath = true;
-    }
-
-    if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
-                                      &obj, name, &value, /* canModify = */ true))
-    {
         useSlowPath = true;
     }
 
     if (useSlowPath) {
-        // JSOP_NEWINIT becomes an MNewObject without preconfigured properties.
+        current->pop();
         MInitProp *init = MInitProp::New(alloc(), obj, name, value);
         current->add(init);
         return resumeAfter(init);
     }
 
-    if (NeedsPostBarrier(info(), value))
-        current->add(MPostWriteBarrier::New(alloc(), obj, value));
-
-    bool needsBarrier = true;
-    if (obj->resultTypeSet() &&
-        !obj->resultTypeSet()->propertyNeedsBarrier(constraints(), NameToId(name)))
-    {
-        needsBarrier = false;
-    }
-
-    if (templateObject->isFixedSlot(shape->slot())) {
-        MStoreFixedSlot *store = MStoreFixedSlot::New(alloc(), obj, shape->slot(), value);
-        if (needsBarrier)
-            store->setNeedsBarrier();
-
-        current->add(store);
-        return resumeAfter(store);
-    }
-
-    MSlots *slots = MSlots::New(alloc(), obj);
-    current->add(slots);
-
-    uint32_t slot = templateObject->dynamicSlotIndex(shape->slot());
-    MStoreSlot *store = MStoreSlot::New(alloc(), slots, slot, value);
-    if (needsBarrier)
-        store->setNeedsBarrier();
-
-    current->add(store);
-    return resumeAfter(store);
+    MInstruction *last = *current->rbegin();
+
+    // This is definitely initializing an 'own' property of the object, treat
+    // it as an assignment.
+    if (!jsop_setprop(name))
+        return false;
+
+    // SETPROP pushed the value, instead of the object. Fix this on the stack,
+    // and check the most recent resume point to see if it needs updating too.
+    current->pop();
+    current->push(obj);
+    for (MInstructionReverseIterator riter = current->rbegin(); *riter != last; riter++) {
+        if (MResumePoint *resumePoint = riter->resumePoint()) {
+            MOZ_ASSERT(resumePoint->pc() == pc);
+            if (resumePoint->mode() == MResumePoint::ResumeAfter) {
+                size_t index = resumePoint->numOperands() - 1;
+                resumePoint->replaceOperand(index, obj);
+            }
+            break;
+        }
+    }
+
+    return true;
 }
 
 bool
 IonBuilder::jsop_initprop_getter_setter(PropertyName *name)
 {
     MDefinition *value = current->pop();
     MDefinition *obj = current->peek(-1);
 
@@ -9318,35 +9301,41 @@ uint32_t
 IonBuilder::getDefiniteSlot(TemporaryTypeSet *types, PropertyName *name, uint32_t *pnfixed,
                             BaselineInspector::ObjectGroupVector &convertUnboxedGroups)
 {
     if (!types || types->unknownObject()) {
         trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
         return UINT32_MAX;
     }
 
-    // Watch for types which the new script properties analysis has not been
-    // performed on yet. Normally this is done after a small number of the
+    // Watch for groups which still have preliminary object information and
+    // have not had the new script properties other analyses performed on their
+    // preliminary objects. Normally this is done after a small number of the
     // objects have been created, but if only a few have been created we can
     // still perform the analysis with a smaller object population. The
     // analysis can have side effects so abort the builder and retry later.
     //
     // We always check this, so that even if we aren't able to find a common
     // slot we ensure that the new script analysis is performed on all accessed
     // objects. Later, this will let us elide baseline IC stubs for preliminary
     // objects, which often have a different number of fixed slots from
     // subsequent objects.
     for (size_t i = 0; i < types->getObjectCount(); i++) {
         TypeSet::ObjectKey *key = types->getObject(i);
         if (!key)
             continue;
 
         if (ObjectGroup *group = key->maybeGroup()) {
             if (group->newScript() && !group->newScript()->analyzed()) {
-                addAbortedNewScriptPropertiesGroup(group);
+                addAbortedPreliminaryGroup(group);
+                trackOptimizationOutcome(TrackedOutcome::NoAnalysisInfo);
+                return UINT32_MAX;
+            }
+            if (group->maybePreliminaryObjects()) {
+                addAbortedPreliminaryGroup(group);
                 trackOptimizationOutcome(TrackedOutcome::NoAnalysisInfo);
                 return UINT32_MAX;
             }
         }
     }
 
     uint32_t slot = UINT32_MAX;
 
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -3736,18 +3736,19 @@ MBeta::printOpcode(FILE *fp) const
     } else {
         fprintf(fp, " ???");
     }
 }
 
 bool
 MNewObject::shouldUseVM() const
 {
-    PlainObject *obj = templateObject();
-    return obj->isSingleton() || obj->hasDynamicSlots();
+    if (JSObject *obj = templateObject())
+        return obj->is<PlainObject>() && obj->as<PlainObject>().hasDynamicSlots();
+    return true;
 }
 
 bool
 MCreateThisWithTemplate::canRecoverOnBailout() const
 {
     MOZ_ASSERT(templateObject()->is<PlainObject>() || templateObject()->is<UnboxedPlainObject>());
     MOZ_ASSERT_IF(templateObject()->is<PlainObject>(),
                   !templateObject()->as<PlainObject>().denseElementsAreCopyOnWrite());
@@ -3756,17 +3757,17 @@ MCreateThisWithTemplate::canRecoverOnBai
 
 MObjectState::MObjectState(MDefinition *obj)
 {
     // This instruction is only used as a summary for bailout paths.
     setResultType(MIRType_Object);
     setRecoveredOnBailout();
     NativeObject *templateObject = nullptr;
     if (obj->isNewObject())
-        templateObject = obj->toNewObject()->templateObject();
+        templateObject = &obj->toNewObject()->templateObject()->as<PlainObject>();
     else if (obj->isCreateThisWithTemplate())
         templateObject = &obj->toCreateThisWithTemplate()->templateObject()->as<PlainObject>();
     else
         templateObject = obj->toNewCallObject()->templateObject();
     numSlots_ = templateObject->slotSpan();
     numFixedSlots_ = templateObject->numFixedSlots();
 }
 
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -2867,28 +2867,29 @@ class MNewObject
     Mode mode_;
 
     MNewObject(CompilerConstraintList *constraints, MConstant *templateConst,
                gc::InitialHeap initialHeap, Mode mode)
       : MUnaryInstruction(templateConst),
         initialHeap_(initialHeap),
         mode_(mode)
     {
-        PlainObject *obj = templateObject();
         MOZ_ASSERT_IF(mode != ObjectLiteral, !shouldUseVM());
         setResultType(MIRType_Object);
-        if (!obj->isSingleton())
+
+        if (JSObject *obj = templateObject())
             setResultTypeSet(MakeSingletonTypeSet(constraints, obj));
 
         // The constant is kept separated in a MConstant, this way we can safely
         // mark it during GC if we recover the object allocation.  Otherwise, by
         // making it emittedAtUses, we do not produce register allocations for
         // it and inline its content inside the code produced by the
         // CodeGenerator.
-        templateConst->setEmittedAtUses();
+        if (templateConst->toConstant()->value().isObject())
+            templateConst->setEmittedAtUses();
     }
 
   public:
     INSTRUCTION_HEADER(NewObject)
 
     static MNewObject *New(TempAllocator &alloc, CompilerConstraintList *constraints,
                            MConstant *templateConst, gc::InitialHeap initialHeap,
                            Mode mode)
@@ -2899,18 +2900,18 @@ class MNewObject
     // Returns true if the code generator should call through to the
     // VM rather than the fast path.
     bool shouldUseVM() const;
 
     Mode mode() const {
         return mode_;
     }
 
-    PlainObject *templateObject() const {
-        return &getOperand(0)->toConstant()->value().toObject().as<PlainObject>();
+    JSObject *templateObject() const {
+        return getOperand(0)->toConstant()->value().toObjectOrNull();
     }
 
     gc::InitialHeap initialHeap() const {
         return initialHeap_;
     }
 
     bool writeRecoverData(CompactBufferWriter &writer) const MOZ_OVERRIDE;
     bool canRecoverOnBailout() const MOZ_OVERRIDE {
--- a/js/src/jit/MIRGenerator.h
+++ b/js/src/jit/MIRGenerator.h
@@ -152,35 +152,35 @@ class MIRGenerator
     }
 
     bool modifiesFrameArguments() const {
         return modifiesFrameArguments_;
     }
 
     typedef Vector<ObjectGroup *, 0, JitAllocPolicy> ObjectGroupVector;
 
-    // When abortReason() == AbortReason_NewScriptProperties, all types which
-    // the new script properties analysis hasn't been performed on yet.
-    const ObjectGroupVector &abortedNewScriptPropertiesGroups() const {
-        return abortedNewScriptPropertiesGroups_;
+    // When abortReason() == AbortReason_PreliminaryObjects, all groups with
+    // preliminary objects which haven't been analyzed yet.
+    const ObjectGroupVector &abortedPreliminaryGroups() const {
+        return abortedPreliminaryGroups_;
     }
 
   public:
     CompileCompartment *compartment;
 
   protected:
     CompileInfo *info_;
     const OptimizationInfo *optimizationInfo_;
     TempAllocator *alloc_;
     JSFunction *fun_;
     uint32_t nslots_;
     MIRGraph *graph_;
     AbortReason abortReason_;
     bool shouldForceAbort_; // Force AbortReason_Disable
-    ObjectGroupVector abortedNewScriptPropertiesGroups_;
+    ObjectGroupVector abortedPreliminaryGroups_;
     bool error_;
     mozilla::Atomic<bool, mozilla::Relaxed> *pauseBuild_;
     mozilla::Atomic<bool, mozilla::Relaxed> cancelBuild_;
 
     uint32_t maxAsmJSStackArgBytes_;
     bool performsCall_;
     bool usesSimd_;
     bool usesSimdCached_;
@@ -195,20 +195,21 @@ class MIRGenerator
     bool instrumentedProfilingIsCached_;
 
     // List of nursery objects used by this compilation. Can be traced by a
     // minor GC while compilation happens off-thread. This Vector should only
     // be accessed on the main thread (IonBuilder, nursery GC or
     // CodeGenerator::link).
     ObjectVector nurseryObjects_;
 
+    void addAbortedPreliminaryGroup(ObjectGroup *group);
+
     Label *outOfBoundsLabel_;
     bool usesSignalHandlersForAsmJSOOB_;
 
-    void addAbortedNewScriptPropertiesGroup(ObjectGroup *type);
     void setForceAbort() {
         shouldForceAbort_ = true;
     }
     bool shouldForceAbort() {
         return shouldForceAbort_;
     }
 
 #if defined(JS_ION_PERF)
--- a/js/src/jit/MIRGraph.cpp
+++ b/js/src/jit/MIRGraph.cpp
@@ -23,17 +23,17 @@ MIRGenerator::MIRGenerator(CompileCompar
                            Label *outOfBoundsLabel, bool usesSignalHandlersForAsmJSOOB)
   : compartment(compartment),
     info_(info),
     optimizationInfo_(optimizationInfo),
     alloc_(alloc),
     graph_(graph),
     abortReason_(AbortReason_NoAbort),
     shouldForceAbort_(false),
-    abortedNewScriptPropertiesGroups_(*alloc_),
+    abortedPreliminaryGroups_(*alloc_),
     error_(false),
     pauseBuild_(nullptr),
     cancelBuild_(false),
     maxAsmJSStackArgBytes_(0),
     performsCall_(false),
     usesSimd_(false),
     usesSimdCached_(false),
     minAsmJSHeapLength_(0),
@@ -91,24 +91,24 @@ MIRGenerator::abort(const char *message,
     va_list ap;
     va_start(ap, message);
     abortFmt(message, ap);
     va_end(ap);
     return false;
 }
 
 void
-MIRGenerator::addAbortedNewScriptPropertiesGroup(ObjectGroup *group)
+MIRGenerator::addAbortedPreliminaryGroup(ObjectGroup *group)
 {
-    for (size_t i = 0; i < abortedNewScriptPropertiesGroups_.length(); i++) {
-        if (group == abortedNewScriptPropertiesGroups_[i])
+    for (size_t i = 0; i < abortedPreliminaryGroups_.length(); i++) {
+        if (group == abortedPreliminaryGroups_[i])
             return;
     }
-    if (!abortedNewScriptPropertiesGroups_.append(group))
-        CrashAtUnhandlableOOM("addAbortedNewScriptPropertiesGroup");
+    if (!abortedPreliminaryGroups_.append(group))
+        CrashAtUnhandlableOOM("addAbortedPreliminaryGroup");
 }
 
 void
 MIRGraph::addBlock(MBasicBlock *block)
 {
     MOZ_ASSERT(block);
     block->setId(blockIdGen_++);
     blocks_.pushBack(block);
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -1172,17 +1172,17 @@ bool
 RNewObject::recover(JSContext *cx, SnapshotIterator &iter) const
 {
     RootedPlainObject templateObject(cx, &iter.read().toObject().as<PlainObject>());
     RootedValue result(cx);
     JSObject *resultObject = nullptr;
 
     // See CodeGenerator::visitNewObjectVMCall
     if (mode_ == MNewObject::ObjectLiteral) {
-        resultObject = NewInitObject(cx, templateObject);
+        resultObject = NewObjectOperationWithTemplate(cx, templateObject);
     } else {
         MOZ_ASSERT(mode_ == MNewObject::ObjectCreate);
         resultObject = ObjectCreateWithTemplate(cx, templateObject);
     }
 
     if (!resultObject)
         return false;
 
--- a/js/src/jit/ScalarReplacement.cpp
+++ b/js/src/jit/ScalarReplacement.cpp
@@ -108,16 +108,19 @@ IsObjectEscaped(MInstruction *ins, JSObj
         obj = ins->toNewObject()->templateObject();
     else if (ins->isCreateThisWithTemplate())
         obj = ins->toCreateThisWithTemplate()->templateObject();
     else if (ins->isNewCallObject())
         obj = ins->toNewCallObject()->templateObject();
     else
         obj = objDefault;
 
+    if (!obj)
+        return true;
+
     // Don't optimize unboxed objects, which aren't handled by MObjectState.
     if (obj->is<UnboxedPlainObject>())
         return true;
 
     // Check if the object is escaped. If the object is not the first argument
     // of either a known Store / Load, then we consider it as escaped. This is a
     // cheap and conservative escape analysis.
     for (MUseIterator i(ins->usesBegin()); i != ins->usesEnd(); i++) {
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -187,22 +187,21 @@ MutatePrototype(JSContext *cx, HandlePla
     bool succeeded;
     if (!SetPrototype(cx, obj, newProto, &succeeded))
         return false;
     MOZ_ASSERT(succeeded);
     return true;
 }
 
 bool
-InitProp(JSContext *cx, HandleNativeObject obj, HandlePropertyName name, HandleValue value,
+InitProp(JSContext *cx, HandleObject obj, HandlePropertyName name, HandleValue value,
          jsbytecode *pc)
 {
     RootedId id(cx, NameToId(name));
-    unsigned propAttrs = GetInitDataPropAttrs(JSOp(*pc));
-    return NativeDefineProperty(cx, obj, id, value, nullptr, nullptr, propAttrs);
+    return InitPropertyOperation(cx, JSOp(*pc), obj, id, value);
 }
 
 template<bool Equal>
 bool
 LooselyEqual(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res)
 {
     if (!js::LooselyEqual(cx, lhs, rhs, res))
         return false;
@@ -261,33 +260,16 @@ StringsEqual(JSContext *cx, HandleString
     if (!Equal)
         *res = !*res;
     return true;
 }
 
 template bool StringsEqual<true>(JSContext *cx, HandleString lhs, HandleString rhs, bool *res);
 template bool StringsEqual<false>(JSContext *cx, HandleString lhs, HandleString rhs, bool *res);
 
-JSObject*
-NewInitObject(JSContext *cx, HandlePlainObject templateObject)
-{
-    NewObjectKind newKind = templateObject->isSingleton() ? SingletonObject : GenericObject;
-    if (!templateObject->hasLazyGroup() && templateObject->group()->shouldPreTenure())
-        newKind = TenuredObject;
-    RootedObject obj(cx, CopyInitializerObject(cx, templateObject, newKind));
-
-    if (!obj)
-        return nullptr;
-
-    if (!templateObject->isSingleton())
-        obj->setGroup(templateObject->group());
-
-    return obj;
-}
-
 bool
 ArraySpliceDense(JSContext *cx, HandleObject obj, uint32_t start, uint32_t deleteCount)
 {
     JS::AutoValueArray<4> argv(cx);
     argv[0].setUndefined();
     argv[1].setObject(*obj);
     argv[2].set(Int32Value(start));
     argv[3].set(Int32Value(deleteCount));
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -642,36 +642,33 @@ JSObject *NewGCObject(JSContext *cx, gc:
 
 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);
-
-bool InitProp(JSContext *cx, HandleNativeObject obj, HandlePropertyName name, HandleValue value,
+bool InitProp(JSContext *cx, HandleObject obj, HandlePropertyName name, HandleValue value,
               jsbytecode *pc);
 
 template<bool Equal>
 bool LooselyEqual(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res);
 
 template<bool Equal>
 bool StrictlyEqual(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res);
 
 bool LessThan(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res);
 bool LessThanOrEqual(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res);
 bool GreaterThan(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res);
 bool GreaterThanOrEqual(JSContext *cx, MutableHandleValue lhs, MutableHandleValue rhs, bool *res);
 
 template<bool Equal>
 bool StringsEqual(JSContext *cx, HandleString left, HandleString right, bool *res);
 
-JSObject *NewInitObject(JSContext *cx, HandlePlainObject templateObject);
-
 bool ArrayPopDense(JSContext *cx, HandleObject obj, MutableHandleValue rval);
 bool ArrayPushDense(JSContext *cx, HandleArrayObject obj, HandleValue v, uint32_t *length);
 bool ArrayShiftDense(JSContext *cx, HandleObject obj, MutableHandleValue rval);
 JSObject *ArrayConcatDense(JSContext *cx, HandleObject obj1, HandleObject obj2, HandleObject res);
 JSString *ArrayJoin(JSContext *cx, HandleObject array, HandleString sep);
 
 bool CharCodeAt(JSContext *cx, HandleString str, int32_t index, uint32_t *code);
 JSFlatString *StringFromCharCode(JSContext *cx, int32_t code);
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -324,16 +324,30 @@ SetNameOperation(JSContext *cx, JSScript
         return NativeSetProperty(cx, scope.as<NativeObject>(), scope.as<NativeObject>(), id,
                                  Unqualified, &valCopy, strict);
     }
 
     return SetProperty(cx, scope, scope, name, &valCopy, strict);
 }
 
 inline bool
+InitPropertyOperation(JSContext *cx, JSOp op, HandleObject obj, HandleId id, HandleValue rhs)
+{
+    if (obj->is<PlainObject>() || obj->is<JSFunction>()) {
+        unsigned propAttrs = GetInitDataPropAttrs(op);
+        return NativeDefineProperty(cx, obj.as<NativeObject>(), id, rhs, nullptr, nullptr,
+                                    propAttrs);
+    }
+
+    MOZ_ASSERT(obj->as<UnboxedPlainObject>().layout().lookup(id));
+    RootedValue v(cx, rhs);
+    return SetProperty(cx, obj, obj, id, &v, false);
+}
+
+inline bool
 DefVarOrConstOperation(JSContext *cx, HandleObject varobj, HandlePropertyName dn, unsigned attrs)
 {
     MOZ_ASSERT(varobj->isQualifiedVarObj());
 
     RootedShape prop(cx);
     RootedObject obj2(cx);
     if (!LookupProperty(cx, varobj, dn, &obj2, &prop))
         return false;
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -3126,35 +3126,33 @@ END_CASE(JSOP_INITELEM_GETTER)
 CASE(JSOP_HOLE)
     PUSH_HOLE();
 END_CASE(JSOP_HOLE)
 
 CASE(JSOP_NEWINIT)
 {
     uint8_t i = GET_UINT8(REGS.pc);
     MOZ_ASSERT(i == JSProto_Array || i == JSProto_Object);
-
     RootedObject &obj = rootObject0;
-    NewObjectKind newKind = GenericObject;
+
     if (i == JSProto_Array) {
+        NewObjectKind newKind = GenericObject;
         if (ObjectGroup::useSingletonForAllocationSite(script, REGS.pc, &ArrayObject::class_))
             newKind = SingletonObject;
         obj = NewDenseEmptyArray(cx, NullPtr(), newKind);
+        if (!obj || !ObjectGroup::setAllocationSiteObjectGroup(cx, script, REGS.pc, obj,
+                                                               newKind == SingletonObject))
+        {
+            goto error;
+        }
     } else {
-        gc::AllocKind allocKind = GuessObjectGCKind(0);
-        if (ObjectGroup::useSingletonForAllocationSite(script, REGS.pc, &PlainObject::class_))
-            newKind = SingletonObject;
-        obj = NewBuiltinClassInstance<PlainObject>(cx, allocKind, newKind);
+        obj = NewObjectOperation(cx, script, REGS.pc);
+        if (!obj)
+            goto error;
     }
-    if (!obj || !ObjectGroup::setAllocationSiteObjectGroup(cx, script, REGS.pc, obj,
-                                                           newKind == SingletonObject))
-    {
-        goto error;
-    }
-
     PUSH_OBJECT(*obj);
 }
 END_CASE(JSOP_NEWINIT)
 
 CASE(JSOP_NEWARRAY)
 {
     unsigned count = GET_UINT24(REGS.pc);
     RootedObject &obj = rootObject0;
@@ -3185,30 +3183,19 @@ CASE(JSOP_NEWARRAY_COPYONWRITE)
         goto error;
 
     PUSH_OBJECT(*obj);
 }
 END_CASE(JSOP_NEWARRAY_COPYONWRITE)
 
 CASE(JSOP_NEWOBJECT)
 {
-    RootedObject &baseobj = rootObject0;
-    baseobj = script->getObject(REGS.pc);
-
-    RootedObject &obj = rootObject1;
-    NewObjectKind newKind = GenericObject;
-    if (ObjectGroup::useSingletonForAllocationSite(script, REGS.pc, baseobj->getClass()))
-        newKind = SingletonObject;
-    obj = CopyInitializerObject(cx, baseobj.as<PlainObject>(), newKind);
-    if (!obj || !ObjectGroup::setAllocationSiteObjectGroup(cx, script, REGS.pc, obj,
-                                                           newKind == SingletonObject))
-    {
+    JSObject *obj = NewObjectOperation(cx, script, REGS.pc);
+    if (!obj)
         goto error;
-    }
-
     PUSH_OBJECT(*obj);
 }
 END_CASE(JSOP_NEWOBJECT)
 
 CASE(JSOP_MUTATEPROTO)
 {
     MOZ_ASSERT(REGS.stackDepth() >= 2);
 
@@ -3239,27 +3226,25 @@ CASE(JSOP_INITHIDDENPROP)
     static_assert(JSOP_INITPROP_LENGTH == JSOP_INITHIDDENPROP_LENGTH,
                   "initprop and inithiddenprop must be the same size");
     /* Load the property's initial value into rval. */
     MOZ_ASSERT(REGS.stackDepth() >= 2);
     RootedValue &rval = rootValue0;
     rval = REGS.sp[-1];
 
     /* Load the object being initialized into lval/obj. */
-    RootedNativeObject &obj = rootNativeObject0;
-    obj = &REGS.sp[-2].toObject().as<NativeObject>();
-    MOZ_ASSERT(obj->is<PlainObject>() || obj->is<JSFunction>());
+    RootedObject &obj = rootObject0;
+    obj = &REGS.sp[-2].toObject();
 
     PropertyName *name = script->getName(REGS.pc);
 
     RootedId &id = rootId0;
     id = NameToId(name);
 
-    unsigned propAttrs = GetInitDataPropAttrs(JSOp(*REGS.pc));
-    if (!NativeDefineProperty(cx, obj, id, rval, nullptr, nullptr, propAttrs))
+    if (!InitPropertyOperation(cx, JSOp(*REGS.pc), obj, id, rval))
         goto error;
 
     REGS.sp--;
 }
 END_CASE(JSOP_INITPROP)
 
 CASE(JSOP_INITELEM)
 {
@@ -4146,16 +4131,92 @@ js::SpreadCallOperation(JSContext *cx, H
         MOZ_CRASH("bad spread opcode");
     }
 
     res.set(args.rval());
     TypeScript::Monitor(cx, script, pc, res);
     return true;
 }
 
+JSObject *
+js::NewObjectOperation(JSContext *cx, HandleScript script, jsbytecode *pc,
+                       NewObjectKind newKind /* = GenericObject */)
+{
+    MOZ_ASSERT(newKind != SingletonObject);
+
+    RootedObjectGroup group(cx);
+    if (ObjectGroup::useSingletonForAllocationSite(script, pc, JSProto_Object)) {
+        newKind = SingletonObject;
+    } else {
+        group = ObjectGroup::allocationSiteGroup(cx, script, pc, JSProto_Object);
+        if (!group)
+            return nullptr;
+        if (group->maybePreliminaryObjects())
+            group->maybePreliminaryObjects()->maybeAnalyze(cx, group);
+
+        if (group->shouldPreTenure() || group->maybePreliminaryObjects())
+            newKind = TenuredObject;
+
+        if (group->maybeUnboxedLayout())
+            return UnboxedPlainObject::create(cx, group, newKind);
+    }
+
+    RootedObject obj(cx);
+
+    if (*pc == JSOP_NEWOBJECT) {
+        RootedPlainObject baseObject(cx, &script->getObject(pc)->as<PlainObject>());
+        obj = CopyInitializerObject(cx, baseObject, newKind);
+    } else {
+        MOZ_ASSERT(*pc == JSOP_NEWINIT);
+        MOZ_ASSERT(GET_UINT8(pc) == JSProto_Object);
+        obj = NewBuiltinClassInstance<PlainObject>(cx, newKind);
+    }
+
+    if (!obj)
+        return nullptr;
+
+    if (newKind == SingletonObject) {
+        if (!JSObject::setSingleton(cx, obj))
+            return nullptr;
+    } else {
+        obj->setGroup(group);
+
+        if (PreliminaryObjectArray *preliminaryObjects = group->maybePreliminaryObjects())
+            preliminaryObjects->registerNewObject(obj);
+    }
+
+    return obj;
+}
+
+JSObject*
+js::NewObjectOperationWithTemplate(JSContext *cx, HandleObject templateObject)
+{
+    // This is an optimized version of NewObjectOperation for use when the
+    // object is not a singleton and has had its preliminary objects analyzed,
+    // with the template object a copy of the object to create.
+    MOZ_ASSERT(!templateObject->isSingleton());
+
+    NewObjectKind newKind = templateObject->group()->shouldPreTenure() ? TenuredObject : GenericObject;
+
+    if (templateObject->group()->maybeUnboxedLayout()) {
+        RootedObjectGroup group(cx, templateObject->group());
+        JSObject *obj = UnboxedPlainObject::create(cx, group, newKind);
+        if (!obj)
+            return nullptr;
+        return obj;
+    }
+
+    JSObject *obj = CopyInitializerObject(cx, templateObject.as<PlainObject>(), newKind);
+    if (!obj)
+        return nullptr;
+
+    obj->setGroup(templateObject->group());
+    return obj;
+}
+
 void
 js::ReportUninitializedLexical(JSContext *cx, HandlePropertyName name)
 {
     JSAutoByteString printable;
     if (AtomToPrintableString(cx, name, &printable)) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_UNINITIALIZED_LEXICAL,
                              printable.ptr());
     }
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -387,16 +387,23 @@ EnterWithOperation(JSContext *cx, Abstra
 bool
 InitGetterSetterOperation(JSContext *cx, jsbytecode *pc, HandleObject obj, HandleValue idval,
                           HandleObject val);
 
 bool
 SpreadCallOperation(JSContext *cx, HandleScript script, jsbytecode *pc, HandleValue thisv,
                     HandleValue callee, HandleValue arr, MutableHandleValue res);
 
+JSObject *
+NewObjectOperation(JSContext *cx, HandleScript script, jsbytecode *pc,
+                   NewObjectKind newKind = GenericObject);
+
+JSObject *
+NewObjectOperationWithTemplate(JSContext *cx, HandleObject templateObject);
+
 inline bool
 SetConstOperation(JSContext *cx, HandleObject varobj, HandlePropertyName name, HandleValue rval)
 {
     return DefineProperty(cx, varobj, name, rval, nullptr, nullptr,
                           JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
 }
 
 void
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -39,16 +39,17 @@ ObjectGroup::ObjectGroup(const Class *cl
     setGeneration(zone()->types.generation);
 }
 
 void
 ObjectGroup::finalize(FreeOp *fop)
 {
     fop->delete_(newScriptDontCheckGeneration());
     fop->delete_(maybeUnboxedLayoutDontCheckGeneration());
+    fop->delete_(maybePreliminaryObjectsDontCheckGeneration());
 }
 
 void
 ObjectGroup::setProtoUnchecked(TaggedProto proto)
 {
     proto_ = proto.raw();
     MOZ_ASSERT_IF(proto_ && proto_->isNative(), proto_->isDelegate());
 }
@@ -73,22 +74,30 @@ ObjectGroup::sizeOfExcludingThis(mozilla
 
 void
 ObjectGroup::setAddendum(AddendumKind kind, void *addendum, bool writeBarrier /* = true */)
 {
     MOZ_ASSERT(!needsSweep());
     MOZ_ASSERT(kind <= (OBJECT_FLAG_ADDENDUM_MASK >> OBJECT_FLAG_ADDENDUM_SHIFT));
 
     if (writeBarrier) {
-        // Manually trigger barriers if we are clearing a TypeNewScript. Other
-        // kinds of addendums are immutable.
-        if (newScript())
+        // Manually trigger barriers if we are clearing new script or
+        // preliminary object information. Other addendums are immutable.
+        switch (addendumKind()) {
+          case Addendum_PreliminaryObjects:
+            PreliminaryObjectArrayWithTemplate::writeBarrierPre(maybePreliminaryObjects());
+            break;
+          case Addendum_NewScript:
             TypeNewScript::writeBarrierPre(newScript());
-        else
-            MOZ_ASSERT(addendumKind() == Addendum_None || addendumKind() == kind);
+            break;
+          case Addendum_None:
+            break;
+          default:
+            MOZ_ASSERT(addendumKind() == kind);
+        }
     }
 
     flags_ &= ~OBJECT_FLAG_ADDENDUM_MASK;
     flags_ |= kind << OBJECT_FLAG_ADDENDUM_SHIFT;
     addendum_ = addendum;
 }
 
 /* static */ bool
@@ -1134,22 +1143,27 @@ ObjectGroup::allocationSiteGroup(JSConte
 
     Rooted<TaggedProto> tagged(cx, TaggedProto(proto));
     ObjectGroup *res = ObjectGroupCompartment::makeGroup(cx, GetClassForProtoKey(kind), tagged,
                                                          OBJECT_FLAG_FROM_ALLOCATION_SITE);
     if (!res)
         return nullptr;
 
     if (JSOp(*pc) == JSOP_NEWOBJECT) {
-        // This object is always constructed the same way and will not be
-        // observed by other code before all properties have been added. Mark
-        // all the properties as definite properties of the object.
-        JSObject *baseobj = script->getObject(GET_UINT32_INDEX(pc));
-        if (!res->addDefiniteProperties(cx, baseobj->lastProperty()))
-            return nullptr;
+        // Keep track of the preliminary objects with this group, so we can try
+        // to use an unboxed layout for the object once some are allocated.
+        Shape *shape = script->getObject(pc)->lastProperty();
+        if (!shape->isEmptyShape()) {
+            PreliminaryObjectArrayWithTemplate *preliminaryObjects =
+                cx->new_<PreliminaryObjectArrayWithTemplate>(shape);
+            if (preliminaryObjects)
+                res->setPreliminaryObjects(preliminaryObjects);
+            else
+                cx->recoverFromOutOfMemory();
+        }
     }
 
     if (!table->add(p, key, res))
         return nullptr;
 
     return res;
 }
 
--- a/js/src/vm/ObjectGroup.h
+++ b/js/src/vm/ObjectGroup.h
@@ -14,16 +14,17 @@
 #include "gc/Barrier.h"
 #include "vm/TypeInference.h"
 
 namespace js {
 
 class TypeDescr;
 class UnboxedLayout;
 
+class PreliminaryObjectArrayWithTemplate;
 class TypeNewScript;
 class HeapTypeSet;
 class AutoClearTypeInferenceStateOnOOM;
 class CompilerConstraintList;
 
 // Information about an object prototype, which can be either a particular
 // object, null, or a lazily generated object. The latter is only used by
 // certain kinds of proxies.
@@ -209,16 +210,19 @@ class ObjectGroup : public gc::TenuredCe
         // When used by interpreted function, the addendum stores the
         // canonical JSFunction object.
         Addendum_InterpretedFunction,
 
         // When used by the 'new' group when constructing an interpreted
         // function, the addendum stores a TypeNewScript.
         Addendum_NewScript,
 
+        // For some plain objects, the addendum stores a PreliminaryObjectArrayWithTemplate.
+        Addendum_PreliminaryObjects,
+
         // When objects in this group have an unboxed representation, the
         // addendum stores an UnboxedLayout (which might have a TypeNewScript
         // as well, if the group is also constructed using 'new').
         Addendum_UnboxedLayout,
 
         // If this group is used by objects that have been converted from an
         // unboxed representation, the addendum points to the original unboxed
         // group.
@@ -279,16 +283,36 @@ class ObjectGroup : public gc::TenuredCe
         maybeSweep(nullptr);
         return newScriptDontCheckGeneration();
     }
 
     void setNewScript(TypeNewScript *newScript) {
         setAddendum(Addendum_NewScript, newScript);
     }
 
+    PreliminaryObjectArrayWithTemplate *maybePreliminaryObjects() {
+        maybeSweep(nullptr);
+        return maybePreliminaryObjectsDontCheckGeneration();
+    }
+
+    PreliminaryObjectArrayWithTemplate *maybePreliminaryObjectsDontCheckGeneration() {
+        if (addendumKind() == Addendum_PreliminaryObjects)
+            return reinterpret_cast<PreliminaryObjectArrayWithTemplate *>(addendum_);
+        return nullptr;
+    }
+
+    void setPreliminaryObjects(PreliminaryObjectArrayWithTemplate *preliminaryObjects) {
+        setAddendum(Addendum_PreliminaryObjects, preliminaryObjects);
+    }
+
+    void detachPreliminaryObjects() {
+        MOZ_ASSERT(maybePreliminaryObjects());
+        setAddendum(Addendum_None, nullptr);
+    }
+
     UnboxedLayout *maybeUnboxedLayout() {
         maybeSweep(nullptr);
         return maybeUnboxedLayoutDontCheckGeneration();
     }
 
     UnboxedLayout &unboxedLayoutDontCheckGeneration() const {
         MOZ_ASSERT(addendumKind() == Addendum_UnboxedLayout);
         return *maybeUnboxedLayoutDontCheckGeneration();
--- a/js/src/vm/TypeInference-inl.h
+++ b/js/src/vm/TypeInference-inl.h
@@ -995,31 +995,16 @@ TypeSet::getObjectClass(unsigned i) cons
     if (JSObject *object = getSingleton(i))
         return object->getClass();
     if (ObjectGroup *group = getGroup(i))
         return group->clasp();
     return nullptr;
 }
 
 /////////////////////////////////////////////////////////////////////
-// TypeNewScript
-/////////////////////////////////////////////////////////////////////
-
-inline void
-TypeNewScript::writeBarrierPre(TypeNewScript *newScript)
-{
-    if (!newScript->function()->runtimeFromAnyThread()->needsIncrementalBarrier())
-        return;
-
-    JS::Zone *zone = newScript->function()->zoneFromAnyThread();
-    if (zone->needsIncrementalBarrier())
-        newScript->trace(zone->barrierTracer());
-}
-
-/////////////////////////////////////////////////////////////////////
 // ObjectGroup
 /////////////////////////////////////////////////////////////////////
 
 inline uint32_t
 ObjectGroup::basePropertyCount()
 {
     return (flags() & OBJECT_FLAG_PROPERTY_COUNT_MASK) >> OBJECT_FLAG_PROPERTY_COUNT_SHIFT;
 }
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -3255,22 +3255,123 @@ PreliminaryObjectArray::sweep()
     // destroyed.
     for (size_t i = 0; i < COUNT; i++) {
         JSObject **ptr = &objects[i];
         if (*ptr && IsObjectAboutToBeFinalized(ptr))
             *ptr = nullptr;
     }
 }
 
+void
+PreliminaryObjectArrayWithTemplate::trace(JSTracer *trc)
+{
+    MarkShape(trc, &shape_, "PreliminaryObjectArrayWithTemplate_shape");
+}
+
+/* static */ void
+PreliminaryObjectArrayWithTemplate::writeBarrierPre(PreliminaryObjectArrayWithTemplate *objects)
+{
+    if (!objects->shape()->runtimeFromAnyThread()->needsIncrementalBarrier())
+        return;
+
+    JS::Zone *zone = objects->shape()->zoneFromAnyThread();
+    if (zone->needsIncrementalBarrier())
+        objects->trace(zone->barrierTracer());
+}
+
+// Return whether shape consists entirely of plain data properties.
+static bool
+OnlyHasDataProperties(Shape *shape)
+{
+    MOZ_ASSERT(!shape->inDictionary());
+
+    while (!shape->isEmptyShape()) {
+        if (!shape->isDataDescriptor() ||
+            !shape->configurable() ||
+            !shape->enumerable() ||
+            !shape->writable() ||
+            !shape->hasSlot())
+        {
+            return false;
+        }
+        shape = shape->previous();
+    }
+
+    return true;
+}
+
+// Find the most recent common ancestor of two shapes, or an empty shape if
+// the two shapes have no common ancestor.
+static Shape *
+CommonPrefix(Shape *first, Shape *second)
+{
+    MOZ_ASSERT(OnlyHasDataProperties(first));
+    MOZ_ASSERT(OnlyHasDataProperties(second));
+
+    while (first->slotSpan() > second->slotSpan())
+        first = first->previous();
+    while (second->slotSpan() > first->slotSpan())
+        second = second->previous();
+
+    while (first != second && !first->isEmptyShape()) {
+        first = first->previous();
+        second = second->previous();
+    }
+
+    return first;
+}
+
+void
+PreliminaryObjectArrayWithTemplate::maybeAnalyze(JSContext *cx, ObjectGroup *group, bool force)
+{
+    // Don't perform the analyses until sufficient preliminary objects have
+    // been allocated.
+    if (!force && !full())
+        return;
+
+    AutoEnterAnalysis enter(cx);
+
+    ScopedJSDeletePtr<PreliminaryObjectArrayWithTemplate> preliminaryObjects(this);
+    group->detachPreliminaryObjects();
+
+    MOZ_ASSERT(shape()->slotSpan() != 0);
+    MOZ_ASSERT(OnlyHasDataProperties(shape()));
+
+    // Make sure all the preliminary objects reflect the properties originally
+    // in the template object.
+    for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+        JSObject *objBase = preliminaryObjects->get(i);
+        if (!objBase)
+            continue;
+        PlainObject *obj = &objBase->as<PlainObject>();
+
+        if (obj->inDictionaryMode() || !OnlyHasDataProperties(obj->lastProperty()))
+            return;
+
+        if (CommonPrefix(obj->lastProperty(), shape()) != shape())
+            return;
+    }
+
+    TryConvertToUnboxedLayout(cx, shape(), group, preliminaryObjects);
+    if (group->maybeUnboxedLayout())
+        return;
+
+    // We weren't able to use an unboxed layout, but since the preliminary
+    // still reflect the template object's properties, and all objects in the
+    // future will be created with those properties, the properties can be
+    // marked as definite for objects in the group.
+    group->addDefiniteProperties(cx, shape());
+}
+
 /////////////////////////////////////////////////////////////////////
 // TypeNewScript
 /////////////////////////////////////////////////////////////////////
 
-// Make a TypeNewScript for |group|, and set it up to hold the initial
-// PRELIMINARY_OBJECT_COUNT objects created with the group.
+// Make a TypeNewScript for |group|, and set it up to hold the preliminary
+// objects created with the group.
 /* static */ void
 TypeNewScript::make(JSContext *cx, ObjectGroup *group, JSFunction *fun)
 {
     MOZ_ASSERT(cx->zone()->types.activeAnalysis);
     MOZ_ASSERT(!group->newScript());
     MOZ_ASSERT(!group->maybeUnboxedLayout());
 
     if (group->unknownProperties())
@@ -3335,58 +3436,16 @@ TypeNewScript::registerNewObject(PlainOb
     // New script objects must have the maximum number of fixed slots, so that
     // we can adjust their shape later to match the number of fixed slots used
     // by the template object we eventually create.
     MOZ_ASSERT(res->numFixedSlots() == NativeObject::MAX_FIXED_SLOTS);
 
     preliminaryObjects->registerNewObject(res);
 }
 
-// Return whether shape consists entirely of plain data properties.
-static bool
-OnlyHasDataProperties(Shape *shape)
-{
-    MOZ_ASSERT(!shape->inDictionary());
-
-    while (!shape->isEmptyShape()) {
-        if (!shape->isDataDescriptor() ||
-            !shape->configurable() ||
-            !shape->enumerable() ||
-            !shape->writable() ||
-            !shape->hasSlot())
-        {
-            return false;
-        }
-        shape = shape->previous();
-    }
-
-    return true;
-}
-
-// Find the most recent common ancestor of two shapes, or an empty shape if
-// the two shapes have no common ancestor.
-static Shape *
-CommonPrefix(Shape *first, Shape *second)
-{
-    MOZ_ASSERT(OnlyHasDataProperties(first));
-    MOZ_ASSERT(OnlyHasDataProperties(second));
-
-    while (first->slotSpan() > second->slotSpan())
-        first = first->previous();
-    while (second->slotSpan() > first->slotSpan())
-        second = second->previous();
-
-    while (first != second && !first->isEmptyShape()) {
-        first = first->previous();
-        second = second->previous();
-    }
-
-    return first;
-}
-
 static bool
 ChangeObjectFixedSlotCount(JSContext *cx, PlainObject *obj, gc::AllocKind allocKind)
 {
     MOZ_ASSERT(OnlyHasDataProperties(obj->lastProperty()));
 
     Shape *newShape = ReshapeForParentAndAllocKind(cx, obj->lastProperty(),
                                                    obj->getTaggedProto(), obj->getParent(),
                                                    allocKind);
@@ -3755,16 +3814,27 @@ TypeNewScript::trace(JSTracer *trc)
 
     if (initializedShape_)
         MarkShape(trc, &initializedShape_, "TypeNewScript_initializedShape");
 
     if (initializedGroup_)
         MarkObjectGroup(trc, &initializedGroup_, "TypeNewScript_initializedGroup");
 }
 
+/* static */ void
+TypeNewScript::writeBarrierPre(TypeNewScript *newScript)
+{
+    if (!newScript->function()->runtimeFromAnyThread()->needsIncrementalBarrier())
+        return;
+
+    JS::Zone *zone = newScript->function()->zoneFromAnyThread();
+    if (zone->needsIncrementalBarrier())
+        newScript->trace(zone->barrierTracer());
+}
+
 void
 TypeNewScript::sweep()
 {
     if (preliminaryObjects)
         preliminaryObjects->sweep();
 }
 
 /////////////////////////////////////////////////////////////////////
@@ -3913,16 +3983,19 @@ ObjectGroup::maybeSweep(AutoClearTypeInf
     MOZ_ASSERT(!zone()->runtimeFromMainThread()->isHeapMinorCollecting());
 
     Maybe<AutoClearTypeInferenceStateOnOOM> fallbackOOM;
     EnsureHasAutoClearTypeInferenceStateOnOOM(oom, zone(), fallbackOOM);
 
     if (maybeUnboxedLayout() && unboxedLayout().newScript())
         unboxedLayout().newScript()->sweep();
 
+    if (maybePreliminaryObjects())
+        maybePreliminaryObjects()->sweep();
+
     if (newScript())
         newScript()->sweep();
 
     LifoAlloc &typeLifoAlloc = zone()->types.typeLifoAlloc;
 
     /*
      * Properties were allocated from the old arena, and need to be copied over
      * to the new one.
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -748,21 +748,21 @@ class TemporaryTypeSet : public TypeSet
 
 bool
 AddClearDefiniteGetterSetterForPrototypeChain(JSContext *cx, ObjectGroup *group, HandleId id);
 
 bool
 AddClearDefiniteFunctionUsesInScript(JSContext *cx, ObjectGroup *group,
                                      JSScript *script, JSScript *calleeScript);
 
-// For types where only a small number of objects have been allocated, this
-// structure keeps track of all objects with the type in existence. Once
-// COUNT objects have been allocated, this structure is cleared and the objects
-// are analyzed, to perform the new script properties analyses or determine if
-// an unboxed representation can be used.
+// For groups where only a small number of objects have been allocated, this
+// structure keeps track of all objects in the group. Once COUNT objects have
+// been allocated, this structure is cleared and the objects are analyzed, to
+// perform the new script properties analyses or determine if an unboxed
+// representation can be used.
 class PreliminaryObjectArray
 {
   public:
     static const uint32_t COUNT = 20;
 
   private:
     // All objects with the type which have been allocated. The pointers in
     // this array are weak.
@@ -779,16 +779,36 @@ class PreliminaryObjectArray
         MOZ_ASSERT(i < COUNT);
         return objects[i];
     }
 
     bool full() const;
     void sweep();
 };
 
+class PreliminaryObjectArrayWithTemplate : public PreliminaryObjectArray
+{
+    HeapPtrShape shape_;
+
+  public:
+    explicit PreliminaryObjectArrayWithTemplate(Shape *shape)
+      : shape_(shape)
+    {}
+
+    Shape *shape() {
+        return shape_;
+    }
+
+    void maybeAnalyze(JSContext *cx, ObjectGroup *group, bool force = false);
+
+    void trace(JSTracer *trc);
+
+    static void writeBarrierPre(PreliminaryObjectArrayWithTemplate *preliminaryObjects);
+};
+
 // New script properties analyses overview.
 //
 // When constructing objects using 'new' on a script, we attempt to determine
 // the properties which that object will eventually have. This is done via two
 // analyses. One of these, the definite properties analysis, is static, and the
 // other, the acquired properties analysis, is dynamic. As objects are
 // constructed using 'new' on some script to create objects of group G, our
 // analysis strategy is as follows:
@@ -876,17 +896,17 @@ class TypeNewScript
 
   public:
     TypeNewScript() { mozilla::PodZero(this); }
     ~TypeNewScript() {
         js_delete(preliminaryObjects);
         js_free(initializerList);
     }
 
-    static inline void writeBarrierPre(TypeNewScript *newScript);
+    static void writeBarrierPre(TypeNewScript *newScript);
 
     bool analyzed() const {
         return preliminaryObjects == nullptr;
     }
 
     PlainObject *templateObject() const {
         return templateObject_;
     }
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -568,21 +568,24 @@ js::TryConvertToUnboxedLayout(JSContext 
     size_t objectCount = 0;
     for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
         JSObject *obj = objects->get(i);
         if (!obj)
             continue;
 
         objectCount++;
 
-        // All preliminary objects must have been created with the largest
-        // allocation kind possible, which will allow their unboxed data to be
-        // filled in inline.
-        MOZ_ASSERT(gc::GetGCKindSlots(obj->asTenured().getAllocKind()) ==
-                   NativeObject::MAX_FIXED_SLOTS);
+        // All preliminary objects must have been created with enough space to
+        // fill in their unboxed data inline. This is ensured either by using
+        // the largest allocation kind (which limits the maximum size of an
+        // unboxed object), or by using an allocation kind that covers all
+        // properties in the template, as the space used by unboxed properties
+        // less than or equal to that used by boxed properties.
+        MOZ_ASSERT(gc::GetGCKindSlots(obj->asTenured().getAllocKind()) >=
+                   Min(NativeObject::MAX_FIXED_SLOTS, templateShape->slotSpan()));
 
         if (obj->as<PlainObject>().lastProperty() != templateShape ||
             obj->as<PlainObject>().hasDynamicElements())
         {
             // Only use an unboxed representation if all created objects match
             // the template shape exactly.
             return true;
         }
--- a/toolkit/devtools/server/tests/mochitest/test_memory_allocations_02.html
+++ b/toolkit/devtools/server/tests/mochitest/test_memory_allocations_02.html
@@ -38,17 +38,17 @@ window.onload = function() {
     ok(true, "Can stop recording allocations");
 
     // Assert that we have the 10 allocations in the `allocator` frame.
 
     var index = 0;
     var found = false;
 
     for (var count of response.counts) {
-      if (count === 10
+      if (count >= 10
           && response.frames[index]
           && response.frames[index].functionDisplayName == "allocator") {
         found = true;
         break;
       }
       index++;
     }