Bug 1133254 - Improve type information and Ion compilation when dealing with converted unboxed objects, r=jandem.
authorBrian Hackett <bhackett1024@gmail.com>
Sat, 21 Feb 2015 18:52:50 -0600
changeset 257321 456afb8e465072537d132444d1190a154d7226bf
parent 257320 84305980a93ca00645a551ee605303bddb56c5e0
child 257322 cef15f16f5a506e583c2137482ee5877b945ea08
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1133254
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1133254 - Improve type information and Ion compilation when dealing with converted unboxed objects, r=jandem.
js/public/TrackedOptimizationInfo.h
js/src/gc/Marking.cpp
js/src/jit/BaselineInspector.cpp
js/src/jit/BaselineInspector.h
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/IonAnalysis.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/LIR-Common.h
js/src/jit/LOpcodes.h
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jsobj.cpp
js/src/vm/ObjectGroup.h
js/src/vm/TypeInference.cpp
js/src/vm/TypeInference.h
js/src/vm/UnboxedObject.cpp
js/src/vm/UnboxedObject.h
--- a/js/public/TrackedOptimizationInfo.h
+++ b/js/public/TrackedOptimizationInfo.h
@@ -105,16 +105,18 @@ namespace JS {
     _(InconsistentFixedSlot,                                            \
       "property not in a consistent fixed slot")                        \
     _(NotObject,                                                        \
       "not definitely an object")                                       \
     _(NotStruct,                                                        \
       "not definitely a TypedObject struct")                            \
     _(NotUnboxed,                                                       \
       "not definitely an unboxed object")                               \
+    _(UnboxedConvertedToNative,                                         \
+      "unboxed object may have been converted")                         \
     _(StructNoField,                                                    \
       "struct doesn't definitely have field")                           \
     _(InconsistentFieldType,                                            \
       "unboxed property does not have consistent type")                 \
     _(InconsistentFieldOffset,                                          \
       "unboxed property does not have consistent offset")               \
     _(NeedsTypeBarrier,                                                 \
       "needs type barrier")                                             \
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1436,16 +1436,19 @@ ScanObjectGroup(GCMarker *gcmarker, Obje
         PushMarkStack(gcmarker, group->singleton());
 
     if (group->newScript())
         group->newScript()->trace(gcmarker);
 
     if (group->maybeUnboxedLayout())
         group->unboxedLayout().trace(gcmarker);
 
+    if (ObjectGroup *unboxedGroup = group->maybeOriginalUnboxedGroup())
+        PushMarkStack(gcmarker, unboxedGroup);
+
     if (TypeDescr *descr = group->maybeTypeDescr())
         PushMarkStack(gcmarker, descr);
 
     if (JSFunction *fun = group->maybeInterpretedFunction())
         PushMarkStack(gcmarker, fun);
 }
 
 static void
@@ -1464,16 +1467,21 @@ gc::MarkChildren(JSTracer *trc, ObjectGr
         MarkObject(trc, &group->singletonRaw(), "group_singleton");
 
     if (group->newScript())
         group->newScript()->trace(trc);
 
     if (group->maybeUnboxedLayout())
         group->unboxedLayout().trace(trc);
 
+    if (ObjectGroup *unboxedGroup = group->maybeOriginalUnboxedGroup()) {
+        MarkObjectGroupUnbarriered(trc, &unboxedGroup, "group_original_unboxed_group");
+        group->setOriginalUnboxedGroup(unboxedGroup);
+    }
+
     if (JSObject *descr = group->maybeTypeDescr()) {
         MarkObjectUnbarriered(trc, &descr, "group_type_descr");
         group->setTypeDescr(&descr->as<TypeDescr>());
     }
 
     if (JSObject *fun = group->maybeInterpretedFunction()) {
         MarkObjectUnbarriered(trc, &fun, "group_function");
         group->setInterpretedFunction(&fun->as<JSFunction>());
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -74,26 +74,41 @@ SetElemICInspector::sawTypedArrayWrite()
     // Check for a SetElem_TypedArray stub.
     for (ICStub *stub = icEntry_->firstStub(); stub; stub = stub->next()) {
         if (stub->isSetElem_TypedArray())
             return true;
     }
     return false;
 }
 
+template <typename S, typename T>
+static bool
+VectorAppendNoDuplicate(S &list, T value)
+{
+    for (size_t i = 0; i < list.length(); i++) {
+        if (list[i] == value)
+            return true;
+    }
+    return list.append(value);
+}
+
 bool
 BaselineInspector::maybeInfoForPropertyOp(jsbytecode *pc,
                                           ShapeVector &nativeShapes,
-                                          ObjectGroupVector &unboxedGroups)
+                                          ObjectGroupVector &unboxedGroups,
+                                          ObjectGroupVector &convertUnboxedGroups)
 {
     // Return lists of native shapes and unboxed objects seen by the baseline
     // IC for the current op. Empty lists indicate no shapes/types are known,
-    // or there was an uncacheable access.
+    // or there was an uncacheable access. convertUnboxedGroups is used for
+    // unboxed object groups which have been seen, but have had instances
+    // converted to native objects and should be eagerly converted by Ion.
     MOZ_ASSERT(nativeShapes.empty());
     MOZ_ASSERT(unboxedGroups.empty());
+    MOZ_ASSERT(convertUnboxedGroups.empty());
 
     if (!hasBaselineScript())
         return true;
 
     MOZ_ASSERT(isValidPC(pc));
     const ICEntry &entry = icEntryFromPC(pc);
 
     ICStub *stub = entry.firstStub();
@@ -109,37 +124,28 @@ BaselineInspector::maybeInfoForPropertyO
         } else if (stub->isSetProp_Unboxed()) {
             group = stub->toSetProp_Unboxed()->group();
         } else {
             nativeShapes.clear();
             unboxedGroups.clear();
             return true;
         }
 
-        // Don't add the same shape/group twice (this can happen if there are
-        // multiple SetProp_Native stubs with different ObjectGroups).
+        if (group && group->unboxedLayout().nativeGroup()) {
+            if (!VectorAppendNoDuplicate(convertUnboxedGroups, group))
+                return false;
+            shape = group->unboxedLayout().nativeShape();
+            group = nullptr;
+        }
+
         if (shape) {
-            bool found = false;
-            for (size_t i = 0; i < nativeShapes.length(); i++) {
-                if (nativeShapes[i] == shape) {
-                    found = true;
-                    break;
-                }
-            }
-            if (!found && !nativeShapes.append(shape))
+            if (!VectorAppendNoDuplicate(nativeShapes, shape))
                 return false;
         } else {
-            bool found = false;
-            for (size_t i = 0; i < unboxedGroups.length(); i++) {
-                if (unboxedGroups[i] == group) {
-                    found = true;
-                    break;
-                }
-            }
-            if (!found && !unboxedGroups.append(group))
+            if (!VectorAppendNoDuplicate(unboxedGroups, group))
                 return false;
         }
 
         stub = stub->next();
     }
 
     if (stub->isGetProp_Fallback()) {
         if (stub->toGetProp_Fallback()->hadUnoptimizableAccess()) {
--- a/js/src/jit/BaselineInspector.h
+++ b/js/src/jit/BaselineInspector.h
@@ -91,17 +91,18 @@ class BaselineInspector
     ICStub *monomorphicStub(jsbytecode *pc);
     bool dimorphicStub(jsbytecode *pc, ICStub **pfirst, ICStub **psecond);
 
   public:
     typedef Vector<Shape *, 4, JitAllocPolicy> ShapeVector;
     typedef Vector<ObjectGroup *, 4, JitAllocPolicy> ObjectGroupVector;
     bool maybeInfoForPropertyOp(jsbytecode *pc,
                                 ShapeVector &nativeShapes,
-                                ObjectGroupVector &unboxedGroups);
+                                ObjectGroupVector &unboxedGroups,
+                                ObjectGroupVector &convertUnboxedGroups);
 
     SetElemICInspector setElemICInspector(jsbytecode *pc) {
         return makeICInspector<SetElemICInspector>(pc, ICStub::SetElem_Fallback);
     }
 
     MIRType expectedResultType(jsbytecode *pc);
     MCompare::CompareType expectedCompareType(jsbytecode *pc);
     MIRType expectedBinaryArithSpecialization(jsbytecode *pc);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -6633,16 +6633,33 @@ CodeGenerator::visitStoreUnboxedPointer(
         Address address(elements, ToInt32(index) * sizeof(uintptr_t) + offsetAdjustment);
         StoreUnboxedPointer(masm, address, type, value);
     } else {
         BaseIndex address(elements, ToRegister(index), ScalePointer, offsetAdjustment);
         StoreUnboxedPointer(masm, address, type, value);
     }
 }
 
+typedef bool (*ConvertUnboxedObjectToNativeFn)(JSContext *, JSObject *);
+static const VMFunction ConvertUnboxedObjectToNativeInfo =
+    FunctionInfo<ConvertUnboxedObjectToNativeFn>(UnboxedPlainObject::convertToNative);
+
+void
+CodeGenerator::visitConvertUnboxedObjectToNative(LConvertUnboxedObjectToNative *lir)
+{
+    Register object = ToRegister(lir->getOperand(0));
+
+    OutOfLineCode *ool = oolCallVM(ConvertUnboxedObjectToNativeInfo, lir,
+                                   (ArgList(), object), StoreNothing());
+
+    masm.branchPtr(Assembler::Equal, Address(object, JSObject::offsetOfGroup()),
+                   ImmGCPtr(lir->mir()->group()), ool->entry());
+    masm.bind(ool->rejoin());
+}
+
 typedef bool (*ArrayPopShiftFn)(JSContext *, HandleObject, MutableHandleValue);
 static const VMFunction ArrayPopDenseInfo = FunctionInfo<ArrayPopShiftFn>(jit::ArrayPopDense);
 static const VMFunction ArrayShiftDenseInfo = FunctionInfo<ArrayPopShiftFn>(jit::ArrayShiftDense);
 
 void
 CodeGenerator::emitArrayPopShift(LInstruction *lir, const MArrayPopShift *mir, Register obj,
                                  Register elementsTemp, Register lengthTemp, TypedOrValueRegister out)
 {
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -246,16 +246,17 @@ class CodeGenerator : public CodeGenerat
     void visitLoadUnboxedPointerV(LLoadUnboxedPointerV *lir);
     void visitLoadUnboxedPointerT(LLoadUnboxedPointerT *lir);
     void visitUnboxObjectOrNull(LUnboxObjectOrNull *lir);
     void visitStoreElementT(LStoreElementT *lir);
     void visitStoreElementV(LStoreElementV *lir);
     void visitStoreElementHoleT(LStoreElementHoleT *lir);
     void visitStoreElementHoleV(LStoreElementHoleV *lir);
     void visitStoreUnboxedPointer(LStoreUnboxedPointer *lir);
+    void visitConvertUnboxedObjectToNative(LConvertUnboxedObjectToNative *lir);
     void emitArrayPopShift(LInstruction *lir, const MArrayPopShift *mir, Register obj,
                            Register elementsTemp, Register lengthTemp, TypedOrValueRegister out);
     void visitArrayPopShiftV(LArrayPopShiftV *lir);
     void visitArrayPopShiftT(LArrayPopShiftT *lir);
     void emitArrayPush(LInstruction *lir, const MArrayPush *mir, Register obj,
                        ConstantOrRegister value, Register elementsTemp, Register length);
     void visitArrayPushV(LArrayPushV *lir);
     void visitArrayPushT(LArrayPushT *lir);
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -2603,16 +2603,18 @@ TryOptimizeLoadObjectOrNull(MDefinition 
 
 static inline MDefinition *
 PassthroughOperand(MDefinition *def)
 {
     if (def->isConvertElementsToDoubles())
         return def->toConvertElementsToDoubles()->elements();
     if (def->isMaybeCopyElementsForWrite())
         return def->toMaybeCopyElementsForWrite()->object();
+    if (def->isConvertUnboxedObjectToNative())
+        return def->toConvertUnboxedObjectToNative()->object();
     return nullptr;
 }
 
 // Eliminate checks which are redundant given each other or other instructions.
 //
 // A type barrier is considered redundant if all missing types have been tested
 // for by earlier control instructions.
 //
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -9181,17 +9181,18 @@ IonBuilder::jsop_rest()
     MSetInitializedLength *initLength = MSetInitializedLength::New(alloc(), elements, index);
     current->add(initLength);
 
     current->push(array);
     return true;
 }
 
 uint32_t
-IonBuilder::getDefiniteSlot(TemporaryTypeSet *types, PropertyName *name)
+IonBuilder::getDefiniteSlot(TemporaryTypeSet *types, PropertyName *name, uint32_t *pnfixed,
+                            BaselineInspector::ObjectGroupVector &convertUnboxedGroups)
 {
     if (!types || types->unknownObject()) {
         trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
         return UINT32_MAX;
     }
 
     // Watch for types which the new script properties analysis has not been
     // performed on yet. Normally this is done after a small number of the
@@ -9230,29 +9231,48 @@ IonBuilder::getDefiniteSlot(TemporaryTyp
             return UINT32_MAX;
         }
 
         if (key->isSingleton()) {
             trackOptimizationOutcome(TrackedOutcome::Singleton);
             return UINT32_MAX;
         }
 
+        // If we encounter a group for an unboxed property which has a
+        // corresponding native group, look for a definite slot in that native
+        // group, and force conversion of incoming objects to the native group.
+        if (key->isGroup() && key->group()->maybeUnboxedLayout()) {
+            if (ObjectGroup *nativeGroup = key->group()->unboxedLayout().nativeGroup()) {
+                if (!convertUnboxedGroups.append(key->group()))
+                    CrashAtUnhandlableOOM("IonBuilder::getDefiniteSlot");
+                key = TypeSet::ObjectKey::get(nativeGroup);
+            }
+        }
+
         HeapTypeSetKey property = key->property(NameToId(name));
         if (!property.maybeTypes() ||
             !property.maybeTypes()->definiteProperty() ||
             property.nonData(constraints()))
         {
             trackOptimizationOutcome(TrackedOutcome::NotFixedSlot);
             return UINT32_MAX;
         }
 
+        // Definite slots will always be fixed slots when they are in the
+        // allowable range for fixed slots, except for objects which were
+        // converted from unboxed objects and have a smaller allocation size.
+        size_t nfixed = NativeObject::MAX_FIXED_SLOTS;
+        if (ObjectGroup *group = key->group()->maybeOriginalUnboxedGroup())
+            nfixed = gc::GetGCKindSlots(group->unboxedLayout().getAllocKind());
+
         uint32_t propertySlot = property.maybeTypes()->definiteSlot();
         if (slot == UINT32_MAX) {
             slot = propertySlot;
-        } else if (slot != propertySlot) {
+            *pnfixed = nfixed;
+        } else if (slot != propertySlot || nfixed != *pnfixed) {
             trackOptimizationOutcome(TrackedOutcome::InconsistentFixedSlot);
             return UINT32_MAX;
         }
     }
 
     return slot;
 }
 
@@ -9288,16 +9308,23 @@ IonBuilder::getUnboxedOffset(TemporaryTy
         }
 
         const UnboxedLayout::Property *property = layout->lookup(name);
         if (!property) {
             trackOptimizationOutcome(TrackedOutcome::StructNoField);
             return UINT32_MAX;
         }
 
+        if (layout->nativeGroup()) {
+            trackOptimizationOutcome(TrackedOutcome::UnboxedConvertedToNative);
+            return UINT32_MAX;
+        }
+
+        key->watchStateChangeForUnboxedConvertedToNative(constraints());
+
         if (offset == UINT32_MAX) {
             offset = property->offset;
             *punboxedType = property->type;
         } else if (offset != property->offset) {
             trackOptimizationOutcome(TrackedOutcome::InconsistentFieldOffset);
             return UINT32_MAX;
         } else if (*punboxedType != property->type) {
             trackOptimizationOutcome(TrackedOutcome::InconsistentFieldType);
@@ -10023,40 +10050,56 @@ IonBuilder::getPropTryComplexPropOfTyped
     LinearSum byteOffset(alloc());
     if (!byteOffset.add(fieldOffset))
         setForceAbort();
 
     return pushDerivedTypedObject(emitted, typedObj, byteOffset,
                                   fieldPrediction, fieldTypeObj);
 }
 
+MDefinition *
+IonBuilder::convertUnboxedObjects(MDefinition *obj,
+                                  const BaselineInspector::ObjectGroupVector &list)
+{
+    for (size_t i = 0; i < list.length(); i++) {
+        obj = MConvertUnboxedObjectToNative::New(alloc(), obj, list[i]);
+        current->add(obj->toInstruction());
+    }
+    return obj;
+}
+
 bool
 IonBuilder::getPropTryDefiniteSlot(bool *emitted, MDefinition *obj, PropertyName *name,
                                    BarrierKind barrier, TemporaryTypeSet *types)
 {
     MOZ_ASSERT(*emitted == false);
 
-    uint32_t slot = getDefiniteSlot(obj->resultTypeSet(), name);
+    BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
+
+    uint32_t nfixed;
+    uint32_t slot = getDefiniteSlot(obj->resultTypeSet(), name, &nfixed, convertUnboxedGroups);
     if (slot == UINT32_MAX)
         return true;
 
     if (obj->type() != MIRType_Object) {
         MGuardObject *guard = MGuardObject::New(alloc(), obj);
         current->add(guard);
         obj = guard;
     }
 
+    obj = convertUnboxedObjects(obj, convertUnboxedGroups);
+
     MInstruction *load;
-    if (slot < NativeObject::MAX_FIXED_SLOTS) {
+    if (slot < nfixed) {
         load = MLoadFixedSlot::New(alloc(), obj, slot);
     } else {
         MInstruction *slots = MSlots::New(alloc(), obj);
         current->add(slots);
 
-        load = MLoadSlot::New(alloc(), slots, slot - NativeObject::MAX_FIXED_SLOTS);
+        load = MLoadSlot::New(alloc(), slots, slot - nfixed);
     }
 
     if (barrier == BarrierKind::NoBarrier)
         load->setResultType(types->getKnownMIRType());
 
     current->add(load);
     current->push(load);
 
@@ -10368,23 +10411,25 @@ IonBuilder::getPropTryInlineAccess(bool 
     MOZ_ASSERT(*emitted == false);
 
     if (obj->type() != MIRType_Object) {
         trackOptimizationOutcome(TrackedOutcome::NotObject);
         return true;
     }
 
     BaselineInspector::ShapeVector nativeShapes(alloc());
-    BaselineInspector::ObjectGroupVector unboxedGroups(alloc());
-    if (!inspector->maybeInfoForPropertyOp(pc, nativeShapes, unboxedGroups))
+    BaselineInspector::ObjectGroupVector unboxedGroups(alloc()), convertUnboxedGroups(alloc());
+    if (!inspector->maybeInfoForPropertyOp(pc, nativeShapes, unboxedGroups, convertUnboxedGroups))
         return false;
 
     if (!canInlinePropertyOpShapes(nativeShapes, unboxedGroups))
         return true;
 
+    obj = convertUnboxedObjects(obj, convertUnboxedGroups);
+
     MIRType rvalType = types->getKnownMIRType();
     if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType))
         rvalType = MIRType_Value;
 
     if (nativeShapes.length() == 1 && unboxedGroups.empty()) {
         // In the monomorphic case, use separate ShapeGuard and LoadSlot
         // instructions.
         spew("Inlining monomorphic GETPROP");
@@ -10912,17 +10957,20 @@ IonBuilder::setPropTryDefiniteSlot(bool 
 {
     MOZ_ASSERT(*emitted == false);
 
     if (barrier) {
         trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
         return true;
     }
 
-    uint32_t slot = getDefiniteSlot(obj->resultTypeSet(), name);
+    BaselineInspector::ObjectGroupVector convertUnboxedGroups(alloc());
+
+    uint32_t nfixed;
+    uint32_t slot = getDefiniteSlot(obj->resultTypeSet(), name, &nfixed, convertUnboxedGroups);
     if (slot == UINT32_MAX)
         return true;
 
     bool writeBarrier = false;
     for (size_t i = 0; i < obj->resultTypeSet()->getObjectCount(); i++) {
         TypeSet::ObjectKey *key = obj->resultTypeSet()->getObject(i);
         if (!key)
             continue;
@@ -10930,26 +10978,28 @@ IonBuilder::setPropTryDefiniteSlot(bool 
         HeapTypeSetKey property = key->property(NameToId(name));
         if (property.nonWritable(constraints())) {
             trackOptimizationOutcome(TrackedOutcome::NonWritableProperty);
             return true;
         }
         writeBarrier |= property.needsBarrier(constraints());
     }
 
+    obj = convertUnboxedObjects(obj, convertUnboxedGroups);
+
     MInstruction *store;
-    if (slot < NativeObject::MAX_FIXED_SLOTS) {
+    if (slot < nfixed) {
         store = MStoreFixedSlot::New(alloc(), obj, slot, value);
         if (writeBarrier)
             store->toStoreFixedSlot()->setNeedsBarrier();
     } else {
         MInstruction *slots = MSlots::New(alloc(), obj);
         current->add(slots);
 
-        store = MStoreSlot::New(alloc(), slots, slot - NativeObject::MAX_FIXED_SLOTS, value);
+        store = MStoreSlot::New(alloc(), slots, slot - nfixed, value);
         if (writeBarrier)
             store->toStoreSlot()->setNeedsBarrier();
     }
 
     current->add(store);
     current->push(value);
 
     if (!resumeAfter(store))
@@ -11048,23 +11098,25 @@ IonBuilder::setPropTryInlineAccess(bool 
     MOZ_ASSERT(*emitted == false);
 
     if (barrier) {
         trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
         return true;
     }
 
     BaselineInspector::ShapeVector nativeShapes(alloc());
-    BaselineInspector::ObjectGroupVector unboxedGroups(alloc());
-    if (!inspector->maybeInfoForPropertyOp(pc, nativeShapes, unboxedGroups))
+    BaselineInspector::ObjectGroupVector unboxedGroups(alloc()), convertUnboxedGroups(alloc());
+    if (!inspector->maybeInfoForPropertyOp(pc, nativeShapes, unboxedGroups, convertUnboxedGroups))
         return false;
 
     if (!canInlinePropertyOpShapes(nativeShapes, unboxedGroups))
         return true;
 
+    obj = convertUnboxedObjects(obj, convertUnboxedGroups);
+
     if (nativeShapes.length() == 1 && unboxedGroups.empty()) {
         spew("Inlining monomorphic SETPROP");
 
         // The Baseline IC was monomorphic, so we inline the property access as
         // long as the shape is not in dictionary mode. We cannot be sure
         // that the shape is still a lastProperty, and calling Shape::search
         // on dictionary mode shapes that aren't lastProperty is invalid.
         Shape *objShape = nativeShapes[0];
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -884,17 +884,20 @@ class IonBuilder
                                   TemporaryTypeSet *objTypes,
                                   TemporaryTypeSet *pushedTypes);
 
     MGetPropertyCache *getInlineableGetPropertyCache(CallInfo &callInfo);
 
     JSObject *testSingletonProperty(JSObject *obj, PropertyName *name);
     bool testSingletonPropertyTypes(MDefinition *obj, JSObject *singleton, PropertyName *name,
                                     bool *testObject, bool *testString);
-    uint32_t getDefiniteSlot(TemporaryTypeSet *types, PropertyName *name);
+    uint32_t getDefiniteSlot(TemporaryTypeSet *types, PropertyName *name, uint32_t *pnfixed,
+                             BaselineInspector::ObjectGroupVector &convertUnboxedGroups);
+    MDefinition *convertUnboxedObjects(MDefinition *obj,
+                                       const BaselineInspector::ObjectGroupVector &list);
     uint32_t getUnboxedOffset(TemporaryTypeSet *types, PropertyName *name,
                               JSValueType *punboxedType);
     MInstruction *loadUnboxedProperty(MDefinition *obj, size_t offset, JSValueType unboxedType,
                                       BarrierKind barrier, TemporaryTypeSet *types);
     MInstruction *storeUnboxedProperty(MDefinition *obj, size_t offset, JSValueType unboxedType,
                                        MDefinition *value);
     bool freezePropTypeSets(TemporaryTypeSet *types,
                             JSObject *foundProto, PropertyName *name);
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -4656,16 +4656,32 @@ class LStoreUnboxedPointer : public LIns
     const LAllocation *index() {
         return getOperand(1);
     }
     const LAllocation *value() {
         return getOperand(2);
     }
 };
 
+// If necessary, convert an unboxed object in a particular group to its native
+// representation.
+class LConvertUnboxedObjectToNative : public LInstructionHelper<0, 1, 0>
+{
+  public:
+    LIR_HEADER(ConvertUnboxedObjectToNative)
+
+    explicit LConvertUnboxedObjectToNative(const LAllocation &object) {
+        setOperand(0, object);
+    }
+
+    MConvertUnboxedObjectToNative *mir() {
+        return mir_->toConvertUnboxedObjectToNative();
+    }
+};
+
 class LArrayPopShiftV : public LInstructionHelper<BOX_PIECES, 1, 2>
 {
   public:
     LIR_HEADER(ArrayPopShiftV)
 
     LArrayPopShiftV(const LAllocation &object, const LDefinition &temp0, const LDefinition &temp1) {
         setOperand(0, object);
         setTemp(0, temp0);
--- a/js/src/jit/LOpcodes.h
+++ b/js/src/jit/LOpcodes.h
@@ -218,16 +218,17 @@
     _(LoadElementT)                 \
     _(LoadElementHole)              \
     _(LoadUnboxedPointerV)          \
     _(LoadUnboxedPointerT)          \
     _(UnboxObjectOrNull)            \
     _(StoreElementV)                \
     _(StoreElementT)                \
     _(StoreUnboxedPointer)          \
+    _(ConvertUnboxedObjectToNative) \
     _(ArrayPopShiftV)               \
     _(ArrayPopShiftT)               \
     _(ArrayPushV)                   \
     _(ArrayPushT)                   \
     _(ArrayConcat)                  \
     _(ArrayJoin)                    \
     _(StoreElementHoleV)            \
     _(StoreElementHoleT)            \
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2762,16 +2762,24 @@ LIRGenerator::visitStoreUnboxedString(MS
     const LAllocation index = useRegisterOrConstant(ins->index());
     const LAllocation value = useRegisterOrNonDoubleConstant(ins->value());
 
     LInstruction *lir = new(alloc()) LStoreUnboxedPointer(elements, index, value);
     add(lir, ins);
 }
 
 void
+LIRGenerator::visitConvertUnboxedObjectToNative(MConvertUnboxedObjectToNative *ins)
+{
+    LInstruction *check = new(alloc()) LConvertUnboxedObjectToNative(useRegister(ins->object()));
+    add(check, ins);
+    assignSafepoint(check, ins);
+}
+
+void
 LIRGenerator::visitEffectiveAddress(MEffectiveAddress *ins)
 {
     define(new(alloc()) LEffectiveAddress(useRegister(ins->base()), useRegister(ins->index())), ins);
 }
 
 void
 LIRGenerator::visitArrayPopShift(MArrayPopShift *ins)
 {
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -192,16 +192,17 @@ class LIRGenerator : public LIRGenerator
     void visitLoadElement(MLoadElement *ins);
     void visitLoadElementHole(MLoadElementHole *ins);
     void visitLoadUnboxedObjectOrNull(MLoadUnboxedObjectOrNull *ins);
     void visitLoadUnboxedString(MLoadUnboxedString *ins);
     void visitStoreElement(MStoreElement *ins);
     void visitStoreElementHole(MStoreElementHole *ins);
     void visitStoreUnboxedObjectOrNull(MStoreUnboxedObjectOrNull *ins);
     void visitStoreUnboxedString(MStoreUnboxedString *ins);
+    void visitConvertUnboxedObjectToNative(MConvertUnboxedObjectToNative *ins);
     void visitEffectiveAddress(MEffectiveAddress *ins);
     void visitArrayPopShift(MArrayPopShift *ins);
     void visitArrayPush(MArrayPush *ins);
     void visitArrayConcat(MArrayConcat *ins);
     void visitArrayJoin(MArrayJoin *ins);
     void visitLoadTypedArrayElement(MLoadTypedArrayElement *ins);
     void visitLoadTypedArrayElementHole(MLoadTypedArrayElementHole *ins);
     void visitLoadTypedArrayElementStatic(MLoadTypedArrayElementStatic *ins);
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -4825,11 +4825,22 @@ jit::PropertyWriteNeedsTypeBarrier(TempA
 
         if ((property.maybeTypes() && !property.maybeTypes()->empty()) || excluded)
             return true;
         excluded = key;
     }
 
     MOZ_ASSERT(excluded);
 
+    // If the excluded object is a group with an unboxed layout, make sure it
+    // does not have a corresponding native group. Objects with the native
+    // group might appear even though they are not in the type set.
+    if (excluded->isGroup()) {
+        if (UnboxedLayout *layout = excluded->group()->maybeUnboxedLayout()) {
+            if (layout->nativeGroup())
+                return true;
+            excluded->watchStateChangeForUnboxedConvertedToNative(constraints);
+        }
+    }
+
     *pobj = AddGroupGuard(alloc, current, *pobj, excluded, /* bailOnEquality = */ true);
     return false;
 }
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -22,16 +22,17 @@
 #include "jit/JitAllocPolicy.h"
 #include "jit/MacroAssembler.h"
 #include "jit/MOpcodes.h"
 #include "jit/TypedObjectPrediction.h"
 #include "jit/TypePolicy.h"
 #include "vm/ArrayObject.h"
 #include "vm/ScopeObject.h"
 #include "vm/TypedArrayCommon.h"
+#include "vm/UnboxedObject.h"
 
 // Undo windows.h damage on Win64
 #undef MemoryBarrier
 
 namespace js {
 
 class StringObject;
 
@@ -8417,16 +8418,70 @@ class MStoreUnboxedString
     AliasSet getAliasSet() const MOZ_OVERRIDE {
         // Use AliasSet::Element for reference typed object fields.
         return AliasSet::Store(AliasSet::Element);
     }
 
     ALLOW_CLONE(MStoreUnboxedString)
 };
 
+// Passes through an object, after ensuring it is converted from an unboxed
+// object to a native representation.
+class MConvertUnboxedObjectToNative
+  : public MUnaryInstruction,
+    public SingleObjectPolicy::Data
+{
+    AlwaysTenured<ObjectGroup *> group_;
+
+    explicit MConvertUnboxedObjectToNative(MDefinition *obj, ObjectGroup *group)
+      : MUnaryInstruction(obj),
+        group_(group)
+    {
+        setGuard();
+        setMovable();
+        setResultType(MIRType_Object);
+    }
+
+  public:
+    INSTRUCTION_HEADER(ConvertUnboxedObjectToNative)
+
+    static MConvertUnboxedObjectToNative *New(TempAllocator &alloc, MDefinition *obj,
+                                              ObjectGroup *group) {
+        return new(alloc) MConvertUnboxedObjectToNative(obj, group);
+    }
+
+    MDefinition *object() const {
+        return getOperand(0);
+    }
+    ObjectGroup *group() const {
+        return group_;
+    }
+    bool congruentTo(const MDefinition *ins) const MOZ_OVERRIDE {
+        if (!congruentIfOperandsEqual(ins))
+            return false;
+        return ins->toConvertUnboxedObjectToNative()->group() == group();
+    }
+    AliasSet getAliasSet() const MOZ_OVERRIDE {
+        // This instruction can read and write to all parts of the object, but
+        // is marked as non-effectful so it can be consolidated by LICM and GVN
+        // and avoid inhibiting other optimizations.
+        //
+        // This is valid to do because when unboxed objects might have a native
+        // group they can be converted to, we do not optimize accesses to the
+        // unboxed objects and do not guard on their group or shape (other than
+        // in this opcode).
+        //
+        // Later accesses can assume the object has a native representation
+        // and optimize accordingly. Those accesses cannot be reordered before
+        // this instruction, however. This is prevented by chaining this
+        // instruction with the object itself, in the same way as MBoundsCheck.
+        return AliasSet::None();
+    }
+};
+
 // Array.prototype.pop or Array.prototype.shift on a dense array.
 class MArrayPopShift
   : public MUnaryInstruction,
     public SingleObjectPolicy::Data
 {
   public:
     enum Mode {
         Pop,
@@ -9768,16 +9823,21 @@ class MGuardShape
     MGuardShape(MDefinition *obj, Shape *shape, BailoutKind bailoutKind)
       : MUnaryInstruction(obj),
         shape_(shape),
         bailoutKind_(bailoutKind)
     {
         setGuard();
         setMovable();
         setResultType(MIRType_Object);
+
+        // Disallow guarding on unboxed object shapes. The group is better to
+        // guard on, and guarding on the shape can interact badly with
+        // MConvertUnboxedObjectToNative.
+        MOZ_ASSERT(shape->getObjectClass() != &UnboxedPlainObject::class_);
     }
 
   public:
     INSTRUCTION_HEADER(GuardShape)
 
     static MGuardShape *New(TempAllocator &alloc, MDefinition *obj, Shape *shape,
                             BailoutKind bailoutKind)
     {
@@ -9864,16 +9924,20 @@ class MGuardObjectGroup
       : MUnaryInstruction(obj),
         group_(group),
         bailOnEquality_(bailOnEquality),
         bailoutKind_(bailoutKind)
     {
         setGuard();
         setMovable();
         setResultType(MIRType_Object);
+
+        // Unboxed groups which might be converted to natives can't be guarded
+        // on, due to MConvertUnboxedObjectToNative.
+        MOZ_ASSERT_IF(group->maybeUnboxedLayout(), !group->unboxedLayout().nativeGroup());
     }
 
   public:
     INSTRUCTION_HEADER(GuardObjectGroup)
 
     static MGuardObjectGroup *New(TempAllocator &alloc, MDefinition *obj, ObjectGroup *group,
                                   bool bailOnEquality, BailoutKind bailoutKind) {
         return new(alloc) MGuardObjectGroup(obj, group, bailOnEquality, bailoutKind);
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -179,16 +179,17 @@ namespace jit {
     _(LoadElement)                                                          \
     _(LoadElementHole)                                                      \
     _(LoadUnboxedObjectOrNull)                                              \
     _(LoadUnboxedString)                                                    \
     _(StoreElement)                                                         \
     _(StoreElementHole)                                                     \
     _(StoreUnboxedObjectOrNull)                                             \
     _(StoreUnboxedString)                                                   \
+    _(ConvertUnboxedObjectToNative)                                         \
     _(ArrayPopShift)                                                        \
     _(ArrayPush)                                                            \
     _(ArrayConcat)                                                          \
     _(ArrayJoin)                                                            \
     _(LoadTypedArrayElement)                                                \
     _(LoadTypedArrayElementHole)                                            \
     _(LoadTypedArrayElementStatic)                                          \
     _(StoreTypedArrayElement)                                               \
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -895,17 +895,17 @@ js::StandardDefineProperty(JSContext *cx
     if (obj->is<ArrayObject>()) {
         Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
         return DefinePropertyOnArray(cx, arr, id, desc, throwError, rval);
     }
 
     if (IsAnyTypedArray(obj))
         return DefinePropertyOnTypedArray(cx, obj, id, desc, throwError, rval);
 
-    if (obj->is<UnboxedPlainObject>() && !obj->as<UnboxedPlainObject>().convertToNative(cx))
+    if (obj->is<UnboxedPlainObject>() && !UnboxedPlainObject::convertToNative(cx, obj))
         return false;
 
     if (obj->getOps()->lookupProperty) {
         if (obj->is<ProxyObject>()) {
             Rooted<PropertyDescriptor> pd(cx);
             desc.populatePropertyDescriptor(obj, &pd);
             pd.object().set(obj);
             return Proxy::defineProperty(cx, obj, id, &pd);
--- a/js/src/vm/ObjectGroup.h
+++ b/js/src/vm/ObjectGroup.h
@@ -214,16 +214,21 @@ class ObjectGroup : public gc::TenuredCe
         // function, the addendum stores a TypeNewScript.
         Addendum_NewScript,
 
         // When objects in this group have an unboxed representation, the
         // addendum stores an UnboxedLayout (which might have a TypeNewScript
         // as well, if the group is also constructed using 'new').
         Addendum_UnboxedLayout,
 
+        // If this group is used by objects that have been converted from an
+        // unboxed representation, the addendum points to the original unboxed
+        // group.
+        Addendum_OriginalUnboxedGroup,
+
         // 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_;
 
@@ -288,16 +293,26 @@ class ObjectGroup : public gc::TenuredCe
         MOZ_ASSERT(addendumKind() == Addendum_UnboxedLayout);
         return *maybeUnboxedLayout();
     }
 
     void setUnboxedLayout(UnboxedLayout *layout) {
         setAddendum(Addendum_UnboxedLayout, layout);
     }
 
+    ObjectGroup *maybeOriginalUnboxedGroup() const {
+        if (addendumKind() == Addendum_OriginalUnboxedGroup)
+            return reinterpret_cast<ObjectGroup *>(addendum_);
+        return nullptr;
+    }
+
+    void setOriginalUnboxedGroup(ObjectGroup *group) {
+        setAddendum(Addendum_OriginalUnboxedGroup, group);
+    }
+
     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;
     }
 
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -1704,16 +1704,41 @@ class ConstraintDataFreezeObjectForTyped
     }
 
     bool shouldSweep() {
         // Note: |viewData| is only used for equality testing.
         return false;
     }
 };
 
+// Constraint which triggers recompilation if an unboxed object in some group
+// is converted to a native object.
+class ConstraintDataFreezeObjectForUnboxedConvertedToNative
+{
+  public:
+    ConstraintDataFreezeObjectForUnboxedConvertedToNative()
+    {}
+
+    const char *kind() { return "freezeObjectForUnboxedConvertedToNative"; }
+
+    bool invalidateOnNewType(TypeSet::Type type) { return false; }
+    bool invalidateOnNewPropertyState(TypeSet *property) { return false; }
+    bool invalidateOnNewObjectState(ObjectGroup *group) {
+        return group->unboxedLayout().nativeGroup() != nullptr;
+    }
+
+    bool constraintHolds(JSContext *cx,
+                         const HeapTypeSetKey &property, TemporaryTypeSet *expected)
+    {
+        return !invalidateOnNewObjectState(property.object()->maybeGroup());
+    }
+
+    bool shouldSweep() { return false; }
+};
+
 } /* anonymous namespace */
 
 void
 TypeSet::ObjectKey::watchStateChangeForInlinedCall(CompilerConstraintList *constraints)
 {
     HeapTypeSetKey objectProperty = property(JSID_EMPTY);
     LifoAlloc *alloc = constraints->alloc();
 
@@ -1728,16 +1753,27 @@ TypeSet::ObjectKey::watchStateChangeForT
     HeapTypeSetKey objectProperty = property(JSID_EMPTY);
     LifoAlloc *alloc = constraints->alloc();
 
     typedef CompilerConstraintInstance<ConstraintDataFreezeObjectForTypedArrayData> T;
     constraints->add(alloc->new_<T>(alloc, objectProperty,
                                     ConstraintDataFreezeObjectForTypedArrayData(tarray)));
 }
 
+void
+TypeSet::ObjectKey::watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList *constraints)
+{
+    HeapTypeSetKey objectProperty = property(JSID_EMPTY);
+    LifoAlloc *alloc = constraints->alloc();
+
+    typedef CompilerConstraintInstance<ConstraintDataFreezeObjectForUnboxedConvertedToNative> T;
+    constraints->add(alloc->new_<T>(alloc, objectProperty,
+                                    ConstraintDataFreezeObjectForUnboxedConvertedToNative()));
+}
+
 static void
 ObjectStateChange(ExclusiveContext *cxArg, ObjectGroup *group, bool markingUnknown)
 {
     if (group->unknownProperties())
         return;
 
     /* All constraints listening to state changes are on the empty id. */
     HeapTypeSet *types = group->maybeGetProperty(JSID_EMPTY);
@@ -2556,26 +2592,38 @@ js::AddTypePropertyId(ExclusiveContext *
 
     if (types->hasType(type))
         return;
 
     InferSpew(ISpewOps, "externalType: property %s %s: %s",
               TypeSet::ObjectGroupString(group), TypeIdString(id), TypeSet::TypeString(type));
     types->addType(cx, type);
 
+    // If this addType caused the type set to be marked as containing any
+    // object, make sure that is reflected in other type sets the addType is
+    // propagated to below.
+    if (type.isObjectUnchecked() && types->unknownObject())
+        type = TypeSet::AnyObjectType();
+
     // Propagate new types from partially initialized groups to fully
     // initialized groups for the acquired properties analysis. Note that we
     // don't need to do this for other property changes, as these will also be
     // reflected via shape changes on the object that will prevent the object
     // from acquiring the fully initialized group.
-    if (group->newScript() && group->newScript()->initializedGroup()) {
-        if (type.isObjectUnchecked() && types->unknownObject())
-            type = TypeSet::AnyObjectType();
+    if (group->newScript() && group->newScript()->initializedGroup())
         AddTypePropertyId(cx, group->newScript()->initializedGroup(), id, type);
-    }
+
+    // Maintain equivalent type information for unboxed object groups and their
+    // corresponding native group. Since type sets might contain the unboxed
+    // group but not the native group, this ensures optimizations based on the
+    // unboxed group are valid for the native group.
+    if (group->maybeUnboxedLayout() && group->maybeUnboxedLayout()->nativeGroup())
+        AddTypePropertyId(cx, group->maybeUnboxedLayout()->nativeGroup(), id, type);
+    if (ObjectGroup *unboxedGroup = group->maybeOriginalUnboxedGroup())
+        AddTypePropertyId(cx, unboxedGroup, id, type);
 }
 
 void
 js::AddTypePropertyId(ExclusiveContext *cx, ObjectGroup *group, jsid id, const Value &value)
 {
     AddTypePropertyId(cx, group, id, TypeSet::GetValueType(value));
 }
 
@@ -2661,16 +2709,22 @@ ObjectGroup::setFlags(ExclusiveContext *
     InferSpew(ISpewOps, "%s: setFlags 0x%x", TypeSet::ObjectGroupString(this), flags);
 
     ObjectStateChange(cx, this, false);
 
     // Propagate flag changes from partially to fully initialized groups for the
     // acquired properties analysis.
     if (newScript() && newScript()->initializedGroup())
         newScript()->initializedGroup()->setFlags(cx, flags);
+
+    // Propagate flag changes between unboxed and corresponding native groups.
+    if (maybeUnboxedLayout() && maybeUnboxedLayout()->nativeGroup())
+        maybeUnboxedLayout()->nativeGroup()->setFlags(cx, flags);
+    if (ObjectGroup *unboxedGroup = maybeOriginalUnboxedGroup())
+        unboxedGroup->setFlags(cx, flags);
 }
 
 void
 ObjectGroup::markUnknown(ExclusiveContext *cx)
 {
     AutoEnterAnalysis enter(cx);
 
     MOZ_ASSERT(cx->zone()->types.activeAnalysis);
@@ -3561,17 +3615,17 @@ TypeNewScript::rollbackPartiallyInitiali
         if (!thisv.isObject() ||
             thisv.toObject().hasLazyGroup() ||
             thisv.toObject().group() != group)
         {
             continue;
         }
 
         if (thisv.toObject().is<UnboxedPlainObject>() &&
-            !thisv.toObject().as<UnboxedPlainObject>().convertToNative(cx))
+            !UnboxedPlainObject::convertToNative(cx, &thisv.toObject()))
         {
             CrashAtUnhandlableOOM("rollbackPartiallyInitializedObjects");
         }
 
         // Found a matching frame.
         RootedPlainObject obj(cx, &thisv.toObject().as<PlainObject>());
 
         // Whether all identified 'new' properties have been initialized.
@@ -3696,16 +3750,22 @@ ConstraintTypeSet::sweep(Zone *zone, Aut
                     clearObjects();
                     objectCount = 0;
                     break;
                 }
             } else if (key->isGroup() && key->group()->unknownPropertiesDontCheckGeneration()) {
                 // Object sets containing objects with unknown properties might
                 // not be complete. Mark the type set as unknown, which it will
                 // be treated as during Ion compilation.
+                //
+                // Note that we don't have to do this when the type set might
+                // be missing the native group corresponding to an unboxed
+                // object group. In this case, the native group points to the
+                // unboxed object group via its addendum, so as long as objects
+                // with either group exist, neither group will be finalized.
                 flags |= TYPE_FLAG_ANYOBJECT;
                 clearObjects();
                 objectCount = 0;
                 break;
             }
         }
         setBaseObjectCount(objectCount);
     } else if (objectCount == 1) {
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -201,21 +201,29 @@ class TemporaryTypeSet;
  * - HeapTypeSet are associated with the properties of ObjectGroups. These
  *   may have constraints added to them to trigger invalidation of either
  *   compiled code or TypeNewScript information.
  *
  * - TemporaryTypeSet are created during compilation and do not outlive
  *   that compilation.
  *
  * The contents of a type set completely describe the values that a particular
- * lvalue might have, except in cases where an object's type is mutated. In
- * such cases, type sets which had the object's old type might not have the
- * object's new type. Type mutation occurs only in specific circumstances ---
- * when an object's prototype changes, and when it is swapped with another
- * object --- and will cause the object's properties to be marked as unknown.
+ * lvalue might have, except for the following cases:
+ *
+ * - If an object's prototype or class is dynamically mutated, its group will
+ *   change. Type sets containing the old group will not necessarily contain
+ *   the new group. When this occurs, the properties of the old and new group
+ *   will both be marked as unknown, which will prevent Ion from optimizing
+ *   based on the object's type information.
+ *
+ * - If an unboxed object is converted to a native object, its group will also
+ *   change and type sets containing the old group will not necessarily contain
+ *   the new group. Unlike the above case, this will not degrade property type
+ *   information, but Ion will no longer optimize unboxed objects with the old
+ *   group.
  */
 class TypeSet
 {
   public:
     // Type set entry for either a JSObject with singleton type or a
     // non-singleton ObjectGroup.
     class ObjectKey {
       public:
@@ -242,16 +250,17 @@ class TypeSet
         TaggedProto proto();
         TypeNewScript *newScript();
 
         bool unknownProperties();
         bool hasFlags(CompilerConstraintList *constraints, ObjectGroupFlags flags);
         bool hasStableClassAndProto(CompilerConstraintList *constraints);
         void watchStateChangeForInlinedCall(CompilerConstraintList *constraints);
         void watchStateChangeForTypedArrayData(CompilerConstraintList *constraints);
+        void watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList *constraints);
         HeapTypeSetKey property(jsid id);
         void ensureTrackedProperty(JSContext *cx, jsid id);
 
         ObjectGroup *maybeGroup();
     };
 
     // Information about a single concrete type. We pack this into one word,
     // where small values are particular primitive or other singleton types and
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -24,16 +24,22 @@ using namespace js;
 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);
+
+    if (nativeGroup_)
+        MarkObjectGroup(trc, &nativeGroup_, "unboxed_layout_nativeGroup");
+
+    if (nativeShape_)
+        MarkShape(trc, &nativeShape_, "unboxed_layout_nativeShape");
 }
 
 size_t
 UnboxedLayout::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
 {
     return mallocSizeOf(this)
          + properties_.sizeOfExcludingThis(mallocSizeOf)
          + (newScript() ? newScript()->sizeOfIncludingThis(mallocSizeOf) : 0)
@@ -150,62 +156,119 @@ UnboxedPlainObject::trace(JSTracer *trc,
             MarkObject(trc, heap, "unboxed_object");
         list++;
     }
 
     // Unboxed objects don't have Values to trace.
     MOZ_ASSERT(*(list + 1) == -1);
 }
 
-bool
-UnboxedPlainObject::convertToNative(JSContext *cx)
+/* static */ bool
+UnboxedLayout::makeNativeGroup(JSContext *cx, ObjectGroup *group)
 {
-    // Immediately clear any new script on this object's group,
-    // as rollbackPartiallyInitializedObjects() will be confused by the type
-    // changes we make in this function.
-    group()->clearNewScript(cx);
+    UnboxedLayout &layout = group->unboxedLayout();
+
+    MOZ_ASSERT(!layout.nativeGroup());
 
-    // clearNewScript() can reentrantly invoke this method.
-    if (!is<UnboxedPlainObject>())
-        return true;
-
-    Rooted<UnboxedPlainObject *> obj(cx, this);
-    Rooted<TaggedProto> proto(cx, getTaggedProto());
+    // Immediately clear any new script on the group, as
+    // rollbackPartiallyInitializedObjects() will be confused by the type
+    // changes we make later on.
+    group->clearNewScript(cx);
 
-    size_t nfixed = gc::GetGCKindSlots(obj->layout().getAllocKind());
+    AutoEnterAnalysis enter(cx);
+
+    Rooted<TaggedProto> proto(cx, group->proto());
 
-    AutoValueVector values(cx);
+    size_t nfixed = gc::GetGCKindSlots(layout.getAllocKind());
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_, proto,
-                                                      getParent(), getMetadata(), nfixed,
-                                                      lastProperty()->getObjectFlags()));
+                                                      cx->global(), nullptr, nfixed, 0));
     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;
+    for (size_t i = 0; i < layout.properties().length(); i++) {
+        const UnboxedLayout::Property &property = layout.properties()[i];
 
         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))
+    ObjectGroup *nativeGroup =
+        ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto,
+                                          group->flags() & OBJECT_FLAG_DYNAMIC_MASK);
+    if (!nativeGroup)
         return false;
 
-    RootedNativeObject nobj(cx, &obj->as<PlainObject>());
-    nobj->setLastPropertyMakeNative(cx, shape);
+    // Propagate all property types from the old group to the new group.
+    for (size_t i = 0; i < group->getPropertyCount(); i++) {
+        if (ObjectGroup::Property *property = group->getProperty(i)) {
+            TypeSet::TypeList types;
+            if (!property->types.enumerateTypes(&types))
+                return false;
+            for (size_t i = 0; i < types.length(); i++)
+                AddTypePropertyId(cx, nativeGroup, property->id, types[i]);
+            HeapTypeSet *nativeProperty = nativeGroup->maybeGetProperty(property->id);
+            if (nativeProperty->canSetDefinite(i))
+                nativeProperty->setDefinite(i);
+        }
+    }
+
+    layout.nativeGroup_ = nativeGroup;
+    layout.nativeShape_ = shape;
+
+    nativeGroup->setOriginalUnboxedGroup(group);
+
+    return true;
+}
+
+/* static */ bool
+UnboxedPlainObject::convertToNative(JSContext *cx, JSObject *obj)
+{
+    const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
+
+    if (!layout.nativeGroup()) {
+        if (!UnboxedLayout::makeNativeGroup(cx, obj->group()))
+            return false;
+
+        // makeNativeGroup can reentrantly invoke this method.
+        if (obj->is<PlainObject>())
+            return true;
+    }
+
+    AutoValueVector values(cx);
+    for (size_t i = 0; i < layout.properties().length(); i++) {
+        if (!values.append(obj->as<UnboxedPlainObject>().getValue(layout.properties()[i])))
+            return false;
+    }
+
+    uint32_t objectFlags = obj->lastProperty()->getObjectFlags();
+    RootedObject metadata(cx, obj->getMetadata());
+
+    obj->setGroup(layout.nativeGroup());
+    obj->as<PlainObject>().setLastPropertyMakeNative(cx, layout.nativeShape());
 
     for (size_t i = 0; i < values.length(); i++)
-        nobj->initSlotUnchecked(i, values[i]);
+        obj->as<PlainObject>().initSlotUnchecked(i, values[i]);
+
+    if (objectFlags) {
+        RootedObject objRoot(cx, obj);
+        if (!obj->setFlags(cx, objectFlags))
+            return false;
+        obj = objRoot;
+    }
+
+    if (metadata) {
+        RootedObject objRoot(cx, obj);
+        RootedObject metadataRoot(cx, metadata);
+        if (!setMetadata(cx, objRoot, metadataRoot))
+            return false;
+    }
 
     return true;
 }
 
 /* static */
 UnboxedPlainObject *
 UnboxedPlainObject::create(JSContext *cx, HandleObjectGroup group, NewObjectKind newKind)
 {
@@ -260,17 +323,17 @@ UnboxedPlainObject::obj_lookupProperty(J
 
     return LookupProperty(cx, proto, id, objp, propp);
 }
 
 /* static */ bool
 UnboxedPlainObject::obj_defineProperty(JSContext *cx, HandleObject obj, HandleId id, HandleValue v,
                                        PropertyOp getter, StrictPropertyOp setter, unsigned attrs)
 {
-    if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
+    if (!convertToNative(cx, obj))
         return false;
 
     return DefineProperty(cx, obj, id, v, getter, setter, attrs);
 }
 
 /* static */ bool
 UnboxedPlainObject::obj_hasProperty(JSContext *cx, HandleObject obj, HandleId id, bool *foundp)
 {
@@ -314,17 +377,17 @@ UnboxedPlainObject::obj_setProperty(JSCo
 {
     const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
 
     if (const UnboxedLayout::Property *property = layout.lookup(id)) {
         if (obj == receiver) {
             if (obj->as<UnboxedPlainObject>().setValue(cx, *property, vp))
                 return true;
 
-            if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
+            if (!convertToNative(cx, obj))
                 return false;
             return SetProperty(cx, obj, receiver, id, vp, strict);
         }
 
         return SetPropertyByDefining(cx, obj, receiver, id, vp, strict, false);
     }
 
     return SetPropertyOnProto(cx, obj, receiver, id, vp, strict);
@@ -346,25 +409,25 @@ UnboxedPlainObject::obj_getOwnPropertyDe
     desc.object().set(nullptr);
     return true;
 }
 
 /* static */ bool
 UnboxedPlainObject::obj_deleteProperty(JSContext *cx, HandleObject obj, HandleId id,
                                        bool *succeeded)
 {
-    if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
+    if (!convertToNative(cx, obj))
         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))
+    if (!convertToNative(cx, obj))
         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();
--- a/js/src/vm/UnboxedObject.h
+++ b/js/src/vm/UnboxedObject.h
@@ -60,19 +60,26 @@ class UnboxedLayout : public mozilla::Li
 
     // Any 'new' script information associated with this layout.
     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_;
 
+    // If objects in this group have ever been converted to native objects,
+    // these store the corresponding native group and initial shape for such
+    // objects. Type information for this object is reflected in nativeGroup.
+    HeapPtrObjectGroup nativeGroup_;
+    HeapPtrShape nativeShape_;
+
   public:
     UnboxedLayout(const PropertyVector &properties, size_t size)
-      : size_(size), newScript_(nullptr), traceList_(nullptr)
+      : size_(size), newScript_(nullptr), traceList_(nullptr),
+        nativeGroup_(nullptr), nativeShape_(nullptr)
     {
         properties_.appendAll(properties);
     }
 
     ~UnboxedLayout() {
         js_delete(newScript_);
         js_free(traceList_);
     }
@@ -108,21 +115,31 @@ class UnboxedLayout : public mozilla::Li
             return lookup(JSID_TO_ATOM(id));
         return nullptr;
     }
 
     size_t size() const {
         return size_;
     }
 
+    ObjectGroup *nativeGroup() const {
+        return nativeGroup_;
+    }
+
+    Shape *nativeShape() const {
+        return nativeShape_;
+    }
+
     inline gc::AllocKind getAllocKind() const;
 
     void trace(JSTracer *trc);
 
     size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+
+    static bool makeNativeGroup(JSContext *cx, ObjectGroup *group);
 };
 
 // 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
 {
@@ -161,18 +178,17 @@ class UnboxedPlainObject : public JSObje
 
     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 bool convertToNative(JSContext *cx, JSObject *obj);
     static UnboxedPlainObject *create(JSContext *cx, HandleObjectGroup group, NewObjectKind newKind);
 
     static void trace(JSTracer *trc, JSObject *object);
 
     static size_t offsetOfData() {
         return offsetof(UnboxedPlainObject, data_[0]);
     }
 };