Bug 1116855 - Add default-disabled unboxed objects for use by interpreted constructors, r=jandem.
authorBrian Hackett <bhackett1024@gmail.com>
Tue, 27 Jan 2015 02:47:25 -0700
changeset 239324 7820fd1419980e83985329f060d6b8a50f2d6b8d
parent 239323 c14c7a032bf70ec45f33c35054c04624cef6d0df
child 239325 0767ae5882f9f54387ee846afbd06e5b51858525
push id497
push usermleibovic@mozilla.com
push dateWed, 28 Jan 2015 16:43:37 +0000
reviewersjandem
bugs1116855
milestone38.0a1
Bug 1116855 - Add default-disabled unboxed objects for use by interpreted constructors, r=jandem.
js/src/builtin/TypedObject.h
js/src/gc/Marking.cpp
js/src/gc/Nursery.cpp
js/src/jit-test/tests/basic/unboxed-object-clear-new-script.js
js/src/jit-test/tests/basic/unboxed-object-convert-to-native.js
js/src/jit/BaselineIC.cpp
js/src/jit/IonBuilder.cpp
js/src/jsapi.h
js/src/jsgc.h
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/moz.build
js/src/shell/js.cpp
js/src/vm/NativeObject.cpp
js/src/vm/NativeObject.h
js/src/vm/Shape.cpp
js/src/vm/Shape.h
js/src/vm/UnboxedObject.cpp
js/src/vm/UnboxedObject.h
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -722,30 +722,23 @@ class OutlineOpaqueTypedObject : public 
 
 // Class for a typed object whose data is allocated inline.
 class InlineTypedObject : public TypedObject
 {
     // Start of the inline data, which immediately follows the shape and type.
     uint8_t data_[1];
 
   public:
-    static const size_t MaximumSize =
-        sizeof(NativeObject) - sizeof(TypedObject) + NativeObject::MAX_FIXED_SLOTS * sizeof(Value);
+    static const size_t MaximumSize = JSObject::MAX_BYTE_SIZE - sizeof(TypedObject);
 
     static gc::AllocKind allocKindForTypeDescriptor(TypeDescr *descr) {
         size_t nbytes = descr->size();
         MOZ_ASSERT(nbytes <= MaximumSize);
 
-        if (nbytes <= sizeof(NativeObject) - sizeof(TypedObject))
-            return gc::FINALIZE_OBJECT0;
-        nbytes -= sizeof(NativeObject) - sizeof(TypedObject);
-
-        size_t dataSlots = AlignBytes(nbytes, sizeof(Value)) / sizeof(Value);
-        MOZ_ASSERT(nbytes <= dataSlots * sizeof(Value));
-        return gc::GetGCObjectKind(dataSlots);
+        return gc::GetGCObjectKindForBytes(nbytes + sizeof(TypedObject));
     }
 
     uint8_t *inlineTypedMem() const {
         static_assert(offsetof(InlineTypedObject, data_) == sizeof(JSObject),
                       "The data for an inline typed object must follow the shape and type.");
         return (uint8_t *) &data_;
     }
 
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -14,16 +14,17 @@
 #include "jit/IonCode.h"
 #include "js/SliceBudget.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/ArrayObject.h"
 #include "vm/ScopeObject.h"
 #include "vm/Shape.h"
 #include "vm/Symbol.h"
 #include "vm/TypedArrayObject.h"
+#include "vm/UnboxedObject.h"
 
 #include "jscompartmentinlines.h"
 #include "jsinferinlines.h"
 #include "jsobjinlines.h"
 
 #include "gc/Nursery-inl.h"
 #include "vm/String-inl.h"
 #include "vm/Symbol-inl.h"
@@ -1433,16 +1434,19 @@ ScanTypeObject(GCMarker *gcmarker, types
         PushMarkStack(gcmarker, type->proto().toObject());
 
     if (type->singleton() && !type->lazy())
         PushMarkStack(gcmarker, type->singleton());
 
     if (type->newScript())
         type->newScript()->trace(gcmarker);
 
+    if (type->maybeUnboxedLayout())
+        type->unboxedLayout().trace(gcmarker);
+
     if (TypeDescr *descr = type->maybeTypeDescr())
         PushMarkStack(gcmarker, descr);
 
     if (JSFunction *fun = type->maybeInterpretedFunction())
         PushMarkStack(gcmarker, fun);
 }
 
 static void
@@ -1459,16 +1463,19 @@ gc::MarkChildren(JSTracer *trc, types::T
         MarkObject(trc, &type->protoRaw(), "type_proto");
 
     if (type->singleton() && !type->lazy())
         MarkObject(trc, &type->singletonRaw(), "type_singleton");
 
     if (type->newScript())
         type->newScript()->trace(trc);
 
+    if (type->maybeUnboxedLayout())
+        type->unboxedLayout().trace(trc);
+
     if (JSObject *descr = type->maybeTypeDescr()) {
         MarkObjectUnbarriered(trc, &descr, "type_descr");
         type->setTypeDescr(&descr->as<TypeDescr>());
     }
 
     if (JSObject *fun = type->maybeInterpretedFunction()) {
         MarkObjectUnbarriered(trc, &fun, "type_function");
         type->setInterpretedFunction(&fun->as<JSFunction>());
@@ -1695,16 +1702,19 @@ GCMarker::processMarkStackTop(SliceBudge
     /*
      * The function uses explicit goto and implements the scanning of the
      * object directly. It allows to eliminate the tail recursion and
      * significantly improve the marking performance, see bug 641025.
      */
     HeapSlot *vp, *end;
     JSObject *obj;
 
+    const int32_t *unboxedTraceList;
+    uint8_t *unboxedMemory;
+
     uintptr_t addr = stack.pop();
     uintptr_t tag = addr & StackTagMask;
     addr &= ~StackTagMask;
 
     if (tag == ValueArrayTag) {
         JS_STATIC_ASSERT(ValueArrayTag == 0);
         MOZ_ASSERT(!(addr & CellMask));
         obj = reinterpret_cast<JSObject *>(addr);
@@ -1740,48 +1750,43 @@ GCMarker::processMarkStackTop(SliceBudge
                 goto scan_obj;
             }
         } else if (v.isSymbol()) {
             markAndScanSymbol(obj, v.toSymbol());
         }
     }
     return;
 
-  scan_typed_obj:
+  scan_unboxed:
     {
-        TypeDescr *descr = &obj->as<InlineOpaqueTypedObject>().typeDescr();
-        if (!descr->hasTraceList())
-            return;
-        const int32_t *list = descr->traceList();
-        uint8_t *memory = obj->as<InlineOpaqueTypedObject>().inlineTypedMem();
-        while (*list != -1) {
-            JSString *str = *reinterpret_cast<JSString **>(memory + *list);
+        while (*unboxedTraceList != -1) {
+            JSString *str = *reinterpret_cast<JSString **>(unboxedMemory + *unboxedTraceList);
             markAndScanString(obj, str);
-            list++;
+            unboxedTraceList++;
         }
-        list++;
-        while (*list != -1) {
-            JSObject *obj2 = *reinterpret_cast<JSObject **>(memory + *list);
+        unboxedTraceList++;
+        while (*unboxedTraceList != -1) {
+            JSObject *obj2 = *reinterpret_cast<JSObject **>(unboxedMemory + *unboxedTraceList);
             if (obj2 && markObject(obj, obj2))
                 pushObject(obj2);
-            list++;
+            unboxedTraceList++;
         }
-        list++;
-        while (*list != -1) {
-            const Value &v = *reinterpret_cast<Value *>(memory + *list);
+        unboxedTraceList++;
+        while (*unboxedTraceList != -1) {
+            const Value &v = *reinterpret_cast<Value *>(unboxedMemory + *unboxedTraceList);
             if (v.isString()) {
                 markAndScanString(obj, v.toString());
             } else if (v.isObject()) {
                 JSObject *obj2 = &v.toObject();
                 if (markObject(obj, obj2))
                     pushObject(obj2);
             } else if (v.isSymbol()) {
                 markAndScanSymbol(obj, v.toSymbol());
             }
-            list++;
+            unboxedTraceList++;
         }
         return;
     }
 
   scan_obj:
     {
         JS_COMPARTMENT_ASSERT(runtime(), obj);
 
@@ -1801,18 +1806,32 @@ GCMarker::processMarkStackTop(SliceBudge
         const Class *clasp = type->clasp();
         if (clasp->trace) {
             // Global objects all have the same trace hook. That hook is safe without barriers
             // if the global has no custom trace hook of its own, or has been moved to a different
             // compartment, and so can't have one.
             MOZ_ASSERT_IF(!(clasp->trace == JS_GlobalObjectTraceHook &&
                             (!obj->compartment()->options().getTrace() || !obj->isOwnGlobal())),
                           clasp->flags & JSCLASS_IMPLEMENTS_BARRIERS);
-            if (clasp->trace == InlineTypedObject::obj_trace)
-                goto scan_typed_obj;
+            if (clasp->trace == InlineTypedObject::obj_trace) {
+                TypeDescr *descr = &obj->as<InlineOpaqueTypedObject>().typeDescr();
+                if (!descr->hasTraceList())
+                    return;
+                unboxedTraceList = descr->traceList();
+                unboxedMemory = obj->as<InlineOpaqueTypedObject>().inlineTypedMem();
+                goto scan_unboxed;
+            }
+            if (clasp == &UnboxedPlainObject::class_) {
+                const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
+                unboxedTraceList = layout.traceList();
+                if (!unboxedTraceList)
+                    return;
+                unboxedMemory = obj->as<UnboxedPlainObject>().data();
+                goto scan_unboxed;
+            }
             clasp->trace(this, obj);
         }
 
         if (!shape->isNative())
             return;
 
         NativeObject *nobj = &obj->as<NativeObject>();
         unsigned nslots = nobj->slotSpan();
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -415,32 +415,38 @@ GetObjectAllocKindForCopy(const Nursery 
     if (obj->is<TypedArrayObject>() && !obj->as<TypedArrayObject>().buffer()) {
         size_t nbytes = obj->as<TypedArrayObject>().byteLength();
         return GetBackgroundAllocKind(TypedArrayObject::AllocKindForLazyBuffer(nbytes));
     }
 
     // Proxies have finalizers and are not nursery allocated.
     MOZ_ASSERT(!IsProxy(obj));
 
+    // Unboxed plain objects are sized according to the data they store.
+    if (obj->is<UnboxedPlainObject>()) {
+        size_t nbytes = obj->as<UnboxedPlainObject>().layout().size();
+        return GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + nbytes);
+    }
+
     // Inlined typed objects are followed by their data, so make sure we copy
     // it all over to the new object.
     if (obj->is<InlineTypedObject>()) {
         // Figure out the size of this object, from the prototype's TypeDescr.
         // The objects we are traversing here are all tenured, so we don't need
         // to check forwarding pointers.
         TypeDescr *descr = &obj->as<InlineTypedObject>().typeDescr();
         MOZ_ASSERT(!IsInsideNursery(descr));
         return InlineTypedObject::allocKindForTypeDescriptor(descr);
     }
 
     // Outline typed objects use the minimum allocation kind.
     if (obj->is<OutlineTypedObject>())
         return FINALIZE_OBJECT0;
 
-    // The only non-native objects in existence are proxies and typed objects.
+    // All nursery allocatable non-native objects are handled above.
     MOZ_ASSERT(obj->isNative());
 
     AllocKind kind = GetGCObjectFixedSlotsKind(obj->as<NativeObject>().numFixedSlots());
     MOZ_ASSERT(!IsBackgroundFinalized(kind));
     if (!CanBeFinalizedInBackground(kind, obj->getClass()))
         return kind;
     return GetBackgroundAllocKind(kind);
 }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/unboxed-object-clear-new-script.js
@@ -0,0 +1,49 @@
+
+function Foo(a, b) {
+    this.a = a;
+    this.b = b;
+}
+
+function invalidate_foo() {
+    var a = [];
+    var counter = 0;
+    for (var i = 0; i < 50; i++)
+        a.push(new Foo(i, i + 1));
+    Object.defineProperty(Foo.prototype, "a", {configurable: true, set: function() { counter++; }});
+    for (var i = 0; i < 50; i++)
+        a.push(new Foo(i, i + 1));
+    delete Foo.prototype.a;
+    var total = 0;
+    for (var i = 0; i < a.length; i++) {
+        assertEq('a' in a[i], i < 50);
+        total += a[i].b;
+    }
+    assertEq(total, 2550);
+    assertEq(counter, 50);
+}
+invalidate_foo();
+
+function Bar(a, b, fn) {
+    this.a = a;
+    if (b == 30)
+        Object.defineProperty(Bar.prototype, "b", {configurable: true, set: fn});
+    this.b = b;
+}
+
+function invalidate_bar() {
+    var a = [];
+    var counter = 0;
+    function fn() { counter++; }
+    for (var i = 0; i < 50; i++)
+        a.push(new Bar(i, i + 1, fn));
+    delete Bar.prototype.b;
+    var total = 0;
+    for (var i = 0; i < a.length; i++) {
+        assertEq('a' in a[i], true);
+        assertEq('b' in a[i], i < 29);
+        total += a[i].a;
+    }
+    assertEq(total, 1225);
+    assertEq(counter, 21);
+}
+invalidate_bar();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/unboxed-object-convert-to-native.js
@@ -0,0 +1,47 @@
+
+// Test various ways of converting an unboxed object to native.
+
+function Foo(a, b) {
+    this.a = a;
+    this.b = b;
+}
+
+var proxyObj = {
+  get: function(recipient, name) {
+    return recipient[name] + 2;
+  }
+};
+
+function f() {
+    var a = [];
+    for (var i = 0; i < 50; i++)
+        a.push(new Foo(i, i + 1));
+
+    var prop = "a";
+
+    i = 0;
+    for (; i < 5; i++)
+        a[i].c = i;
+    for (; i < 10; i++)
+        Object.defineProperty(a[i], 'c', {value: i});
+    for (; i < 15; i++)
+        a[i] = new Proxy(a[i], proxyObj);
+    for (; i < 20; i++)
+        a[i].a = 3.5;
+    for (; i < 25; i++)
+        delete a[i].b;
+    for (; i < 30; i++)
+        a[prop] = 4;
+
+    var total = 0;
+    for (i = 0; i < a.length; i++) {
+        if ('a' in a[i])
+            total += a[i].a;
+        if ('b' in a[i])
+            total += a[i].b;
+        if ('c' in a[i])
+            total += a[i].c;
+    }
+    assertEq(total, 2382.5);
+}
+f();
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -9080,33 +9080,37 @@ TryAttachCallStub(JSContext *cx, ICCall_
         // information, for use during Ion compilation.
         if (IsIonEnabled(cx))
             types::EnsureTrackPropertyTypes(cx, fun, NameToId(cx->names().prototype));
 
         // Remember the template object associated with any script being called
         // as a constructor, for later use during Ion compilation.
         RootedPlainObject templateObject(cx);
         if (constructing) {
-            templateObject = CreateThisForFunction(cx, fun, MaybeSingletonObject);
-            if (!templateObject)
+            JSObject *thisObject = CreateThisForFunction(cx, fun, MaybeSingletonObject);
+            if (!thisObject)
                 return false;
 
-            // If we are calling a constructor for which the new script
-            // properties analysis has not been performed yet, don't attach a
-            // stub. After the analysis is performed, CreateThisForFunction may
-            // start returning objects with a different type, and the Ion
-            // compiler might get confused.
-            if (templateObject->type()->newScript() &&
-                !templateObject->type()->newScript()->analyzed())
-            {
-                // Clear the object just created from the preliminary objects
-                // on the TypeNewScript, as it will not be used or filled in by
-                // running code.
-                templateObject->type()->newScript()->unregisterNewObject(templateObject);
-                return true;
+            if (thisObject->is<PlainObject>()) {
+                templateObject = &thisObject->as<PlainObject>();
+
+                // If we are calling a constructor for which the new script
+                // properties analysis has not been performed yet, don't attach a
+                // stub. After the analysis is performed, CreateThisForFunction may
+                // start returning objects with a different type, and the Ion
+                // compiler might get confused.
+                if (templateObject->type()->newScript() &&
+                    !templateObject->type()->newScript()->analyzed())
+                {
+                    // Clear the object just created from the preliminary objects
+                    // on the TypeNewScript, as it will not be used or filled in by
+                    // running code.
+                    templateObject->type()->newScript()->unregisterNewObject(templateObject);
+                    return true;
+                }
             }
         }
 
         JitSpew(JitSpew_BaselineIC,
                 "  Generating Call_Scripted stub (fun=%p, %s:%d, cons=%s, spread=%s)",
                 fun.get(), fun->nonLazyScript()->filename(), fun->nonLazyScript()->lineno(),
                 constructing ? "yes" : "no", isSpread ? "yes" : "no");
         ICCallScriptedCompiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -5376,16 +5376,20 @@ IonBuilder::createThisScriptedSingleton(
         return nullptr;
 
     JSObject *templateObject = inspector->getTemplateObject(pc);
     if (!templateObject || !templateObject->is<PlainObject>())
         return nullptr;
     if (!templateObject->hasTenuredProto() || templateObject->getProto() != proto)
         return nullptr;
 
+    types::TypeObjectKey *templateObjectType = types::TypeObjectKey::get(templateObject->type());
+    if (templateObjectType->hasFlags(constraints(), types::OBJECT_FLAG_NEW_SCRIPT_CLEARED))
+        return nullptr;
+
     types::StackTypeSet *thisTypes = types::TypeScript::ThisTypes(target->nonLazyScript());
     if (!thisTypes || !thisTypes->hasType(types::Type::ObjectType(templateObject)))
         return nullptr;
 
     // Generate an inline path to create a new |this| object with
     // the given singleton prototype.
     MConstant *templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     MCreateThisWithTemplate *createThis =
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1492,16 +1492,17 @@ namespace JS {
 
 class JS_PUBLIC_API(RuntimeOptions) {
   public:
     RuntimeOptions()
       : baseline_(false),
         ion_(false),
         asmJS_(false),
         nativeRegExp_(false),
+        unboxedObjects_(false),
         werror_(false),
         strictMode_(false),
         extraWarnings_(false),
         varObjFix_(false)
     {
     }
 
     bool baseline() const { return baseline_; }
@@ -1535,16 +1536,22 @@ class JS_PUBLIC_API(RuntimeOptions) {
     }
 
     bool nativeRegExp() const { return nativeRegExp_; }
     RuntimeOptions &setNativeRegExp(bool flag) {
         nativeRegExp_ = flag;
         return *this;
     }
 
+    bool unboxedObjects() const { return unboxedObjects_; }
+    RuntimeOptions &setUnboxedObjects(bool flag) {
+        unboxedObjects_ = flag;
+        return *this;
+    }
+
     bool werror() const { return werror_; }
     RuntimeOptions &setWerror(bool flag) {
         werror_ = flag;
         return *this;
     }
     RuntimeOptions &toggleWerror() {
         werror_ = !werror_;
         return *this;
@@ -1580,16 +1587,17 @@ class JS_PUBLIC_API(RuntimeOptions) {
         return *this;
     }
 
   private:
     bool baseline_ : 1;
     bool ion_ : 1;
     bool asmJS_ : 1;
     bool nativeRegExp_ : 1;
+    bool unboxedObjects_ : 1;
     bool werror_ : 1;
     bool strictMode_ : 1;
     bool extraWarnings_ : 1;
     bool varObjFix_ : 1;
 };
 
 JS_PUBLIC_API(RuntimeOptions &)
 RuntimeOptionsRef(JSRuntime *rt);
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -187,16 +187,32 @@ GetGCArrayKind(size_t numSlots)
 
 static inline AllocKind
 GetGCObjectFixedSlotsKind(size_t numFixedSlots)
 {
     MOZ_ASSERT(numFixedSlots < SLOTS_TO_THING_KIND_LIMIT);
     return slotsToThingKind[numFixedSlots];
 }
 
+// Get the best kind to use when allocating an object that needs a specific
+// number of bytes.
+static inline AllocKind
+GetGCObjectKindForBytes(size_t nbytes)
+{
+    MOZ_ASSERT(nbytes <= JSObject::MAX_BYTE_SIZE);
+
+    if (nbytes <= sizeof(NativeObject))
+        return FINALIZE_OBJECT0;
+    nbytes -= sizeof(NativeObject);
+
+    size_t dataSlots = AlignBytes(nbytes, sizeof(Value)) / sizeof(Value);
+    MOZ_ASSERT(nbytes <= dataSlots * sizeof(Value));
+    return GetGCObjectKind(dataSlots);
+}
+
 static inline AllocKind
 GetBackgroundAllocKind(AllocKind kind)
 {
     MOZ_ASSERT(!IsBackgroundFinalized(kind));
     MOZ_ASSERT(kind <= FINALIZE_OBJECT_LAST);
     return (AllocKind) (kind + 1);
 }
 
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -25,16 +25,17 @@
 #include "jit/CompileInfo.h"
 #include "jit/Ion.h"
 #include "jit/IonAnalysis.h"
 #include "jit/JitCompartment.h"
 #include "js/MemoryMetrics.h"
 #include "vm/HelperThreads.h"
 #include "vm/Opcodes.h"
 #include "vm/Shape.h"
+#include "vm/UnboxedObject.h"
 
 #include "jsatominlines.h"
 #include "jsgcinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 #include "vm/NativeObject-inl.h"
 
@@ -3269,71 +3270,108 @@ TypeObject::markUnknown(ExclusiveContext
         Property *prop = getProperty(i);
         if (prop) {
             prop->types.addType(cx, Type::UnknownType());
             prop->types.setNonDataProperty(cx);
         }
     }
 }
 
+TypeNewScript *
+TypeObject::anyNewScript()
+{
+    if (newScript())
+        return newScript();
+    if (maybeUnboxedLayout())
+        return unboxedLayout().newScript();
+    return nullptr;
+}
+
+void
+TypeObject::detachNewScript(bool writeBarrier)
+{
+    // Clear the TypeNewScript from this TypeObject and, if it has been
+    // analyzed, remove it from the newTypeObjects table so that it will not be
+    // produced by calling 'new' on the associated function anymore.
+    // The TypeNewScript is not actually destroyed.
+    TypeNewScript *newScript = anyNewScript();
+    MOZ_ASSERT(newScript);
+
+    if (newScript->analyzed()) {
+        NewTypeObjectTable &newTypeObjects = newScript->function()->compartment()->newTypeObjects;
+        NewTypeObjectTable::Ptr p =
+            newTypeObjects.lookup(NewTypeObjectTable::Lookup(nullptr, proto(), newScript->function()));
+        MOZ_ASSERT(p->object == this);
+
+        newTypeObjects.remove(p);
+    }
+
+    if (this->newScript())
+        setAddendum(Addendum_None, nullptr, writeBarrier);
+    else
+        unboxedLayout().setNewScript(nullptr, writeBarrier);
+}
+
 void
 TypeObject::maybeClearNewScriptOnOOM()
 {
     MOZ_ASSERT(zone()->isGCSweepingOrCompacting());
 
     if (!isMarked())
         return;
 
-    if (!newScript())
+    TypeNewScript *newScript = anyNewScript();
+    if (!newScript)
         return;
 
-    for (unsigned i = 0; i < getPropertyCount(); i++) {
-        Property *prop = getProperty(i);
-        if (!prop)
-            continue;
-        if (prop->types.definiteProperty())
-            prop->types.setNonDataPropertyIgnoringConstraints();
-    }
-
-    // This method is called during GC sweeping, so there is no write barrier
-    // that needs to be triggered.
-    js_delete(newScript());
-    addendum_ = nullptr;
+    addFlags(OBJECT_FLAG_NEW_SCRIPT_CLEARED);
+
+    // This method is called during GC sweeping, so don't trigger pre barriers.
+    detachNewScript(/* writeBarrier = */ false);
+
+    js_delete(newScript);
 }
 
 void
 TypeObject::clearNewScript(ExclusiveContext *cx)
 {
-    if (!newScript())
+    TypeNewScript *newScript = anyNewScript();
+    if (!newScript)
         return;
 
-    TypeNewScript *newScript = this->newScript();
-    setNewScript(nullptr);
-
     AutoEnterAnalysis enter(cx);
 
-    /*
-     * Any definite properties we added due to analysis of the new script when
-     * the type object was created are now invalid: objects with the same type
-     * can be created by using 'new' on a different script or through some
-     * other mechanism (e.g. Object.create). Rather than clear out the definite
-     * bits on the object's properties, just mark such properties as having
-     * been deleted/reconfigured, which will have the same effect on JITs
-     * wanting to use the definite bits to optimize property accesses.
-     */
-    for (unsigned i = 0; i < getPropertyCount(); i++) {
-        Property *prop = getProperty(i);
-        if (!prop)
-            continue;
-        if (prop->types.definiteProperty())
-            prop->types.setNonDataProperty(cx);
-    }
+    // Invalidate any Ion code constructing objects of this type.
+    setFlags(cx, OBJECT_FLAG_NEW_SCRIPT_CLEARED);
+
+    // Mark the constructing function as having its 'new' script cleared, so we
+    // will not try to construct another one later.
+    if (!newScript->function()->setNewScriptCleared(cx))
+        cx->recoverFromOutOfMemory();
+
+    detachNewScript(/* writeBarrier = */ true);
 
     if (cx->isJSContext()) {
-        newScript->rollbackPartiallyInitializedObjects(cx->asJSContext(), this);
+        bool found = newScript->rollbackPartiallyInitializedObjects(cx->asJSContext(), this);
+
+        // If we managed to rollback any partially initialized objects, then
+        // any definite properties we added due to analysis of the new script
+        // are now invalid, so remove them. If there weren't any partially
+        // initialized objects then we don't need to change type information,
+        // as no more objects of this type will be created and the 'new' script
+        // analysis was still valid when older objects were created.
+        if (found) {
+            for (unsigned i = 0; i < getPropertyCount(); i++) {
+                Property *prop = getProperty(i);
+                if (!prop)
+                    continue;
+                if (prop->types.definiteProperty())
+                    prop->types.setNonDataProperty(cx);
+            }
+        }
     } else {
         // Threads with an ExclusiveContext are not allowed to run scripts.
         MOZ_ASSERT(!cx->perThreadData->runtimeIfOnOwnerThread() ||
                    !cx->perThreadData->runtimeIfOnOwnerThread()->activation());
     }
 
     js_delete(newScript);
     markStateChange(cx);
@@ -3766,42 +3804,98 @@ JSFunction::setTypeForScriptedFunction(E
         fun->setType(type);
         type->setInterpretedFunction(fun);
     }
 
     return true;
 }
 
 /////////////////////////////////////////////////////////////////////
+// PreliminaryObjectArray
+/////////////////////////////////////////////////////////////////////
+
+void
+PreliminaryObjectArray::registerNewObject(JSObject *res)
+{
+    // The preliminary object pointers are weak, and won't be swept properly
+    // during nursery collections, so the preliminary objects need to be
+    // initially tenured.
+    MOZ_ASSERT(!IsInsideNursery(res));
+
+    for (size_t i = 0; i < COUNT; i++) {
+        if (!objects[i]) {
+            objects[i] = res;
+            return;
+        }
+    }
+
+    MOZ_CRASH("There should be room for registering the new object");
+}
+
+void
+PreliminaryObjectArray::unregisterNewObject(JSObject *res)
+{
+    for (size_t i = 0; i < COUNT; i++) {
+        if (objects[i] == res) {
+            objects[i] = nullptr;
+            return;
+        }
+    }
+
+    MOZ_CRASH("The object should be one of the preliminary objects");
+}
+
+bool
+PreliminaryObjectArray::full() const
+{
+    for (size_t i = 0; i < COUNT; i++) {
+        if (!objects[i])
+            return false;
+    }
+    return true;
+}
+
+void
+PreliminaryObjectArray::sweep()
+{
+    // All objects in the array are weak, so clear any that are about to be
+    // destroyed.
+    for (size_t i = 0; i < COUNT; i++) {
+        JSObject **ptr = &objects[i];
+        if (*ptr && IsObjectAboutToBeFinalized(ptr))
+            *ptr = nullptr;
+    }
+}
+
+/////////////////////////////////////////////////////////////////////
 // TypeNewScript
 /////////////////////////////////////////////////////////////////////
 
 // Make a TypeNewScript for |type|, and set it up to hold the initial
 // PRELIMINARY_OBJECT_COUNT objects created with the type.
 /* static */ void
 TypeNewScript::make(JSContext *cx, TypeObject *type, JSFunction *fun)
 {
     MOZ_ASSERT(cx->zone()->types.activeAnalysis);
     MOZ_ASSERT(!type->newScript());
+    MOZ_ASSERT(!type->maybeUnboxedLayout());
 
     if (type->unknownProperties())
         return;
 
     ScopedJSDeletePtr<TypeNewScript> newScript(cx->new_<TypeNewScript>());
     if (!newScript)
         return;
 
-    newScript->fun = fun;
-
-    PlainObject **preliminaryObjects =
-        type->zone()->pod_calloc<PlainObject *>(PRELIMINARY_OBJECT_COUNT);
-    if (!preliminaryObjects)
+    newScript->function_ = fun;
+
+    newScript->preliminaryObjects = type->zone()->new_<PreliminaryObjectArray>();
+    if (!newScript->preliminaryObjects)
         return;
 
-    newScript->preliminaryObjects = preliminaryObjects;
     type->setNewScript(newScript.forget());
 
     gc::TraceTypeNewScript(type);
 }
 
 size_t
 TypeNewScript::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
 {
@@ -3811,50 +3905,29 @@ TypeNewScript::sizeOfIncludingThis(mozil
     return n;
 }
 
 void
 TypeNewScript::registerNewObject(PlainObject *res)
 {
     MOZ_ASSERT(!analyzed());
 
-    // The preliminary object pointers are weak, and won't be swept properly
-    // during nursery collections, so the preliminary objects need to be
-    // initially tenured.
-    MOZ_ASSERT(!IsInsideNursery(res));
-
     // 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);
 
-    for (size_t i = 0; i < PRELIMINARY_OBJECT_COUNT; i++) {
-        if (!preliminaryObjects[i]) {
-            preliminaryObjects[i] = res;
-            return;
-        }
-    }
-
-    MOZ_CRASH("There should be room for registering the new object");
+    preliminaryObjects->registerNewObject(res);
 }
 
 void
 TypeNewScript::unregisterNewObject(PlainObject *res)
 {
     MOZ_ASSERT(!analyzed());
-
-    for (size_t i = 0; i < PRELIMINARY_OBJECT_COUNT; i++) {
-        if (preliminaryObjects[i] == res) {
-            preliminaryObjects[i] = nullptr;
-            return;
-        }
-    }
-
-    // The object should be one of the preliminary objects.
-    MOZ_CRASH();
+    preliminaryObjects->unregisterNewObject(res);
 }
 
 // Return whether shape consists entirely of plain data properties.
 static bool
 OnlyHasDataProperties(Shape *shape)
 {
     MOZ_ASSERT(!shape->inDictionary());
 
@@ -3945,38 +4018,35 @@ TypeNewScript::maybeAnalyze(JSContext *c
     if (regenerate)
         *regenerate = false;
 
     if (analyzed()) {
         // The analyses have already been performed.
         return true;
     }
 
-    if (!force) {
-        // Don't perform the analyses until sufficient preliminary objects have
-        // been allocated.
-        for (size_t i = 0; i < PRELIMINARY_OBJECT_COUNT; i++) {
-            if (!preliminaryObjects[i])
-                return true;
-        }
-    }
+    // Don't perform the analyses until sufficient preliminary objects have
+    // been allocated.
+    if (!force && !preliminaryObjects->full())
+        return true;
 
     AutoEnterAnalysis enter(cx);
 
     // Any failures after this point will clear out this TypeNewScript.
     DestroyTypeNewScript destroyNewScript(cx, type);
 
     // Compute the greatest common shape prefix and the largest slot span of
     // the preliminary objects.
     Shape *prefixShape = nullptr;
     size_t maxSlotSpan = 0;
-    for (size_t i = 0; i < PRELIMINARY_OBJECT_COUNT; i++) {
-        PlainObject *obj = preliminaryObjects[i];
-        if (!obj)
+    for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+        JSObject *objBase = preliminaryObjects->get(i);
+        if (!objBase)
             continue;
+        PlainObject *obj = &objBase->as<PlainObject>();
 
         // For now, we require all preliminary objects to have only simple
         // lineages of plain data properties.
         Shape *shape = obj->lastProperty();
         if (shape->inDictionary() || !OnlyHasDataProperties(shape))
             return true;
 
         maxSlotSpan = Max<size_t>(maxSlotSpan, obj->slotSpan());
@@ -4002,20 +4072,21 @@ TypeNewScript::maybeAnalyze(JSContext *c
         // preliminary objects that have already been constructed. Optimizing
         // definite property accesses requires both that the property is
         // definitely in a particular slot and that the object has a specific
         // number of fixed slots. So, adjust the shape and slot layout of all
         // the preliminary objects so that their structure matches that of the
         // template object. Also recompute the prefix shape, as it reflects the
         // old number of fixed slots.
         Shape *newPrefixShape = nullptr;
-        for (size_t i = 0; i < PRELIMINARY_OBJECT_COUNT; i++) {
-            PlainObject *obj = preliminaryObjects[i];
-            if (!obj)
+        for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+            JSObject *objBase = preliminaryObjects->get(i);
+            if (!objBase)
                 continue;
+            PlainObject *obj = &objBase->as<PlainObject>();
             if (!ChangeObjectFixedSlotCount(cx, obj, kind))
                 return false;
             if (newPrefixShape) {
                 MOZ_ASSERT(CommonPrefix(obj->lastProperty(), newPrefixShape) == newPrefixShape);
             } else {
                 newPrefixShape = obj->lastProperty();
                 while (newPrefixShape->slotSpan() > prefixShape->slotSpan())
                     newPrefixShape = newPrefixShape->previous();
@@ -4027,17 +4098,17 @@ TypeNewScript::maybeAnalyze(JSContext *c
     RootedTypeObject typeRoot(cx, type);
     templateObject_ = NewObjectWithType<PlainObject>(cx, typeRoot, cx->global(), kind, MaybeSingletonObject);
     if (!templateObject_)
         return false;
 
     Vector<Initializer> initializerVector(cx);
 
     RootedPlainObject templateRoot(cx, templateObject());
-    if (!jit::AnalyzeNewScriptDefiniteProperties(cx, fun, type, templateRoot, &initializerVector))
+    if (!jit::AnalyzeNewScriptDefiniteProperties(cx, function(), type, templateRoot, &initializerVector))
         return false;
 
     if (!type->newScript())
         return true;
 
     MOZ_ASSERT(OnlyHasDataProperties(templateObject()->lastProperty()));
 
     if (templateObject()->slotSpan() != 0) {
@@ -4072,19 +4143,38 @@ TypeNewScript::maybeAnalyze(JSContext *c
             return false;
 
         initializerList = type->zone()->pod_calloc<Initializer>(initializerVector.length());
         if (!initializerList)
             return false;
         PodCopy(initializerList, initializerVector.begin(), initializerVector.length());
     }
 
-    js_free(preliminaryObjects);
+    // Try to use an unboxed representation for the type.
+    if (!TryConvertToUnboxedLayout(cx, templateObject()->lastProperty(), type, preliminaryObjects))
+        return false;
+
+    js_delete(preliminaryObjects);
     preliminaryObjects = nullptr;
 
+    if (type->maybeUnboxedLayout()) {
+        // An unboxed layout was constructed for the type, and this has already
+        // been hooked into it.
+        MOZ_ASSERT(type->unboxedLayout().newScript() == this);
+        destroyNewScript.type = nullptr;
+
+        // Clear out the template object. This is not used for TypeNewScripts
+        // with an unboxed layout, and additionally this template is now a
+        // mutant object with a non-native class and native shape, and must be
+        // collected by the next GC.
+        templateObject_ = nullptr;
+
+        return true;
+    }
+
     if (prefixShape->slotSpan() == templateObject()->slotSpan()) {
         // The definite properties analysis found exactly the properties that
         // are held in common by the preliminary objects. No further analysis
         // is needed.
         if (!type->addDefiniteProperties(cx, templateObject()->lastProperty()))
             return false;
 
         destroyNewScript.type = nullptr;
@@ -4107,21 +4197,21 @@ TypeNewScript::maybeAnalyze(JSContext *c
         return false;
 
     if (!initialType->addDefiniteProperties(cx, templateObject()->lastProperty()))
         return false;
     if (!type->addDefiniteProperties(cx, prefixShape))
         return false;
 
     NewTypeObjectTable &table = cx->compartment()->newTypeObjects;
-    NewTypeObjectTable::Lookup lookup(type->clasp(), type->proto(), fun);
+    NewTypeObjectTable::Lookup lookup(nullptr, type->proto(), function());
 
     MOZ_ASSERT(table.lookup(lookup)->object == type);
     table.remove(lookup);
-    table.putNew(lookup, NewTypeObjectEntry(initialType, fun));
+    table.putNew(lookup, NewTypeObjectEntry(initialType, function()));
 
     templateObject()->setType(initialType);
 
     // Transfer this TypeNewScript from the fully initialized type to the
     // partially initialized type.
     type->setNewScript(nullptr);
     initialType->setNewScript(this);
 
@@ -4130,46 +4220,54 @@ TypeNewScript::maybeAnalyze(JSContext *c
 
     destroyNewScript.type = nullptr;
 
     if (regenerate)
         *regenerate = true;
     return true;
 }
 
-void
+bool
 TypeNewScript::rollbackPartiallyInitializedObjects(JSContext *cx, TypeObject *type)
 {
     // If we cleared this new script while in the middle of initializing an
     // object, it will still have the new script's shape and reflect the no
     // longer correct state of the object once its initialization is completed.
     // We can't detect the possibility of this statically while remaining
     // robust, but the new script keeps track of where each property is
     // initialized so we can walk the stack and fix up any such objects.
+    // Return whether any objects were modified.
 
     if (!initializerList)
-        return;
-
-    RootedFunction function(cx, fun);
+        return false;
+
+    bool found = false;
+
+    RootedFunction function(cx, this->function());
     Vector<uint32_t, 32> pcOffsets(cx);
     for (ScriptFrameIter iter(cx); !iter.done(); ++iter) {
         pcOffsets.append(iter.script()->pcToOffset(iter.pc()));
 
-        // This frame has no this.
-        if (!iter.isConstructing() || iter.matchCallee(cx, function))
+        if (!iter.isConstructing() || !iter.matchCallee(cx, function))
             continue;
 
         Value thisv = iter.thisv(cx);
         if (!thisv.isObject() ||
             thisv.toObject().hasLazyType() ||
             thisv.toObject().type() != type)
         {
             continue;
         }
 
+        if (thisv.toObject().is<UnboxedPlainObject>() &&
+            !thisv.toObject().as<UnboxedPlainObject>().convertToNative(cx))
+        {
+            CrashAtUnhandlableOOM("rollbackPartiallyInitializedObjects");
+        }
+
         // Found a matching frame.
         RootedPlainObject obj(cx, &thisv.toObject().as<PlainObject>());
 
         // Whether all identified 'new' properties have been initialized.
         bool finished = false;
 
         // If not finished, number of properties that have been added.
         uint32_t numProperties = 0;
@@ -4212,48 +4310,45 @@ TypeNewScript::rollbackPartiallyInitiali
                 }
             } else {
                 MOZ_ASSERT(init->kind == Initializer::DONE);
                 finished = true;
                 break;
             }
         }
 
-        if (!finished)
+        if (!finished) {
             (void) NativeObject::rollbackProperties(cx, obj, numProperties);
-    }
+            found = true;
+        }
+    }
+
+    return found;
 }
 
 void
 TypeNewScript::trace(JSTracer *trc)
 {
-    MarkObject(trc, &fun, "TypeNewScript_function");
+    MarkObject(trc, &function_, "TypeNewScript_function");
 
     if (templateObject_)
         MarkObject(trc, &templateObject_, "TypeNewScript_templateObject");
 
     if (initializedShape_)
         MarkShape(trc, &initializedShape_, "TypeNewScript_initializedShape");
 
     if (initializedType_)
         MarkTypeObject(trc, &initializedType_, "TypeNewScript_initializedType");
 }
 
 void
 TypeNewScript::sweep()
 {
-    // preliminaryObjects only holds weak pointers, so clear any objects that
-    // are about to be destroyed.
-    if (preliminaryObjects) {
-        for (size_t i = 0; i < PRELIMINARY_OBJECT_COUNT; i++) {
-            PlainObject **ptr = &preliminaryObjects[i];
-            if (*ptr && IsObjectAboutToBeFinalized(ptr))
-                *ptr = nullptr;
-        }
-    }
+    if (preliminaryObjects)
+        preliminaryObjects->sweep();
 }
 
 /////////////////////////////////////////////////////////////////////
 // JSObject
 /////////////////////////////////////////////////////////////////////
 
 bool
 JSObject::shouldSplicePrototype(JSContext *cx)
@@ -4357,17 +4452,17 @@ NewTypeObjectEntry::hash(const Lookup &l
            PointerHasher<const Class *, 3>::hash(lookup.clasp) ^
            PointerHasher<JSObject *, 3>::hash(lookup.associated);
 }
 
 /* static */ inline bool
 NewTypeObjectEntry::match(const NewTypeObjectEntry &key, const Lookup &lookup)
 {
     return key.object->proto() == lookup.matchProto &&
-           key.object->clasp() == lookup.clasp &&
+           (!lookup.clasp || key.object->clasp() == lookup.clasp) &&
            key.associated == lookup.associated;
 }
 
 #ifdef DEBUG
 bool
 JSObject::hasNewType(const Class *clasp, TypeObject *type)
 {
     NewTypeObjectTable &table = compartment()->newTypeObjects;
@@ -4426,17 +4521,18 @@ class NewTypeObjectsSetRef : public Buff
         trc->setTracingLocation(&*prior);
         Mark(trc, &proto, "newTypeObjects set prototype");
         if (prior == proto)
             return;
 
         NewTypeObjectTable::Ptr p =
             set->lookup(NewTypeObjectTable::Lookup(clasp, TaggedProto(prior), TaggedProto(proto),
                                                    associated));
-        MOZ_ASSERT(p);  // newTypeObjects set must still contain original entry.
+        if (!p)
+            return;
 
         set->rekeyAs(NewTypeObjectTable::Lookup(clasp, TaggedProto(prior), TaggedProto(proto), associated),
                      NewTypeObjectTable::Lookup(clasp, TaggedProto(proto), associated), *p);
     }
 };
 
 static void
 TypeObjectTablePostBarrier(ExclusiveContext *cx, NewTypeObjectTable *table,
@@ -4460,52 +4556,71 @@ TypeObjectTablePostBarrier(ExclusiveCont
 
 TypeObject *
 ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto, JSObject *associated)
 {
     MOZ_ASSERT_IF(associated, proto.isObject());
     MOZ_ASSERT_IF(associated, associated->is<JSFunction>() || associated->is<TypeDescr>());
     MOZ_ASSERT_IF(proto.isObject(), isInsideCurrentCompartment(proto.toObject()));
 
+    // A null lookup clasp is used for 'new' type objects with an associated
+    // function. The type starts out as a plain object but might mutate into an
+    // unboxed plain object.
+    MOZ_ASSERT(!clasp == (associated && associated->is<JSFunction>()));
+
     NewTypeObjectTable &newTypeObjects = compartment()->newTypeObjects;
 
     if (!newTypeObjects.initialized() && !newTypeObjects.init())
         return nullptr;
 
-    // Canonicalize new functions to use the original one associated with its script.
     if (associated && associated->is<JSFunction>()) {
+        MOZ_ASSERT(!clasp);
+
+        // Canonicalize new functions to use the original one associated with its script.
         JSFunction *fun = &associated->as<JSFunction>();
         if (fun->hasScript())
             associated = fun->nonLazyScript()->functionNonDelazifying();
         else if (fun->isInterpretedLazy() && !fun->isSelfHostedBuiltin())
             associated = fun->lazyScript()->functionNonDelazifying();
         else
             associated = nullptr;
+
+        // If we have previously cleared the 'new' script information for this
+        // function, don't try to construct another one.
+        if (associated && associated->wasNewScriptCleared())
+            associated = nullptr;
+
+        if (!associated)
+            clasp = &PlainObject::class_;
     }
 
     NewTypeObjectTable::AddPtr p =
         newTypeObjects.lookupForAdd(NewTypeObjectTable::Lookup(clasp, proto, associated));
     if (p) {
         TypeObject *type = p->object;
-        MOZ_ASSERT(type->clasp() == clasp);
+        MOZ_ASSERT_IF(clasp, type->clasp() == clasp);
+        MOZ_ASSERT_IF(!clasp, type->clasp() == &PlainObject::class_ ||
+                              type->clasp() == &UnboxedPlainObject::class_);
         MOZ_ASSERT(type->proto() == proto);
         return type;
     }
 
     AutoEnterAnalysis enter(this);
 
     if (proto.isObject() && !proto.toObject()->setDelegate(this))
         return nullptr;
 
     TypeObjectFlags initialFlags = 0;
-    if (!proto.isObject() || proto.toObject()->lastProperty()->hasObjectFlag(BaseShape::NEW_TYPE_UNKNOWN))
+    if (!proto.isObject() || proto.toObject()->isNewTypeUnknown())
         initialFlags = OBJECT_FLAG_DYNAMIC_MASK;
 
     Rooted<TaggedProto> protoRoot(this, proto);
-    TypeObject *type = compartment()->types.newTypeObject(this, clasp, protoRoot, initialFlags);
+    TypeObject *type = compartment()->types.newTypeObject(this,
+                                                          clasp ? clasp : &PlainObject::class_,
+                                                          protoRoot, initialFlags);
     if (!type)
         return nullptr;
 
     if (!newTypeObjects.add(p, NewTypeObjectEntry(type, associated)))
         return nullptr;
 
     TypeObjectTablePostBarrier(this, &newTypeObjects, clasp, proto, associated);
 
@@ -4722,16 +4837,19 @@ TypeObject::maybeSweep(AutoClearTypeInfe
     setGeneration(zone()->types.generation);
 
     MOZ_ASSERT(zone()->isGCSweepingOrCompacting());
     MOZ_ASSERT(!zone()->runtimeFromMainThread()->isHeapMinorCollecting());
 
     Maybe<AutoClearTypeInferenceStateOnOOM> fallbackOOM;
     EnsureHasAutoClearTypeInferenceStateOnOOM(oom, zone(), fallbackOOM);
 
+    if (maybeUnboxedLayout() && unboxedLayout().newScript())
+        unboxedLayout().newScript()->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.
@@ -4937,63 +5055,26 @@ JSCompartment::fixupNewTypeObjectTable(N
                 proto = TaggedProto(Forwarded(proto.toObject()));
                 needRekey = true;
             }
             if (entry.associated && IsForwarded(entry.associated)) {
                 entry.associated = Forwarded(entry.associated);
                 needRekey = true;
             }
             if (needRekey) {
-                NewTypeObjectTable::Lookup lookup(entry.object->clasp(),
-                                                  proto,
-                                                  entry.associated);
+                const Class *clasp = entry.object->clasp();
+                if (entry.associated && entry.associated->is<JSFunction>())
+                    clasp = nullptr;
+                NewTypeObjectTable::Lookup lookup(clasp, proto, entry.associated);
                 e.rekeyFront(lookup, entry);
             }
         }
     }
 }
 
-void
-TypeNewScript::fixupAfterMovingGC()
-{
-    if (fun && IsForwarded(fun.get()))
-        fun = Forwarded(fun.get());
-    /* preliminaryObjects are handled by sweep(). */
-    if (templateObject_ && IsForwarded(templateObject_.get()))
-        templateObject_ = Forwarded(templateObject_.get());
-    if (initializedShape_ && IsForwarded(initializedShape_.get()))
-        initializedShape_ = Forwarded(initializedShape_.get());
-}
-
-void
-TypeObject::fixupAfterMovingGC()
-{
-    if (proto().isObject() && IsForwarded(proto_.get()))
-        proto_ = Forwarded(proto_.get());
-    if (singleton_ && !lazy() && IsForwarded(singleton_.get()))
-        singleton_ = Forwarded(singleton_.get());
-    if (addendum_) {
-        switch (addendumKind()) {
-          case Addendum_NewScript:
-            newScript()->fixupAfterMovingGC();
-            break;
-          case Addendum_TypeDescr:
-            if (IsForwarded(&typeDescr()))
-                addendum_ = Forwarded(&typeDescr());
-            break;
-          case Addendum_InterpretedFunction:
-            if (IsForwarded(maybeInterpretedFunction()))
-                addendum_ = Forwarded(maybeInterpretedFunction());
-            break;
-          default:
-            MOZ_CRASH();
-        }
-    }
-}
-
 #ifdef JSGC_HASH_TABLE_CHECKS
 
 void
 JSCompartment::checkTypeObjectTablesAfterMovingGC()
 {
     checkTypeObjectTableAfterMovingGC(newTypeObjects);
     checkTypeObjectTableAfterMovingGC(lazyTypeObjects);
 }
@@ -5011,18 +5092,21 @@ JSCompartment::checkTypeObjectTableAfter
     for (NewTypeObjectTable::Enum e(table); !e.empty(); e.popFront()) {
         NewTypeObjectEntry entry = e.front();
         CheckGCThingAfterMovingGC(entry.object.get());
         TaggedProto proto = entry.object->proto();
         if (proto.isObject())
             CheckGCThingAfterMovingGC(proto.toObject());
         CheckGCThingAfterMovingGC(entry.associated);
 
-        NewTypeObjectTable::Lookup
-            lookup(entry.object->clasp(), proto, entry.associated);
+        const Class *clasp = entry.object->clasp();
+        if (entry.associated && entry.associated->is<JSFunction>())
+            clasp = nullptr;
+
+        NewTypeObjectTable::Lookup lookup(clasp, proto, entry.associated);
         NewTypeObjectTable::Ptr ptr = table.lookup(lookup);
         MOZ_ASSERT(ptr.found() && &*ptr == &e.front());
     }
 }
 
 #endif // JSGC_HASH_TABLE_CHECKS
 
 TypeCompartment::~TypeCompartment()
@@ -5121,18 +5205,22 @@ TypeCompartment::addSizeOfExcludingThis(
             *objectTypeTables += mallocSizeOf(key.properties) + mallocSizeOf(value.types);
         }
     }
 }
 
 size_t
 TypeObject::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
 {
-    TypeNewScript *newScript = newScriptDontCheckGeneration();
-    return newScript ? newScript->sizeOfIncludingThis(mallocSizeOf) : 0;
+    size_t n = 0;
+    if (TypeNewScript *newScript = newScriptDontCheckGeneration())
+        n += newScript->sizeOfIncludingThis(mallocSizeOf);
+    if (UnboxedLayout *layout = maybeUnboxedLayoutDontCheckGeneration())
+        n += layout->sizeOfIncludingThis(mallocSizeOf);
+    return n;
 }
 
 TypeZone::TypeZone(Zone *zone)
   : zone_(zone),
     typeLifoAlloc(TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
     generation(0),
     compilerOutputs(nullptr),
     sweepTypeLifoAlloc(TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
@@ -5286,24 +5374,26 @@ TypeScript::printTypes(JSContext *cx, Ha
         }
     }
 
     fprintf(stderr, "\n");
 }
 #endif /* DEBUG */
 
 void
-TypeObject::setAddendum(AddendumKind kind, void *addendum)
+TypeObject::setAddendum(AddendumKind kind, void *addendum, bool writeBarrier /* = true */)
 {
     MOZ_ASSERT(!needsSweep());
     MOZ_ASSERT(kind <= (OBJECT_FLAG_ADDENDUM_MASK >> OBJECT_FLAG_ADDENDUM_SHIFT));
-    MOZ_ASSERT(addendumKind() == 0 || addendumKind() == kind);
-
-    // Manually trigger barriers if we are clearing a TypeNewScript. Other
-    // kinds of addendums are immutable.
-    if (newScript()) {
-        MOZ_ASSERT(kind == Addendum_NewScript);
-        TypeNewScript::writeBarrierPre(newScript());
-    }
-
+
+    if (writeBarrier) {
+        // Manually trigger barriers if we are clearing a TypeNewScript. Other
+        // kinds of addendums are immutable.
+        if (newScript())
+            TypeNewScript::writeBarrierPre(newScript());
+        else
+            MOZ_ASSERT(addendumKind() == Addendum_None || addendumKind() == kind);
+    }
+
+    flags_ &= ~OBJECT_FLAG_ADDENDUM_MASK;
     flags_ |= kind << OBJECT_FLAG_ADDENDUM_SHIFT;
     addendum_ = addendum;
 }
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -22,16 +22,17 @@
 #include "jit/IonTypes.h"
 #include "js/UbiNode.h"
 #include "js/Utility.h"
 #include "js/Vector.h"
 
 namespace js {
 
 class TypeDescr;
+class UnboxedLayout;
 
 class TaggedProto
 {
   public:
     static JSObject * const LazyProto;
 
     TaggedProto() : proto(nullptr) {}
     explicit TaggedProto(JSObject *proto) : proto(proto) {}
@@ -401,33 +402,36 @@ enum : uint32_t {
      * Whether objects with this type should be allocated directly in the
      * tenured heap.
      */
     OBJECT_FLAG_PRE_TENURE            = 0x00800000,
 
     /* Whether objects with this type might have copy on write elements. */
     OBJECT_FLAG_COPY_ON_WRITE         = 0x01000000,
 
+    /* Whether this type has had its 'new' script cleared in the past. */
+    OBJECT_FLAG_NEW_SCRIPT_CLEARED    = 0x02000000,
+
     /*
      * Whether all properties of this object are considered unknown.
      * If set, all other flags in DYNAMIC_MASK will also be set.
      */
-    OBJECT_FLAG_UNKNOWN_PROPERTIES    = 0x02000000,
+    OBJECT_FLAG_UNKNOWN_PROPERTIES    = 0x04000000,
 
     /* Flags which indicate dynamic properties of represented objects. */
-    OBJECT_FLAG_DYNAMIC_MASK          = 0x03ff0000,
+    OBJECT_FLAG_DYNAMIC_MASK          = 0x07ff0000,
 
     // Mask/shift for the kind of addendum attached to this type object.
-    OBJECT_FLAG_ADDENDUM_MASK         = 0x0c000000,
-    OBJECT_FLAG_ADDENDUM_SHIFT        = 26,
+    OBJECT_FLAG_ADDENDUM_MASK         = 0x38000000,
+    OBJECT_FLAG_ADDENDUM_SHIFT        = 27,
 
     // Mask/shift for this type object's generation. If out of sync with the
     // TypeZone's generation, this TypeObject hasn't been swept yet.
-    OBJECT_FLAG_GENERATION_MASK       = 0x10000000,
-    OBJECT_FLAG_GENERATION_SHIFT      = 28,
+    OBJECT_FLAG_GENERATION_MASK       = 0x40000000,
+    OBJECT_FLAG_GENERATION_SHIFT      = 30,
 };
 typedef uint32_t TypeObjectFlags;
 
 class StackTypeSet;
 class HeapTypeSet;
 class TemporaryTypeSet;
 
 /*
@@ -630,17 +634,16 @@ class StackTypeSet : public ConstraintTy
 
 class HeapTypeSet : public ConstraintTypeSet
 {
     inline void newPropertyState(ExclusiveContext *cx);
 
   public:
     /* Mark this type set as representing a non-data property. */
     inline void setNonDataProperty(ExclusiveContext *cx);
-    inline void setNonDataPropertyIgnoringConstraints(); // Variant for use during GC.
 
     /* Mark this type set as representing a non-writable property. */
     inline void setNonWritableProperty(ExclusiveContext *cx);
 
     // Mark this type set as being non-constant.
     inline void setNonConstantProperty(ExclusiveContext *cx);
 };
 
@@ -789,16 +792,48 @@ struct Property
     Property(const Property &o)
       : id(o.id.get()), types(o.types)
     {}
 
     static uint32_t keyBits(jsid id) { return uint32_t(JSID_BITS(id)); }
     static jsid getKey(Property *p) { return p->id; }
 };
 
+// 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.
+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.
+    JSObject *objects[COUNT];
+
+  public:
+    PreliminaryObjectArray() {
+        mozilla::PodZero(this);
+    }
+
+    void registerNewObject(JSObject *res);
+    void unregisterNewObject(JSObject *res);
+
+    JSObject *get(size_t i) const {
+        MOZ_ASSERT(i < COUNT);
+        return objects[i];
+    }
+
+    bool full() const;
+    void sweep();
+};
+
 // 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 type T, our
 // analysis strategy is as follows:
@@ -846,31 +881,28 @@ class TypeNewScript
           : kind(kind), offset(offset)
         {}
     };
 
   private:
     // Scripted function which this information was computed for.
     // If instances of the associated type object are created without calling
     // 'new' on this function, the new script information is cleared.
-    HeapPtrFunction fun;
+    HeapPtrFunction function_;
 
-    // If fewer than PRELIMINARY_OBJECT_COUNT instances of the type are
-    // created, this array holds pointers to each of those objects. When the
-    // threshold has been reached, the definite and acquired properties
-    // analyses are performed and this array is cleared. The pointers in this
-    // array are weak.
-    static const uint32_t PRELIMINARY_OBJECT_COUNT = 20;
-    PlainObject **preliminaryObjects;
+    // Any preliminary objects with the type. The analyses are not performed
+    // until this array is cleared.
+    PreliminaryObjectArray *preliminaryObjects;
 
     // After the new script properties analyses have been performed, a template
     // object to use for newly constructed objects. The shape of this object
     // reflects all definite properties the object will have, and the
-    // allocation kind to use. Note that this is actually a PlainObject, but is
-    // JSObject here to avoid cyclic include dependencies.
+    // allocation kind to use. This is null if the new objects have an unboxed
+    // layout, in which case the UnboxedLayout provides the initial structure
+    // of the object.
     HeapPtrPlainObject templateObject_;
 
     // Order in which definite properties become initialized. We need this in
     // case the definite properties are invalidated (such as by adding a setter
     // to an object on the prototype chain) while an object is in the middle of
     // being initialized, so we can walk the stack and fixup any objects which
     // look for in-progress objects which were prematurely set with an incorrect
     // shape. Property assignments in inner frames are preceded by a series of
@@ -887,55 +919,50 @@ class TypeNewScript
 
     // Type object with definite properties set for all properties found by
     // both the definite and acquired properties analyses.
     HeapPtrTypeObject initializedType_;
 
   public:
     TypeNewScript() { mozilla::PodZero(this); }
     ~TypeNewScript() {
-        js_free(preliminaryObjects);
+        js_delete(preliminaryObjects);
         js_free(initializerList);
     }
 
     static inline void writeBarrierPre(TypeNewScript *newScript);
 
     bool analyzed() const {
-        if (preliminaryObjects) {
-            MOZ_ASSERT(!templateObject());
-            MOZ_ASSERT(!initializerList);
-            MOZ_ASSERT(!initializedShape());
-            MOZ_ASSERT(!initializedType());
-            return false;
-        }
-        MOZ_ASSERT(templateObject());
-        return true;
+        return preliminaryObjects == nullptr;
     }
 
     PlainObject *templateObject() const {
         return templateObject_;
     }
 
     Shape *initializedShape() const {
         return initializedShape_;
     }
 
     TypeObject *initializedType() const {
         return initializedType_;
     }
 
+    JSFunction *function() const {
+        return function_;
+    }
+
     void trace(JSTracer *trc);
     void sweep();
-    void fixupAfterMovingGC();
 
     void registerNewObject(PlainObject *res);
     void unregisterNewObject(PlainObject *res);
     bool maybeAnalyze(JSContext *cx, TypeObject *type, bool *regenerate, bool force = false);
 
-    void rollbackPartiallyInitializedObjects(JSContext *cx, TypeObject *type);
+    bool rollbackPartiallyInitializedObjects(JSContext *cx, TypeObject *type);
 
     static void make(JSContext *cx, TypeObject *type, JSFunction *fun);
 
     size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 };
 
 /*
  * Lazy type objects overview.
@@ -976,17 +1003,16 @@ struct TypeObject : public gc::TenuredCe
 
   public:
 
     const Class *clasp() const {
         return clasp_;
     }
 
     void setClasp(const Class *clasp) {
-        MOZ_ASSERT(singleton());
         clasp_ = clasp;
     }
 
     TaggedProto proto() const {
         return TaggedProto(proto_);
     }
 
     JSObject *singleton() const {
@@ -1024,37 +1050,51 @@ struct TypeObject : public gc::TenuredCe
         // When used by interpreted function, the addendum stores the
         // canonical JSFunction object.
         Addendum_InterpretedFunction,
 
         // When used by the 'new' type when constructing an interpreted
         // function, the addendum stores a TypeNewScript.
         Addendum_NewScript,
 
+        // When objects with this type have an unboxed representation, the
+        // addendum stores an UnboxedLayout (which might have a TypeNewScript
+        // as well, if the type is also constructed using 'new').
+        Addendum_UnboxedLayout,
+
         // When used by typed objects, the addendum stores a TypeDescr.
         Addendum_TypeDescr
     };
 
     // If non-null, holds additional information about this object, whose
     // format is indicated by the object's addendum kind.
     void *addendum_;
 
-    void setAddendum(AddendumKind kind, void *addendum);
+    void setAddendum(AddendumKind kind, void *addendum, bool writeBarrier = true);
 
     AddendumKind addendumKind() const {
         return (AddendumKind)
             ((flags_ & OBJECT_FLAG_ADDENDUM_MASK) >> OBJECT_FLAG_ADDENDUM_SHIFT);
     }
 
     TypeNewScript *newScriptDontCheckGeneration() const {
-        return addendumKind() == Addendum_NewScript
-               ? reinterpret_cast<TypeNewScript *>(addendum_)
-               : nullptr;
+        if (addendumKind() == Addendum_NewScript)
+            return reinterpret_cast<TypeNewScript *>(addendum_);
+        return nullptr;
     }
 
+    UnboxedLayout *maybeUnboxedLayoutDontCheckGeneration() const {
+        if (addendumKind() == Addendum_UnboxedLayout)
+            return reinterpret_cast<UnboxedLayout *>(addendum_);
+        return nullptr;
+    }
+
+    TypeNewScript *anyNewScript();
+    void detachNewScript(bool writeBarrier);
+
   public:
 
     TypeObjectFlags flags() {
         maybeSweep(nullptr);
         return flags_;
     }
 
     void addFlags(TypeObjectFlags flags) {
@@ -1071,16 +1111,30 @@ struct TypeObject : public gc::TenuredCe
         maybeSweep(nullptr);
         return newScriptDontCheckGeneration();
     }
 
     void setNewScript(TypeNewScript *newScript) {
         setAddendum(Addendum_NewScript, newScript);
     }
 
+    UnboxedLayout *maybeUnboxedLayout() {
+        maybeSweep(nullptr);
+        return maybeUnboxedLayoutDontCheckGeneration();
+    }
+
+    UnboxedLayout &unboxedLayout() {
+        MOZ_ASSERT(addendumKind() == Addendum_UnboxedLayout);
+        return *maybeUnboxedLayout();
+    }
+
+    void setUnboxedLayout(UnboxedLayout *layout) {
+        setAddendum(Addendum_UnboxedLayout, layout);
+    }
+
     TypeDescr *maybeTypeDescr() {
         // Note: there is no need to sweep when accessing the type descriptor
         // of an object, as it is strongly held and immutable.
         if (addendumKind() == Addendum_TypeDescr)
             return reinterpret_cast<TypeDescr *>(addendum_);
         return nullptr;
     }
 
@@ -1110,18 +1164,18 @@ struct TypeObject : public gc::TenuredCe
      * Properties of this object. This may contain JSID_VOID, representing the
      * types of all integer indexes of the object, and/or JSID_EMPTY, holding
      * constraints listening to changes to the object's state.
      *
      * The type sets in the properties of a type object describe the possible
      * values that can be read out of that property in actual JS objects.
      * In native objects, property types account for plain data properties
      * (those with a slot and no getter or setter hook) and dense elements.
-     * In typed objects, property types account for object and value properties
-     * and elements in the object.
+     * In typed objects and unboxed objects, property types account for object
+     * and value properties and elements in the object.
      *
      * For accesses on these properties, the correspondence is as follows:
      *
      * 1. If the type has unknownProperties(), the possible properties and
      *    value types for associated JSObjects are unknown.
      *
      * 2. Otherwise, for any JSObject obj with TypeObject type, and any jsid id
      *    which is a property in obj, before obj->getProperty(id) the property
@@ -1134,19 +1188,20 @@ struct TypeObject : public gc::TenuredCe
      *    remain empty, and the 'undefined' type will only be added after a
      *    subsequent assignment or deletion. After these properties have been
      *    assigned a defined value, the only way they can become undefined
      *    again is after such an assign or deletion.
      *
      * 2. Array lengths are special cased by the compiler and VM and are not
      *    reflected in property types.
      *
-     * 3. In typed objects, the initial values of properties (null pointers and
-     *    undefined values) are not reflected in the property types. These
-     *    values are always possible when reading the property.
+     * 3. In typed objects (but not unboxed objects), the initial values of
+     *    properties (null pointers and undefined values) are not reflected in
+     *    the property types. These values are always possible when reading the
+     *    property.
      *
      * We establish these by using write barriers on calls to setProperty and
      * defineProperty which are on native properties, and on any jitcode which
      * might update the property with a new type.
      */
     Property **propertySet;
   public:
 
@@ -1234,21 +1289,20 @@ struct TypeObject : public gc::TenuredCe
 
   public:
     void setGeneration(uint32_t generation) {
         MOZ_ASSERT(generation <= (OBJECT_FLAG_GENERATION_MASK >> OBJECT_FLAG_GENERATION_SHIFT));
         flags_ &= ~OBJECT_FLAG_GENERATION_MASK;
         flags_ |= generation << OBJECT_FLAG_GENERATION_SHIFT;
     }
 
-    void fixupAfterMovingGC();
-
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
     inline void finalize(FreeOp *fop);
+    void fixupAfterMovingGC() {}
 
     static inline ThingRootKind rootKind() { return THING_ROOT_TYPE_OBJECT; }
 
     static inline uint32_t offsetOfClasp() {
         return offsetof(TypeObject, clasp_);
     }
 
     static inline uint32_t offsetOfProto() {
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -17,16 +17,17 @@
 #include "jit/BaselineJIT.h"
 #include "vm/ArrayObject.h"
 #include "vm/BooleanObject.h"
 #include "vm/NumberObject.h"
 #include "vm/SharedArrayObject.h"
 #include "vm/SharedTypedArrayObject.h"
 #include "vm/StringObject.h"
 #include "vm/TypedArrayObject.h"
+#include "vm/UnboxedObject.h"
 
 #include "jscntxtinlines.h"
 
 namespace js {
 namespace types {
 
 /////////////////////////////////////////////////////////////////////
 // CompilerOutput & RecompileInfo
@@ -1077,28 +1078,22 @@ HeapTypeSet::newPropertyState(ExclusiveC
             constraint = constraint->next;
         }
     } else {
         MOZ_ASSERT(!constraintList);
     }
 }
 
 inline void
-HeapTypeSet::setNonDataPropertyIgnoringConstraints()
-{
-    flags |= TYPE_FLAG_NON_DATA_PROPERTY;
-}
-
-inline void
 HeapTypeSet::setNonDataProperty(ExclusiveContext *cx)
 {
     if (flags & TYPE_FLAG_NON_DATA_PROPERTY)
         return;
 
-    setNonDataPropertyIgnoringConstraints();
+    flags |= TYPE_FLAG_NON_DATA_PROPERTY;
     newPropertyState(cx);
 }
 
 inline void
 HeapTypeSet::setNonWritableProperty(ExclusiveContext *cx)
 {
     if (flags & TYPE_FLAG_NON_WRITABLE_PROPERTY)
         return;
@@ -1195,16 +1190,17 @@ inline TypeObject::TypeObject(const Clas
 
     InferSpew(ISpewOps, "newObject: %s", TypeObjectString(this));
 }
 
 inline void
 TypeObject::finalize(FreeOp *fop)
 {
     fop->delete_(newScriptDontCheckGeneration());
+    fop->delete_(maybeUnboxedLayoutDontCheckGeneration());
 }
 
 inline uint32_t
 TypeObject::basePropertyCount()
 {
     return (flags() & OBJECT_FLAG_PROPERTY_COUNT_MASK) >> OBJECT_FLAG_PROPERTY_COUNT_SHIFT;
 }
 
@@ -1289,20 +1285,20 @@ TypeObject::getProperty(unsigned i)
         return (Property *) propertySet;
     }
     return propertySet[i];
 }
 
 inline void
 TypeNewScript::writeBarrierPre(TypeNewScript *newScript)
 {
-    if (!newScript->fun->runtimeFromAnyThread()->needsIncrementalBarrier())
+    if (!newScript->function()->runtimeFromAnyThread()->needsIncrementalBarrier())
         return;
 
-    JS::Zone *zone = newScript->fun->zoneFromAnyThread();
+    JS::Zone *zone = newScript->function()->zoneFromAnyThread();
     if (zone->needsIncrementalBarrier())
         newScript->trace(zone->barrierTracer());
 }
 
 } } /* namespace js::types */
 
 inline js::types::TypeScript *
 JSScript::types()
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -896,16 +896,19 @@ bool
 js::StandardDefineProperty(JSContext *cx, HandleObject obj, HandleId id, const PropDesc &desc,
                            bool throwError, bool *rval)
 {
     if (obj->is<ArrayObject>()) {
         Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
         return DefinePropertyOnArray(cx, arr, id, desc, throwError, rval);
     }
 
+    if (obj->is<UnboxedPlainObject>() && !obj->as<UnboxedPlainObject>().convertToNative(cx))
+        return false;
+
     if (obj->getOps()->lookupGeneric) {
         if (obj->is<ProxyObject>()) {
             Rooted<PropertyDescriptor> pd(cx);
             desc.populatePropertyDescriptor(obj, &pd);
             pd.object().set(obj);
             return Proxy::defineProperty(cx, obj, id, &pd);
         }
         return Reject(cx, obj, JSMSG_OBJECT_NOT_EXTENSIBLE, throwError, rval);
@@ -958,16 +961,19 @@ js::DefineProperties(JSContext *cx, Hand
         Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
         for (size_t i = 0, len = ids.length(); i < len; i++) {
             if (!DefinePropertyOnArray(cx, arr, ids[i], descs[i], true, &dummy))
                 return false;
         }
         return true;
     }
 
+    if (obj->is<UnboxedPlainObject>() && !obj->as<UnboxedPlainObject>().convertToNative(cx))
+        return false;
+
     if (obj->getOps()->lookupGeneric) {
         if (obj->is<ProxyObject>()) {
             Rooted<PropertyDescriptor> pd(cx);
             for (size_t i = 0, len = ids.length(); i < len; i++) {
                 descs[i].populatePropertyDescriptor(obj, &pd);
                 if (!Proxy::defineProperty(cx, obj, ids[i], &pd))
                     return false;
             }
@@ -1486,20 +1492,23 @@ js::CreateThis(JSContext *cx, const Clas
         return nullptr;
 
     JSObject *proto = protov.isObjectOrNull() ? protov.toObjectOrNull() : nullptr;
     JSObject *parent = callee->getParent();
     gc::AllocKind kind = NewObjectGCKind(newclasp);
     return NewObjectWithClassProto(cx, newclasp, proto, parent, kind);
 }
 
-static inline PlainObject *
+static inline JSObject *
 CreateThisForFunctionWithType(JSContext *cx, HandleTypeObject type, JSObject *parent,
                               NewObjectKind newKind)
 {
+    if (type->maybeUnboxedLayout() && newKind != SingletonObject)
+        return UnboxedPlainObject::create(cx, type, newKind);
+
     if (types::TypeNewScript *newScript = type->newScript()) {
         if (newScript->analyzed()) {
             // The definite properties analysis has been performed for this
             // type, so get the shape and finalize kind to use from the
             // TypeNewScript's template.
             RootedPlainObject templateObject(cx, newScript->templateObject());
             MOZ_ASSERT(templateObject->type() == type);
 
@@ -1535,36 +1544,36 @@ CreateThisForFunctionWithType(JSContext 
 
         return res;
     }
 
     gc::AllocKind allocKind = NewObjectGCKind(&PlainObject::class_);
     return NewObjectWithType<PlainObject>(cx, type, parent, allocKind, newKind);
 }
 
-PlainObject *
+JSObject *
 js::CreateThisForFunctionWithProto(JSContext *cx, HandleObject callee, JSObject *proto,
                                    NewObjectKind newKind /* = GenericObject */)
 {
-    RootedPlainObject res(cx);
+    RootedObject res(cx);
 
     if (proto) {
-        RootedTypeObject type(cx, cx->getNewType(&PlainObject::class_, TaggedProto(proto),
+        RootedTypeObject type(cx, cx->getNewType(nullptr, TaggedProto(proto),
                                                  &callee->as<JSFunction>()));
         if (!type)
             return nullptr;
 
         if (type->newScript() && !type->newScript()->analyzed()) {
             bool regenerate;
             if (!type->newScript()->maybeAnalyze(cx, type, &regenerate))
                 return nullptr;
             if (regenerate) {
                 // The script was analyzed successfully and may have changed
                 // the new type table, so refetch the type.
-                type = cx->getNewType(&PlainObject::class_, TaggedProto(proto),
+                type = cx->getNewType(nullptr, TaggedProto(proto),
                                       &callee->as<JSFunction>());
                 MOZ_ASSERT(type && type->newScript());
             }
         }
 
         res = CreateThisForFunctionWithType(cx, type, callee->getParent(), newKind);
     } else {
         gc::AllocKind allocKind = NewObjectGCKind(&PlainObject::class_);
@@ -1576,31 +1585,31 @@ js::CreateThisForFunctionWithProto(JSCon
         if (!script)
             return nullptr;
         TypeScript::SetThis(cx, script, types::Type::ObjectType(res));
     }
 
     return res;
 }
 
-PlainObject *
+JSObject *
 js::CreateThisForFunction(JSContext *cx, HandleObject callee, NewObjectKind newKind)
 {
     RootedValue protov(cx);
     if (!GetProperty(cx, callee, callee, cx->names().prototype, &protov))
         return nullptr;
     JSObject *proto;
     if (protov.isObject())
         proto = &protov.toObject();
     else
         proto = nullptr;
-    PlainObject *obj = CreateThisForFunctionWithProto(cx, callee, proto, newKind);
+    JSObject *obj = CreateThisForFunctionWithProto(cx, callee, proto, newKind);
 
     if (obj && newKind == SingletonObject) {
-        RootedPlainObject nobj(cx, obj);
+        RootedPlainObject nobj(cx, &obj->as<PlainObject>());
 
         /* Reshape the singleton before passing it as the 'this' value. */
         NativeObject::clear(cx, nobj);
 
         JSScript *calleeScript = callee->as<JSFunction>().nonLazyScript();
         TypeScript::SetThis(cx, calleeScript, types::Type::ObjectType(nobj));
 
         return nobj;
@@ -3791,16 +3800,17 @@ JSObject::dump()
     if (obj->isBoundFunction()) fprintf(stderr, " bound_function");
     if (obj->isQualifiedVarObj()) fprintf(stderr, " varobj");
     if (obj->isUnqualifiedVarObj()) fprintf(stderr, " unqualified_varobj");
     if (obj->watched()) fprintf(stderr, " watched");
     if (obj->isIteratedSingleton()) fprintf(stderr, " iterated_singleton");
     if (obj->isNewTypeUnknown()) fprintf(stderr, " new_type_unknown");
     if (obj->hasUncacheableProto()) fprintf(stderr, " has_uncacheable_proto");
     if (obj->hadElementsAccess()) fprintf(stderr, " had_elements_access");
+    if (obj->wasNewScriptCleared()) fprintf(stderr, " new_script_cleared");
 
     if (obj->isNative()) {
         NativeObject *nobj = &obj->as<NativeObject>();
         if (nobj->inDictionaryMode())
             fprintf(stderr, " inDictionaryMode");
         if (nobj->hasShapeTable())
             fprintf(stderr, " hasShapeTable");
     }
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -421,16 +421,24 @@ class JSObject : public js::gc::Cell
      * Mark an object as requiring its default 'new' type to have unknown
      * properties.
      */
     bool isNewTypeUnknown() const {
         return lastProperty()->hasObjectFlag(js::BaseShape::NEW_TYPE_UNKNOWN);
     }
     static bool setNewTypeUnknown(JSContext *cx, const js::Class *clasp, JS::HandleObject obj);
 
+    // Mark an object as having its 'new' script information cleared.
+    bool wasNewScriptCleared() const {
+        return lastProperty()->hasObjectFlag(js::BaseShape::NEW_SCRIPT_CLEARED);
+    }
+    bool setNewScriptCleared(js::ExclusiveContext *cx) {
+        return setFlag(cx, js::BaseShape::NEW_SCRIPT_CLEARED);
+    }
+
     /* Set a new prototype for an object with a singleton type. */
     bool splicePrototype(JSContext *cx, const js::Class *clasp, js::Handle<js::TaggedProto> proto);
 
     /*
      * For bootstrapping, whether to splice a prototype for Function.prototype
      * or the global object.
      */
     bool shouldSplicePrototype(JSContext *cx);
@@ -594,16 +602,19 @@ class JSObject : public js::gc::Cell
     /* JIT Accessors */
 
     static size_t offsetOfShape() { return offsetof(JSObject, shape_); }
     js::HeapPtrShape *addressOfShape() { return &shape_; }
 
     static size_t offsetOfType() { return offsetof(JSObject, type_); }
     js::HeapPtrTypeObject *addressOfType() { return &type_; }
 
+    // Maximum size in bytes of a JSObject.
+    static const size_t MAX_BYTE_SIZE = 4 * sizeof(void *) + 16 * sizeof(JS::Value);
+
   private:
     JSObject() = delete;
     JSObject(const JSObject &other) = delete;
     void operator=(const JSObject &other) = delete;
 };
 
 template <class U>
 MOZ_ALWAYS_INLINE JS::Handle<U*>
@@ -1149,22 +1160,22 @@ GetInitialHeap(NewObjectKind newKind, co
         return gc::TenuredHeap;
     if (clasp->finalize && !(clasp->flags & JSCLASS_FINALIZE_FROM_NURSERY))
         return gc::TenuredHeap;
     return gc::DefaultHeap;
 }
 
 // Specialized call for constructing |this| with a known function callee,
 // and a known prototype.
-extern PlainObject *
+extern JSObject *
 CreateThisForFunctionWithProto(JSContext *cx, js::HandleObject callee, JSObject *proto,
                                NewObjectKind newKind = GenericObject);
 
 // Specialized call for constructing |this| with a known function callee.
-extern PlainObject *
+extern JSObject *
 CreateThisForFunction(JSContext *cx, js::HandleObject callee, NewObjectKind newKind);
 
 // Generic call for constructing |this|.
 extern JSObject *
 CreateThis(JSContext *cx, const js::Class *clasp, js::HandleObject callee);
 
 extern JSObject *
 CloneObject(JSContext *cx, HandleObject obj, Handle<js::TaggedProto> proto, HandleObject parent);
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -266,16 +266,17 @@ UNIFIED_SOURCES += [
     'vm/SPSProfiler.cpp',
     'vm/Stack.cpp',
     'vm/String.cpp',
     'vm/StringBuffer.cpp',
     'vm/StructuredClone.cpp',
     'vm/Symbol.cpp',
     'vm/TypedArrayObject.cpp',
     'vm/UbiNode.cpp',
+    'vm/UnboxedObject.cpp',
     'vm/Unicode.cpp',
     'vm/Value.cpp',
     'vm/WeakMapPtr.cpp',
     'vm/Xdr.cpp'
 ]
 
 # jsarray.cpp and jsatom.cpp cannot be built in unified mode because
 # xpcshell is broken during packaging when compiled with gcc-4.8.2
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5524,21 +5524,23 @@ ProcessArgs(JSContext *cx, JSObject *obj
 
 static bool
 SetRuntimeOptions(JSRuntime *rt, const OptionParser &op)
 {
     bool enableBaseline = !op.getBoolOption("no-baseline");
     bool enableIon = !op.getBoolOption("no-ion");
     bool enableAsmJS = !op.getBoolOption("no-asmjs");
     bool enableNativeRegExp = !op.getBoolOption("no-native-regexp");
+    bool enableUnboxedObjects = op.getBoolOption("unboxed-objects");
 
     JS::RuntimeOptionsRef(rt).setBaseline(enableBaseline)
                              .setIon(enableIon)
                              .setAsmJS(enableAsmJS)
-                             .setNativeRegExp(enableNativeRegExp);
+                             .setNativeRegExp(enableNativeRegExp)
+                             .setUnboxedObjects(enableUnboxedObjects);
 
     if (const char *str = op.getStringOption("ion-scalar-replacement")) {
         if (strcmp(str, "on") == 0)
             jit::js_JitOptions.disableScalarReplacement = false;
         else if (strcmp(str, "off") == 0)
             jit::js_JitOptions.disableScalarReplacement = true;
         else
             return OptionFailure("ion-scalar-replacement", str);
@@ -5870,16 +5872,17 @@ main(int argc, char **argv, char **envp)
                                          "String arguments to bind as |scriptArgs| in the "
                                          "shell's global")
         || !op.addIntOption('\0', "thread-count", "COUNT", "Use COUNT auxiliary threads "
                             "(default: # of cores - 1)", -1)
         || !op.addBoolOption('\0', "ion", "Enable IonMonkey (default)")
         || !op.addBoolOption('\0', "no-ion", "Disable IonMonkey")
         || !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation")
         || !op.addBoolOption('\0', "no-native-regexp", "Disable native regexp compilation")
+        || !op.addBoolOption('\0', "unboxed-objects", "Allow creating unboxed objects")
         || !op.addStringOption('\0', "ion-scalar-replacement", "on/off",
                                "Scalar Replacement (default: on, off to disable)")
         || !op.addStringOption('\0', "ion-gvn", "[mode]",
                                "Specify Ion global value numbering:\n"
                                "  off: disable GVN\n"
                                "  on:  enable GVN (default)\n")
         || !op.addStringOption('\0', "ion-licm", "on/off",
                                "Loop invariant code motion (default: on, off to disable)")
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -362,16 +362,30 @@ NativeObject::setLastPropertyShrinkFixed
     MOZ_ASSERT(shape->slotSpan() <= oldFixed);
     MOZ_ASSERT(shape->slotSpan() <= newFixed);
     MOZ_ASSERT(dynamicSlotsCount(oldFixed, shape->slotSpan(), getClass()) == 0);
     MOZ_ASSERT(dynamicSlotsCount(newFixed, shape->slotSpan(), getClass()) == 0);
 
     shape_ = shape;
 }
 
+void
+NativeObject::setLastPropertyMakeNonNative(Shape *shape)
+{
+    MOZ_ASSERT(!inDictionaryMode());
+    MOZ_ASSERT(!shape->getObjectClass()->isNative());
+    MOZ_ASSERT(shape->compartment() == compartment());
+    MOZ_ASSERT(shape->slotSpan() == 0);
+    MOZ_ASSERT(shape->numFixedSlots() == 0);
+    MOZ_ASSERT(!hasDynamicElements());
+    MOZ_ASSERT(!hasDynamicSlots());
+
+    shape_ = shape;
+}
+
 /* static */ bool
 NativeObject::setSlotSpan(ExclusiveContext *cx, HandleNativeObject obj, uint32_t span)
 {
     MOZ_ASSERT(obj->inDictionaryMode());
 
     size_t oldSpan = obj->lastProperty()->base()->slotSpan();
     if (oldSpan == span)
         return true;
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -368,16 +368,18 @@ class NativeObject : public JSObject
                       "shadow type must match actual type");
         static_assert(offsetof(NativeObject, slots_) == offsetof(shadow::Object, slots),
                       "shadow slots must match actual slots");
         static_assert(offsetof(NativeObject, elements_) == offsetof(shadow::Object, _1),
                       "shadow placeholder must match actual elements");
 
         static_assert(MAX_FIXED_SLOTS <= Shape::FIXED_SLOTS_MAX,
                       "verify numFixedSlots() bitfield is big enough");
+        static_assert(sizeof(NativeObject) + MAX_FIXED_SLOTS * sizeof(Value) == JSObject::MAX_BYTE_SIZE,
+                      "inconsistent maximum object size");
     }
 
   public:
     HeapSlotArray getDenseElements() {
         return HeapSlotArray(elements_, !getElementsHeader()->isCopyOnWrite());
     }
     HeapSlotArray getDenseElementsAllowCopyOnWrite() {
         // Backdoor allowing direct access to copy on write elements.
@@ -405,16 +407,21 @@ class NativeObject : public JSObject
                                 HandleNativeObject obj, HandleShape shape);
 
     // As for setLastProperty(), but allows the number of fixed slots to
     // change. This can only be used when fixed slots are being erased from the
     // object, and only when the object will not require dynamic slots to cover
     // the new properties.
     void setLastPropertyShrinkFixedSlots(Shape *shape);
 
+    // As for setLastProperty(), but changes the class associated with the
+    // object to a non-native one. This leaves the object with a type and shape
+    // that are (temporarily) inconsistent.
+    void setLastPropertyMakeNonNative(Shape *shape);
+
   protected:
 #ifdef DEBUG
     void checkShapeConsistency();
 #else
     void checkShapeConsistency() { }
 #endif
 
     Shape *
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -1073,17 +1073,16 @@ NativeObject::rollbackProperties(Exclusi
     while (true) {
         if (obj->lastProperty()->isEmptyShape()) {
             MOZ_ASSERT(slotSpan == 0);
             break;
         } else {
             uint32_t slot = obj->lastProperty()->slot();
             if (slot < slotSpan)
                 break;
-            MOZ_ASSERT(obj->getSlot(slot).isUndefined());
         }
         if (!obj->removeProperty(cx, obj->lastProperty()->propid()))
             return false;
     }
 
     return true;
 }
 
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -401,17 +401,21 @@ class BaseShape : public gc::TenuredCell
         // chain that has the QUALIFIED_VAROBJ flag set. If it's "unqualified"
         // (i.e., if it was introduced without any var, let, or const, which
         // incidentally is an error in strict mode) then it goes on the lowest
         // scope in the chain with the UNQUALIFIED_VAROBJ flag set (which is
         // typically the global).
         QUALIFIED_VAROBJ    = 0x2000,
         UNQUALIFIED_VAROBJ  = 0x4000,
 
-        OBJECT_FLAG_MASK    = 0x7ff8
+        // For a function used as an interpreted constructor, whether a 'new'
+        // type had constructor information cleared.
+        NEW_SCRIPT_CLEARED  = 0x8000,
+
+        OBJECT_FLAG_MASK    = 0xfff8
     };
 
   private:
     const Class         *clasp_;        /* Class of referring object. */
     HeapPtrObject       parent;         /* Parent of referring object. */
     HeapPtrObject       metadata;       /* Optional holder of metadata about
                                          * the referring object. */
     JSCompartment       *compartment_;  /* Compartment shape belongs to. */
new file mode 100644
--- /dev/null
+++ b/js/src/vm/UnboxedObject.cpp
@@ -0,0 +1,689 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "vm/UnboxedObject.h"
+
+#include "jsinferinlines.h"
+#include "jsobjinlines.h"
+
+#include "vm/Shape-inl.h"
+
+using mozilla::ArrayLength;
+using mozilla::DebugOnly;
+using mozilla::PodCopy;
+
+using namespace js;
+
+/////////////////////////////////////////////////////////////////////
+// UnboxedLayout
+/////////////////////////////////////////////////////////////////////
+
+void
+UnboxedLayout::trace(JSTracer *trc)
+{
+    for (size_t i = 0; i < properties_.length(); i++)
+        MarkStringUnbarriered(trc, &properties_[i].name, "unboxed_layout_name");
+
+    if (newScript())
+        newScript()->trace(trc);
+}
+
+size_t
+UnboxedLayout::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
+{
+    return mallocSizeOf(this)
+         + properties_.sizeOfExcludingThis(mallocSizeOf)
+         + (newScript() ? newScript()->sizeOfIncludingThis(mallocSizeOf) : 0)
+         + mallocSizeOf(traceList());
+}
+
+void
+UnboxedLayout::setNewScript(types::TypeNewScript *newScript, bool writeBarrier /* = true */)
+{
+    if (newScript_ && writeBarrier)
+        types::TypeNewScript::writeBarrierPre(newScript_);
+    newScript_ = newScript;
+}
+
+/////////////////////////////////////////////////////////////////////
+// UnboxedPlainObject
+/////////////////////////////////////////////////////////////////////
+
+bool
+UnboxedPlainObject::setValue(JSContext *cx, const UnboxedLayout::Property &property, const Value &v)
+{
+    uint8_t *p = &data_[property.offset];
+
+    switch (property.type) {
+      case JSVAL_TYPE_BOOLEAN:
+        if (v.isBoolean()) {
+            *p = v.toBoolean();
+            return true;
+        }
+        return false;
+
+      case JSVAL_TYPE_INT32:
+        if (v.isInt32()) {
+            *reinterpret_cast<int32_t*>(p) = v.toInt32();
+            return true;
+        }
+        return false;
+
+      case JSVAL_TYPE_DOUBLE:
+        if (v.isNumber()) {
+            *reinterpret_cast<double*>(p) = v.toNumber();
+            return true;
+        }
+        return false;
+
+      case JSVAL_TYPE_STRING:
+        if (v.isString()) {
+            *reinterpret_cast<HeapPtrString*>(p) = v.toString();
+            return true;
+        }
+        return false;
+
+      case JSVAL_TYPE_OBJECT:
+        if (v.isObjectOrNull()) {
+            // Update property types when writing object properties. Types for
+            // other properties were captured when the unboxed layout was
+            // created.
+            types::AddTypePropertyId(cx, this, NameToId(property.name), v);
+
+            *reinterpret_cast<HeapPtrObject*>(p) = v.toObjectOrNull();
+            return true;
+        }
+        return false;
+
+      default:
+        MOZ_CRASH("Invalid type for unboxed value");
+    }
+}
+
+Value
+UnboxedPlainObject::getValue(const UnboxedLayout::Property &property)
+{
+    uint8_t *p = &data_[property.offset];
+
+    switch (property.type) {
+      case JSVAL_TYPE_BOOLEAN:
+        return BooleanValue(*p != 0);
+
+      case JSVAL_TYPE_INT32:
+        return Int32Value(*reinterpret_cast<int32_t*>(p));
+
+      case JSVAL_TYPE_DOUBLE:
+        return DoubleValue(*reinterpret_cast<double*>(p));
+
+      case JSVAL_TYPE_STRING:
+        return StringValue(*reinterpret_cast<JSString**>(p));
+
+      case JSVAL_TYPE_OBJECT:
+        return ObjectOrNullValue(*reinterpret_cast<JSObject**>(p));
+
+      default:
+        MOZ_CRASH("Invalid type for unboxed value");
+    }
+}
+
+void
+UnboxedPlainObject::trace(JSTracer *trc, JSObject *obj)
+{
+    const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
+    const int32_t *list = layout.traceList();
+    if (!list)
+        return;
+
+    uint8_t *data = obj->as<UnboxedPlainObject>().data();
+    while (*list != -1) {
+        HeapPtrString *heap = reinterpret_cast<HeapPtrString *>(data + *list);
+        MarkString(trc, heap, "unboxed_string");
+        list++;
+    }
+    list++;
+    while (*list != -1) {
+        HeapPtrObject *heap = reinterpret_cast<HeapPtrObject *>(data + *list);
+        if (*heap)
+            MarkObject(trc, heap, "unboxed_object");
+        list++;
+    }
+
+    // Unboxed objects don't have Values to trace.
+    MOZ_ASSERT(*(list + 1) == -1);
+}
+
+bool
+UnboxedPlainObject::convertToNative(JSContext *cx)
+{
+    // Immediately clear any new script on this object's type,
+    // as rollbackPartiallyInitializedObjects() will be confused by the type
+    // changes we make in this function.
+    type()->clearNewScript(cx);
+
+    // clearNewScript() can reentrantly invoke this method.
+    if (!is<UnboxedPlainObject>())
+        return true;
+
+    Rooted<UnboxedPlainObject *> obj(cx, this);
+    Rooted<TaggedProto> proto(cx, getTaggedProto());
+
+    size_t nfixed = gc::GetGCKindSlots(obj->layout().getAllocKind());
+
+    AutoValueVector values(cx);
+    RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_, proto,
+                                                      getMetadata(), getParent(), nfixed,
+                                                      lastProperty()->getObjectFlags()));
+    if (!shape)
+        return false;
+
+    for (size_t i = 0; i < obj->layout().properties().length(); i++) {
+        const UnboxedLayout::Property &property = obj->layout().properties()[i];
+
+        if (!values.append(obj->getValue(property)))
+            return false;
+
+        StackShape unrootedChild(shape->base()->unowned(), NameToId(property.name), i,
+                                 JSPROP_ENUMERATE, 0);
+        RootedGeneric<StackShape*> child(cx, &unrootedChild);
+        shape = cx->compartment()->propertyTree.getChild(cx, shape, *child);
+        if (!shape)
+            return false;
+    }
+
+    if (!SetClassAndProto(cx, obj, &PlainObject::class_, proto))
+        return false;
+
+    // Any failures after this point will leave the object as a mutant, and we
+    // can't recover.
+
+    RootedPlainObject nobj(cx, &obj->as<PlainObject>());
+    if (!nobj->setLastProperty(cx, nobj, shape))
+        CrashAtUnhandlableOOM("UnboxedPlainObject::convertToNative");
+
+    for (size_t i = 0; i < values.length(); i++)
+        nobj->initSlot(i, values[i]);
+
+    return true;
+}
+
+/* static */
+UnboxedPlainObject *
+UnboxedPlainObject::create(JSContext *cx, HandleTypeObject type, NewObjectKind newKind)
+{
+    MOZ_ASSERT(type->clasp() == &class_);
+    gc::AllocKind allocKind = type->unboxedLayout().getAllocKind();
+
+    UnboxedPlainObject *res = NewObjectWithType<UnboxedPlainObject>(cx, type, cx->global(),
+                                                                    allocKind, newKind);
+    if (!res)
+        return nullptr;
+
+    // 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) {
+        uint8_t *data = res->data();
+        while (*list != -1) {
+            HeapPtrString *heap = reinterpret_cast<HeapPtrString *>(data + *list);
+            heap->init(cx->names().empty);
+            list++;
+        }
+        list++;
+        while (*list != -1) {
+            HeapPtrObject *heap = reinterpret_cast<HeapPtrObject *>(data + *list);
+            heap->init(nullptr);
+            list++;
+        }
+        // Unboxed objects don't have Values to initialize.
+        MOZ_ASSERT(*(list + 1) == -1);
+    }
+
+    return res;
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_lookupGeneric(JSContext *cx, HandleObject obj,
+                                      HandleId id, MutableHandleObject objp,
+                                      MutableHandleShape propp)
+{
+    if (obj->as<UnboxedPlainObject>().layout().lookup(id)) {
+        MarkNonNativePropertyFound(propp);
+        objp.set(obj);
+        return true;
+    }
+
+    RootedObject proto(cx, obj->getProto());
+    if (!proto) {
+        objp.set(nullptr);
+        propp.set(nullptr);
+        return true;
+    }
+
+    return LookupProperty(cx, proto, id, objp, propp);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_lookupProperty(JSContext *cx, HandleObject obj,
+                                       HandlePropertyName name,
+                                       MutableHandleObject objp,
+                                       MutableHandleShape propp)
+{
+    RootedId id(cx, NameToId(name));
+    return obj_lookupGeneric(cx, obj, id, objp, propp);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_lookupElement(JSContext *cx, HandleObject obj,
+                                      uint32_t index, MutableHandleObject objp,
+                                      MutableHandleShape propp)
+{
+    RootedId id(cx);
+    if (!IndexToId(cx, index, &id))
+        return false;
+    return obj_lookupGeneric(cx, obj, id, objp, propp);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_defineGeneric(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                                      PropertyOp getter, StrictPropertyOp setter, unsigned attrs)
+{
+    if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
+        return false;
+
+    return DefineProperty(cx, obj, id, v, getter, setter, attrs);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_defineProperty(JSContext *cx, HandleObject obj,
+                                       HandlePropertyName name, HandleValue v,
+                                       PropertyOp getter, StrictPropertyOp setter, unsigned attrs)
+{
+    Rooted<jsid> id(cx, NameToId(name));
+    return obj_defineGeneric(cx, obj, id, v, getter, setter, attrs);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_defineElement(JSContext *cx, HandleObject obj, uint32_t index, HandleValue v,
+                                      PropertyOp getter, StrictPropertyOp setter, unsigned attrs)
+{
+    AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter);
+    RootedId id(cx);
+    if (!IndexToId(cx, index, &id))
+        return false;
+    return obj_defineGeneric(cx, obj, id, v, getter, setter, attrs);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_getGeneric(JSContext *cx, HandleObject obj, HandleObject receiver,
+                                   HandleId id, MutableHandleValue vp)
+{
+    const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
+
+    if (const UnboxedLayout::Property *property = layout.lookup(id)) {
+        vp.set(obj->as<UnboxedPlainObject>().getValue(*property));
+        return true;
+    }
+
+    RootedObject proto(cx, obj->getProto());
+    if (!proto) {
+        vp.setUndefined();
+        return true;
+    }
+
+    return GetProperty(cx, proto, receiver, id, vp);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_getProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
+                                    HandlePropertyName name, MutableHandleValue vp)
+{
+    RootedId id(cx, NameToId(name));
+    return obj_getGeneric(cx, obj, receiver, id, vp);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_getElement(JSContext *cx, HandleObject obj, HandleObject receiver,
+                                   uint32_t index, MutableHandleValue vp)
+{
+    RootedId id(cx);
+    if (!IndexToId(cx, index, &id))
+        return false;
+    return obj_getGeneric(cx, obj, receiver, id, vp);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_setGeneric(JSContext *cx, HandleObject obj, HandleId id,
+                                   MutableHandleValue vp, bool strict)
+{
+    const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
+
+    if (const UnboxedLayout::Property *property = layout.lookup(id)) {
+        if (obj->as<UnboxedPlainObject>().setValue(cx, *property, vp))
+            return true;
+
+        if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
+            return false;
+        return SetProperty(cx, obj, obj, id, vp, strict);
+    }
+
+    RootedObject proto(cx, obj->getProto());
+    if (!proto) {
+        if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
+            return false;
+        return SetProperty(cx, obj, obj, id, vp, strict);
+    }
+
+    return SetProperty(cx, proto, obj, id, vp, strict);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_setProperty(JSContext *cx, HandleObject obj, HandlePropertyName name,
+                                    MutableHandleValue vp, bool strict)
+{
+    RootedId id(cx, NameToId(name));
+    return obj_setGeneric(cx, obj, id, vp, strict);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_setElement(JSContext *cx, HandleObject obj, uint32_t index,
+                                   MutableHandleValue vp, bool strict)
+{
+    RootedId id(cx);
+    if (!IndexToId(cx, index, &id))
+        return false;
+    return obj_setGeneric(cx, obj, id, vp, strict);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_getOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id,
+                                                 MutableHandle<JSPropertyDescriptor> desc)
+{
+    const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
+
+    if (const UnboxedLayout::Property *property = layout.lookup(id)) {
+        desc.value().set(obj->as<UnboxedPlainObject>().getValue(*property));
+        desc.setAttributes(JSPROP_ENUMERATE);
+        desc.object().set(obj);
+        return true;
+    }
+
+    desc.object().set(nullptr);
+    return true;
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_setGenericAttributes(JSContext *cx, HandleObject obj,
+                                             HandleId id, unsigned *attrsp)
+{
+    if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
+        return false;
+    return SetPropertyAttributes(cx, obj, id, attrsp);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_deleteGeneric(JSContext *cx, HandleObject obj, HandleId id, bool *succeeded)
+{
+    if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
+        return false;
+    return DeleteProperty(cx, obj, id, succeeded);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_watch(JSContext *cx, HandleObject obj, HandleId id, HandleObject callable)
+{
+    if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
+        return false;
+    return WatchProperty(cx, obj, id, callable);
+}
+
+/* static */ bool
+UnboxedPlainObject::obj_enumerate(JSContext *cx, HandleObject obj, AutoIdVector &properties)
+{
+    const UnboxedLayout::PropertyVector &unboxed = obj->as<UnboxedPlainObject>().layout().properties();
+    for (size_t i = 0; i < unboxed.length(); i++) {
+        if (!properties.append(NameToId(unboxed[i].name)))
+            return false;
+    }
+    return true;
+}
+
+const Class UnboxedPlainObject::class_ = {
+    "Object",
+    Class::NON_NATIVE | JSCLASS_IMPLEMENTS_BARRIERS,
+    nullptr,        /* addProperty */
+    nullptr,        /* delProperty */
+    nullptr,        /* getProperty */
+    nullptr,        /* setProperty */
+    nullptr,        /* enumerate   */
+    nullptr,        /* resolve     */
+    nullptr,        /* convert     */
+    nullptr,        /* finalize    */
+    nullptr,        /* call        */
+    nullptr,        /* hasInstance */
+    nullptr,        /* construct   */
+    UnboxedPlainObject::trace,
+    JS_NULL_CLASS_SPEC,
+    JS_NULL_CLASS_EXT,
+    {
+        UnboxedPlainObject::obj_lookupGeneric,
+        UnboxedPlainObject::obj_lookupProperty,
+        UnboxedPlainObject::obj_lookupElement,
+        UnboxedPlainObject::obj_defineGeneric,
+        UnboxedPlainObject::obj_defineProperty,
+        UnboxedPlainObject::obj_defineElement,
+        UnboxedPlainObject::obj_getGeneric,
+        UnboxedPlainObject::obj_getProperty,
+        UnboxedPlainObject::obj_getElement,
+        UnboxedPlainObject::obj_setGeneric,
+        UnboxedPlainObject::obj_setProperty,
+        UnboxedPlainObject::obj_setElement,
+        UnboxedPlainObject::obj_getOwnPropertyDescriptor,
+        UnboxedPlainObject::obj_setGenericAttributes,
+        UnboxedPlainObject::obj_deleteGeneric,
+        UnboxedPlainObject::obj_watch,
+        nullptr,   /* No unwatch needed, as watch() converts the object to native */
+        nullptr,   /* getElements */
+        UnboxedPlainObject::obj_enumerate,
+        nullptr, /* thisObject */
+    }
+};
+
+/////////////////////////////////////////////////////////////////////
+// API
+/////////////////////////////////////////////////////////////////////
+
+static bool
+UnboxedTypeIncludes(JSValueType supertype, JSValueType subtype)
+{
+    if (supertype == JSVAL_TYPE_DOUBLE && subtype == JSVAL_TYPE_INT32)
+        return true;
+    if (supertype == JSVAL_TYPE_OBJECT && subtype == JSVAL_TYPE_NULL)
+        return true;
+    return false;
+}
+
+bool
+js::TryConvertToUnboxedLayout(JSContext *cx, Shape *templateShape,
+                              types::TypeObject *type, types::PreliminaryObjectArray *objects)
+{
+    if (!cx->runtime()->options().unboxedObjects())
+        return true;
+
+    if (templateShape->slotSpan() == 0)
+        return true;
+
+    UnboxedLayout::PropertyVector properties;
+    if (!properties.appendN(UnboxedLayout::Property(), templateShape->slotSpan()))
+        return false;
+
+    size_t objectCount = 0;
+    for (size_t i = 0; i < types::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);
+
+        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;
+        }
+
+        for (size_t i = 0; i < templateShape->slotSpan(); i++) {
+            Value val = obj->as<PlainObject>().getSlot(i);
+
+            JSValueType &existing = properties[i].type;
+            JSValueType type = val.isDouble() ? JSVAL_TYPE_DOUBLE : val.extractNonDoubleType();
+
+            if (existing == JSVAL_TYPE_MAGIC || existing == type || UnboxedTypeIncludes(type, existing))
+                existing = type;
+            else if (!UnboxedTypeIncludes(existing, type))
+                return true;
+        }
+    }
+
+    if (objectCount <= 1) {
+        // If only one of the objects has been created, it is more likely to
+        // have new properties added later.
+        return true;
+    }
+
+    for (size_t i = 0; i < templateShape->slotSpan(); i++) {
+        // We can't use an unboxed representation if e.g. all the objects have
+        // a null value for one of the properties, as we can't decide what type
+        // it is supposed to have.
+        if (UnboxedTypeSize(properties[i].type) == 0)
+            return true;
+    }
+
+    // Fill in the names for all the object's properties.
+    for (Shape::Range<NoGC> r(templateShape); !r.empty(); r.popFront()) {
+        size_t slot = r.front().slot();
+        MOZ_ASSERT(!properties[slot].name);
+        properties[slot].name = JSID_TO_ATOM(r.front().propid())->asPropertyName();
+    }
+
+    // Fill in all the unboxed object's property offsets, ordering fields from the
+    // largest down to avoid alignment issues.
+    uint32_t offset = 0;
+
+    static const size_t typeSizes[] = { 8, 4, 1 };
+
+    Vector<int32_t, 8, SystemAllocPolicy> objectOffsets, stringOffsets;
+
+    DebugOnly<size_t> addedProperties = 0;
+    for (size_t i = 0; i < ArrayLength(typeSizes); i++) {
+        size_t size = typeSizes[i];
+        for (size_t j = 0; j < templateShape->slotSpan(); j++) {
+            JSValueType type = properties[j].type;
+            if (UnboxedTypeSize(type) == size) {
+                if (type == JSVAL_TYPE_OBJECT) {
+                    if (!objectOffsets.append(offset))
+                        return false;
+                } else if (type == JSVAL_TYPE_STRING) {
+                    if (!stringOffsets.append(offset))
+                        return false;
+                }
+                addedProperties++;
+                properties[j].offset = offset;
+                offset += size;
+            }
+        }
+    }
+    MOZ_ASSERT(addedProperties == templateShape->slotSpan());
+
+    // The entire object must be allocatable inline.
+    if (sizeof(JSObject) + offset > JSObject::MAX_BYTE_SIZE)
+        return true;
+
+    UnboxedLayout *layout = type->zone()->new_<UnboxedLayout>(properties, offset);
+    if (!layout)
+        return false;
+
+    // Construct the layout's trace list.
+    if (!objectOffsets.empty() || !stringOffsets.empty()) {
+        Vector<int32_t, 8, SystemAllocPolicy> entries;
+        if (!entries.appendAll(stringOffsets) ||
+            !entries.append(-1) ||
+            !entries.appendAll(objectOffsets) ||
+            !entries.append(-1) ||
+            !entries.append(-1))
+        {
+            return false;
+        }
+        int32_t *traceList = type->zone()->pod_malloc<int32_t>(entries.length());
+        if (!traceList)
+            return false;
+        PodCopy(traceList, entries.begin(), entries.length());
+        layout->setTraceList(traceList);
+    }
+
+    // We've determined that all the preliminary objects can use the new layout
+    // just constructed, so convert the existing type to be an
+    // UnboxedPlainObject rather than a PlainObject, and update the preliminary
+    // objects to use the new layout. Do the fallible stuff first before
+    // modifying any objects.
+
+    // Get an empty shape which we can use for the preliminary objects.
+    Shape *newShape = EmptyShape::getInitialShape(cx, &UnboxedPlainObject::class_,
+                                                  type->proto(),
+                                                  templateShape->getObjectMetadata(),
+                                                  templateShape->getObjectParent(),
+                                                  templateShape->getObjectFlags());
+    if (!newShape) {
+        cx->clearPendingException();
+        return false;
+    }
+
+    // Accumulate a list of all the properties in each preliminary object, and
+    // update their shapes.
+    Vector<Value, 0, SystemAllocPolicy> values;
+    if (!values.reserve(objectCount * templateShape->slotSpan()))
+        return false;
+    for (size_t i = 0; i < types::PreliminaryObjectArray::COUNT; i++) {
+        if (!objects->get(i))
+            continue;
+
+        RootedNativeObject obj(cx, &objects->get(i)->as<NativeObject>());
+        for (size_t j = 0; j < templateShape->slotSpan(); j++)
+            values.infallibleAppend(obj->getSlot(j));
+
+        // Clear the object to remove any dynamically allocated information.
+        NativeObject::clear(cx, obj);
+
+        obj->setLastPropertyMakeNonNative(newShape);
+    }
+
+    if (types::TypeNewScript *newScript = type->newScript())
+        layout->setNewScript(newScript);
+
+    type->setClasp(&UnboxedPlainObject::class_);
+    type->setUnboxedLayout(layout);
+
+    size_t valueCursor = 0;
+    for (size_t i = 0; i < types::PreliminaryObjectArray::COUNT; i++) {
+        if (!objects->get(i))
+            continue;
+        UnboxedPlainObject *obj = &objects->get(i)->as<UnboxedPlainObject>();
+        memset(obj->data(), 0, layout->size());
+        for (size_t j = 0; j < templateShape->slotSpan(); j++) {
+            Value v = values[valueCursor++];
+            JS_ALWAYS_TRUE(obj->setValue(cx, properties[j], v));
+        }
+    }
+
+    MOZ_ASSERT(valueCursor == values.length());
+    return true;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/vm/UnboxedObject.h
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef vm_UnboxedObject_h
+#define vm_UnboxedObject_h
+
+#include "jsgc.h"
+#include "jsinfer.h"
+#include "jsobj.h"
+
+namespace js {
+
+// Memory required for an unboxed value of a given type. Returns zero for types
+// which can't be used for unboxed objects.
+static inline size_t
+UnboxedTypeSize(JSValueType type)
+{
+    switch (type) {
+      case JSVAL_TYPE_BOOLEAN: return 1;
+      case JSVAL_TYPE_INT32:   return 4;
+      case JSVAL_TYPE_DOUBLE:  return 8;
+      case JSVAL_TYPE_STRING:  return sizeof(void *);
+      case JSVAL_TYPE_OBJECT:  return sizeof(void *);
+      default:                 return 0;
+    }
+}
+
+// Class describing the layout of an UnboxedPlainObject.
+class UnboxedLayout
+{
+  public:
+    struct Property {
+        PropertyName *name;
+        uint32_t offset;
+        JSValueType type;
+
+        Property()
+          : name(nullptr), offset(0), type(JSVAL_TYPE_MAGIC)
+        {}
+    };
+
+    typedef Vector<Property, 0, SystemAllocPolicy> PropertyVector;
+
+  private:
+    // All properties on objects with this layout, in enumeration order.
+    PropertyVector properties_;
+
+    // Byte size of the data for objects with this layout.
+    size_t size_;
+
+    // Any 'new' script information associated with this layout.
+    types::TypeNewScript *newScript_;
+
+    // List for use in tracing objects with this layout. This has the same
+    // structure as the trace list on a TypeDescr.
+    int32_t *traceList_;
+
+  public:
+    UnboxedLayout(const PropertyVector &properties, size_t size)
+      : size_(size), newScript_(nullptr), traceList_(nullptr)
+    {
+        properties_.appendAll(properties);
+    }
+
+    ~UnboxedLayout() {
+        js_delete(newScript_);
+        js_free(traceList_);
+    }
+
+    const PropertyVector &properties() const {
+        return properties_;
+    }
+
+    types::TypeNewScript *newScript() const {
+        return newScript_;
+    }
+
+    void setNewScript(types::TypeNewScript *newScript, bool writeBarrier = true);
+
+    const int32_t *traceList() const {
+        return traceList_;
+    }
+
+    void setTraceList(int32_t *traceList) {
+        traceList_ = traceList;
+    }
+
+    const Property *lookup(JSAtom *atom) const {
+        for (size_t i = 0; i < properties_.length(); i++) {
+            if (properties_[i].name == atom)
+                return &properties_[i];
+        }
+        return nullptr;
+    }
+
+    const Property *lookup(jsid id) const {
+        if (JSID_IS_STRING(id))
+            return lookup(JSID_TO_ATOM(id));
+        return nullptr;
+    }
+
+    size_t size() const {
+        return size_;
+    }
+
+    inline gc::AllocKind getAllocKind() const;
+
+    void trace(JSTracer *trc);
+
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+};
+
+// 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
+{
+    // Start of the inline data, which immediately follows the shape and type.
+    uint8_t data_[1];
+
+  public:
+    static const Class class_;
+
+    static bool obj_lookupGeneric(JSContext *cx, HandleObject obj,
+                                  HandleId id, MutableHandleObject objp,
+                                  MutableHandleShape propp);
+
+    static bool obj_lookupProperty(JSContext *cx, HandleObject obj,
+                                   HandlePropertyName name,
+                                   MutableHandleObject objp,
+                                   MutableHandleShape propp);
+
+    static bool obj_lookupElement(JSContext *cx, HandleObject obj,
+                                  uint32_t index, MutableHandleObject objp,
+                                  MutableHandleShape propp);
+
+    static bool obj_defineGeneric(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
+                                  PropertyOp getter, StrictPropertyOp setter, unsigned attrs);
+
+    static bool obj_defineProperty(JSContext *cx, HandleObject obj,
+                                   HandlePropertyName name, HandleValue v,
+                                   PropertyOp getter, StrictPropertyOp setter, unsigned attrs);
+
+    static bool obj_defineElement(JSContext *cx, HandleObject obj, uint32_t index, HandleValue v,
+                                  PropertyOp getter, StrictPropertyOp setter, unsigned attrs);
+
+    static bool obj_getGeneric(JSContext *cx, HandleObject obj, HandleObject receiver,
+                               HandleId id, MutableHandleValue vp);
+
+    static bool obj_getProperty(JSContext *cx, HandleObject obj, HandleObject receiver,
+                                HandlePropertyName name, MutableHandleValue vp);
+
+    static bool obj_getElement(JSContext *cx, HandleObject obj, HandleObject receiver,
+                               uint32_t index, MutableHandleValue vp);
+
+    static bool obj_setGeneric(JSContext *cx, HandleObject obj, HandleId id,
+                               MutableHandleValue vp, bool strict);
+    static bool obj_setProperty(JSContext *cx, HandleObject obj, HandlePropertyName name,
+                                MutableHandleValue vp, bool strict);
+    static bool obj_setElement(JSContext *cx, HandleObject obj, uint32_t index,
+                               MutableHandleValue vp, bool strict);
+
+    static bool obj_getOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id,
+                                             MutableHandle<JSPropertyDescriptor> desc);
+
+    static bool obj_setGenericAttributes(JSContext *cx, HandleObject obj,
+                                         HandleId id, unsigned *attrsp);
+
+    static bool obj_deleteGeneric(JSContext *cx, HandleObject obj, HandleId id, bool *succeeded);
+
+    static bool obj_enumerate(JSContext *cx, HandleObject obj, AutoIdVector &properties);
+    static bool obj_watch(JSContext *cx, HandleObject obj, HandleId id, HandleObject callable);
+
+    const UnboxedLayout &layout() const {
+        return type()->unboxedLayout();
+    }
+
+    uint8_t *data() {
+        return &data_[0];
+    }
+
+    bool setValue(JSContext *cx, const UnboxedLayout::Property &property, const Value &v);
+    Value getValue(const UnboxedLayout::Property &property);
+
+    bool convertToNative(JSContext *cx);
+
+    static UnboxedPlainObject *create(JSContext *cx, HandleTypeObject type, NewObjectKind newKind);
+
+    static void trace(JSTracer *trc, JSObject *object);
+
+    static size_t offsetOfData() {
+        return offsetof(UnboxedPlainObject, data_[0]);
+    }
+};
+
+// Try to construct an UnboxedLayout for each of the preliminary objects,
+// provided they all match the template shape. If successful, converts the
+// preliminary objects and their type to the new unboxed representation.
+bool
+TryConvertToUnboxedLayout(JSContext *cx, Shape *templateShape,
+                          types::TypeObject *type, types::PreliminaryObjectArray *objects);
+
+inline gc::AllocKind
+UnboxedLayout::getAllocKind() const
+{
+    return gc::GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + size());
+}
+
+} // namespace js
+
+#endif /* vm_UnboxedObject_h */