Bug 1198861 - Improve type barrier logic to avoid unnecessary tests on primitive types, and fix redundant baseline stubs for SETELEM property adds, r=jandem.
☠☠ backed out by a9705e55f06d ☠ ☠
authorBrian Hackett <bhackett1024@gmail.com>
Sat, 19 Sep 2015 10:40:22 -0600
changeset 296025 d29fef133d8a0960a57031f71b299fa58aa4d74d
parent 296024 dec41eaf2fedc00d0fe809e83fe2985c17426371
child 296026 0ad4ca92e9a909763f054fee3d111f8274d00b61
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1198861
milestone43.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 1198861 - Improve type barrier logic to avoid unnecessary tests on primitive types, and fix redundant baseline stubs for SETELEM property adds, r=jandem.
js/src/jit/BaselineIC.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/IonTypes.h
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/MacroAssembler.cpp
js/src/vm/TypeInference.cpp
js/src/vm/TypeInference.h
js/src/vm/UnboxedObject.cpp
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -3512,17 +3512,17 @@ SetElemAddHasSameShapes(ICSetElem_DenseO
         return false;
 
     JSObject* proto = obj->getProto();
     for (size_t i = 0; i < stub->protoChainDepth(); i++) {
         if (!proto->isNative())
             return false;
         if (proto->as<NativeObject>().lastProperty() != nstub->shape(i + 1))
             return false;
-        proto = obj->getProto();
+        proto = proto->getProto();
         if (!proto) {
             if (i != stub->protoChainDepth() - 1)
                 return false;
             break;
         }
     }
 
     return true;
@@ -3530,32 +3530,63 @@ SetElemAddHasSameShapes(ICSetElem_DenseO
 
 static bool
 DenseOrUnboxedArraySetElemStubExists(JSContext* cx, ICStub::Kind kind,
                                      ICSetElem_Fallback* stub, HandleObject obj)
 {
     MOZ_ASSERT(kind == ICStub::SetElem_DenseOrUnboxedArray ||
                kind == ICStub::SetElem_DenseOrUnboxedArrayAdd);
 
+    if (obj->isSingleton())
+        return false;
+
     for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
         if (kind == ICStub::SetElem_DenseOrUnboxedArray && iter->isSetElem_DenseOrUnboxedArray()) {
             ICSetElem_DenseOrUnboxedArray* nstub = iter->toSetElem_DenseOrUnboxedArray();
-            if (obj->maybeShape() == nstub->shape() && obj->getGroup(cx) == nstub->group())
+            if (obj->maybeShape() == nstub->shape() && obj->group() == nstub->group())
                 return true;
         }
 
         if (kind == ICStub::SetElem_DenseOrUnboxedArrayAdd && iter->isSetElem_DenseOrUnboxedArrayAdd()) {
             ICSetElem_DenseOrUnboxedArrayAdd* nstub = iter->toSetElem_DenseOrUnboxedArrayAdd();
-            if (obj->getGroup(cx) == nstub->group() && SetElemAddHasSameShapes(nstub, obj))
+            if (obj->group() == nstub->group() && SetElemAddHasSameShapes(nstub, obj))
                 return true;
         }
     }
     return false;
 }
 
+static void
+RemoveMatchingDenseOrUnboxedArraySetElemAddStub(JSContext* cx,
+                                                ICSetElem_Fallback* stub, HandleObject obj)
+{
+    if (obj->isSingleton())
+        return;
+
+    // Before attaching a new stub to add elements to a dense or unboxed array,
+    // remove any other stub with the same group/shape but different prototype
+    // shapes.
+    for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) {
+        if (!iter->isSetElem_DenseOrUnboxedArrayAdd())
+            continue;
+
+        ICSetElem_DenseOrUnboxedArrayAdd* nstub = iter->toSetElem_DenseOrUnboxedArrayAdd();
+        if (obj->group() != nstub->group())
+            continue;
+
+        static const size_t MAX_DEPTH = ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH;
+        ICSetElem_DenseOrUnboxedArrayAddImpl<MAX_DEPTH>* nostub = nstub->toImplUnchecked<MAX_DEPTH>();
+
+        if (obj->maybeShape() == nostub->shape(0)) {
+            iter.unlink(cx);
+            return;
+        }
+    }
+}
+
 static bool
 TypedArraySetElemStubExists(ICSetElem_Fallback* stub, HandleObject obj, bool expectOOB)
 {
     for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
         if (!iter->isSetElem_TypedArray())
             continue;
         ICSetElem_TypedArray* taStub = iter->toSetElem_TypedArray();
         if (obj->maybeShape() == taStub->shape() && taStub->expectOutOfBounds() == expectOOB)
@@ -3727,16 +3758,18 @@ DoSetElemFallback(JSContext* cx, Baselin
             RootedObjectGroup group(cx, obj->getGroup(cx));
             if (!group)
                 return false;
 
             if (addingCase &&
                 !DenseOrUnboxedArraySetElemStubExists(cx, ICStub::SetElem_DenseOrUnboxedArrayAdd,
                                                       stub, obj))
             {
+                RemoveMatchingDenseOrUnboxedArraySetElemAddStub(cx, stub, obj);
+
                 JitSpew(JitSpew_BaselineIC,
                         "  Generating SetElem_DenseOrUnboxedArrayAdd stub "
                         "(shape=%p, group=%p, protoDepth=%u)",
                         shape.get(), group.get(), protoDepth);
                 ICSetElemDenseOrUnboxedArrayAddCompiler compiler(cx, obj, protoDepth);
                 ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(script));
                 if (!newStub)
                     return false;
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -8454,16 +8454,22 @@ IonBuilder::pushScalarLoadFromTypedObjec
     // be valid and unbarriered. Also, need not set resultTypeSet,
     // because knownType is scalar and a resultTypeSet would provide
     // no useful additional info.
     load->setResultType(knownType);
 
     return true;
 }
 
+static bool
+BarrierMustTestTypeTag(BarrierKind kind)
+{
+    return kind == BarrierKind::TypeSet || kind == BarrierKind::TypeTagOnly;
+}
+
 bool
 IonBuilder::pushReferenceLoadFromTypedObject(MDefinition* typedObj,
                                              const LinearSum& byteOffset,
                                              ReferenceTypeDescr::Type type,
                                              PropertyName* name)
 {
     // Find location within the owner object.
     MDefinition* elements;
@@ -8489,17 +8495,17 @@ IonBuilder::pushReferenceLoadFromTypedOb
         break;
       }
       case ReferenceTypeDescr::TYPE_OBJECT: {
         // Make sure the barrier reflects the possibility of reading null. When
         // there is no other barrier needed we include the null bailout with
         // MLoadUnboxedObjectOrNull, which avoids the need to box the result
         // for a type barrier instruction.
         MLoadUnboxedObjectOrNull::NullBehavior nullBehavior;
-        if (barrier == BarrierKind::NoBarrier && !observedTypes->hasType(TypeSet::NullType()))
+        if (!observedTypes->hasType(TypeSet::NullType()) && !BarrierMustTestTypeTag(barrier))
             nullBehavior = MLoadUnboxedObjectOrNull::BailOnNull;
         else
             nullBehavior = MLoadUnboxedObjectOrNull::HandleNull;
         load = MLoadUnboxedObjectOrNull::New(alloc(), elements, scaledOffset, nullBehavior,
                                              adjustment);
         break;
       }
       case ReferenceTypeDescr::TYPE_STRING: {
@@ -11177,17 +11183,17 @@ IonBuilder::loadUnboxedValue(MDefinition
         break;
 
       case JSVAL_TYPE_STRING:
         load = MLoadUnboxedString::New(alloc(), elements, index, elementsOffset);
         break;
 
       case JSVAL_TYPE_OBJECT: {
         MLoadUnboxedObjectOrNull::NullBehavior nullBehavior;
-        if (types->hasType(TypeSet::NullType()) || barrier != BarrierKind::NoBarrier)
+        if (types->hasType(TypeSet::NullType()) || BarrierMustTestTypeTag(barrier))
             nullBehavior = MLoadUnboxedObjectOrNull::HandleNull;
         else
             nullBehavior = MLoadUnboxedObjectOrNull::NullNotPossible;
         load = MLoadUnboxedObjectOrNull::New(alloc(), elements, index, nullBehavior,
                                              elementsOffset);
         break;
       }
 
@@ -11574,20 +11580,17 @@ IonBuilder::getPropTryCache(bool* emitte
     if (inspector->hasSeenAccessedGetter(pc))
         barrier = BarrierKind::TypeSet;
 
     // Caches can read values from prototypes, so update the barrier to
     // reflect such possible values.
     if (barrier != BarrierKind::TypeSet) {
         BarrierKind protoBarrier =
             PropertyReadOnPrototypeNeedsTypeBarrier(this, obj, name, types);
-        if (protoBarrier != BarrierKind::NoBarrier) {
-            MOZ_ASSERT(barrier <= protoBarrier);
-            barrier = protoBarrier;
-        }
+        barrier = CombineBarrierKinds(barrier, protoBarrier);
     }
 
     MGetPropertyCache* load = MGetPropertyCache::New(alloc(), obj, name,
                                                      barrier == BarrierKind::TypeSet);
 
     // Try to mark the cache as idempotent.
     if (obj->type() == MIRType_Object && !invalidatedIdempotentCache()) {
         if (PropertyReadIsIdempotent(constraints(), obj, name))
--- a/js/src/jit/IonTypes.h
+++ b/js/src/jit/IonTypes.h
@@ -740,17 +740,40 @@ enum ABIFunctionType
 enum class BarrierKind : uint32_t {
     // No barrier is needed.
     NoBarrier,
 
     // The barrier only has to check the value's type tag is in the TypeSet.
     // Specific object types don't have to be checked.
     TypeTagOnly,
 
+    // The barrier only has to check that object values are in the type set.
+    // Non-object types don't have to be checked.
+    ObjectTypesOnly,
+
     // Check if the value is in the TypeSet, including the object type if it's
     // an object.
     TypeSet
 };
 
+static inline BarrierKind
+CombineBarrierKinds(BarrierKind first, BarrierKind second)
+{
+    // Barrier kinds form the following lattice:
+    //
+    //         TypeSet
+    //          |   |
+    // TypeTagOnly ObjectTypesOnly
+    //          |   |
+    //        NoBarrier
+    //
+    // This function computes the least upper bound of two barrier kinds.
+    if (first == BarrierKind::NoBarrier || first == second)
+        return second;
+    if (second == BarrierKind::NoBarrier)
+        return first;
+    return BarrierKind::TypeSet;
+}
+
 } // namespace jit
 } // namespace js
 
 #endif /* jit_IonTypes_h */
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -4980,16 +4980,22 @@ PropertyReadNeedsTypeBarrier(CompilerCon
     if (property.maybeTypes()) {
         if (!TypeSetIncludes(observed, MIRType_Value, property.maybeTypes())) {
             // If all possible objects have been observed, we don't have to
             // guard on the specific object types.
             if (property.maybeTypes()->objectsAreSubset(observed)) {
                 property.freeze(constraints);
                 return BarrierKind::TypeTagOnly;
             }
+            // If all possible primitives have been observed, we don't have to
+            // guard on those primitives.
+            if (property.maybeTypes()->primitivesAreSubset(observed)) {
+                property.freeze(constraints);
+                return BarrierKind::ObjectTypesOnly;
+            }
             return BarrierKind::TypeSet;
         }
     }
 
     // Type information for global objects is not required to reflect the
     // initial 'undefined' value for properties, in particular global
     // variables declared with 'var'. Until the property is assigned a value
     // other than undefined, a barrier is required.
@@ -5001,44 +5007,16 @@ PropertyReadNeedsTypeBarrier(CompilerCon
             return BarrierKind::TypeSet;
         }
     }
 
     property.freeze(constraints);
     return BarrierKind::NoBarrier;
 }
 
-static bool
-ObjectSubsumes(TypeSet::ObjectKey* first, TypeSet::ObjectKey* second)
-{
-    if (first->isSingleton() ||
-        second->isSingleton() ||
-        first->clasp() != second->clasp() ||
-        first->unknownProperties() ||
-        second->unknownProperties())
-    {
-        return false;
-    }
-
-    if (first->clasp() == &ArrayObject::class_) {
-        HeapTypeSetKey firstElements = first->property(JSID_VOID);
-        HeapTypeSetKey secondElements = second->property(JSID_VOID);
-
-        return firstElements.maybeTypes() && secondElements.maybeTypes() &&
-               firstElements.maybeTypes()->equals(secondElements.maybeTypes());
-    }
-
-    if (first->clasp() == &UnboxedArrayObject::class_) {
-        return first->group()->unboxedLayout().elementType() ==
-               second->group()->unboxedLayout().elementType();
-    }
-
-    return false;
-}
-
 BarrierKind
 jit::PropertyReadNeedsTypeBarrier(JSContext* propertycx,
                                   CompilerConstraintList* constraints,
                                   TypeSet::ObjectKey* key, PropertyName* name,
                                   TemporaryTypeSet* observed, bool updateObserved)
 {
     if (!updateObserved)
         return PropertyReadNeedsTypeBarrier(constraints, key, name, observed);
@@ -5073,40 +5051,16 @@ jit::PropertyReadNeedsTypeBarrier(JSCont
                     }
                 }
             }
 
             obj = obj->getProto();
         }
     }
 
-    // If any objects which could be observed are similar to ones that have
-    // already been observed, add them to the observed type set.
-    if (!key->unknownProperties()) {
-        HeapTypeSetKey property = key->property(name ? NameToId(name) : JSID_VOID);
-
-        if (property.maybeTypes() && !property.maybeTypes()->unknownObject()) {
-            for (size_t i = 0; i < property.maybeTypes()->getObjectCount(); i++) {
-                TypeSet::ObjectKey* key = property.maybeTypes()->getObject(i);
-                if (!key || observed->unknownObject())
-                    continue;
-
-                for (size_t j = 0; j < observed->getObjectCount(); j++) {
-                    TypeSet::ObjectKey* observedKey = observed->getObject(j);
-                    if (observedKey && ObjectSubsumes(observedKey, key)) {
-                        // Note: the return value here is ignored.
-                        observed->addType(TypeSet::ObjectType(key),
-                                          GetJitContext()->temp->lifoAlloc());
-                        break;
-                    }
-                }
-            }
-        }
-    }
-
     return PropertyReadNeedsTypeBarrier(constraints, key, name, observed);
 }
 
 BarrierKind
 jit::PropertyReadNeedsTypeBarrier(JSContext* propertycx,
                                   CompilerConstraintList* constraints,
                                   MDefinition* obj, PropertyName* name,
                                   TemporaryTypeSet* observed)
@@ -5120,25 +5074,19 @@ jit::PropertyReadNeedsTypeBarrier(JSCont
 
     BarrierKind res = BarrierKind::NoBarrier;
 
     bool updateObserved = types->getObjectCount() == 1;
     for (size_t i = 0; i < types->getObjectCount(); i++) {
         if (TypeSet::ObjectKey* key = types->getObject(i)) {
             BarrierKind kind = PropertyReadNeedsTypeBarrier(propertycx, constraints, key, name,
                                                             observed, updateObserved);
-            if (kind == BarrierKind::TypeSet)
+            res = CombineBarrierKinds(res, kind);
+            if (res == BarrierKind::TypeSet)
                 return BarrierKind::TypeSet;
-
-            if (kind == BarrierKind::TypeTagOnly) {
-                MOZ_ASSERT(res == BarrierKind::NoBarrier || res == BarrierKind::TypeTagOnly);
-                res = BarrierKind::TypeTagOnly;
-            } else {
-                MOZ_ASSERT(kind == BarrierKind::NoBarrier);
-            }
         }
     }
 
     return res;
 }
 
 BarrierKind
 jit::PropertyReadOnPrototypeNeedsTypeBarrier(IonBuilder* builder,
@@ -5162,25 +5110,19 @@ jit::PropertyReadOnPrototypeNeedsTypeBar
             if (!key->hasStableClassAndProto(builder->constraints()))
                 return BarrierKind::TypeSet;
             if (!key->proto().isObject())
                 break;
             JSObject* proto = builder->checkNurseryObject(key->proto().toObject());
             key = TypeSet::ObjectKey::get(proto);
             BarrierKind kind = PropertyReadNeedsTypeBarrier(builder->constraints(),
                                                             key, name, observed);
-            if (kind == BarrierKind::TypeSet)
+            res = CombineBarrierKinds(res, kind);
+            if (res == BarrierKind::TypeSet)
                 return BarrierKind::TypeSet;
-
-            if (kind == BarrierKind::TypeTagOnly) {
-                MOZ_ASSERT(res == BarrierKind::NoBarrier || res == BarrierKind::TypeTagOnly);
-                res = BarrierKind::TypeTagOnly;
-            } else {
-                MOZ_ASSERT(kind == BarrierKind::NoBarrier);
-            }
         }
     }
 
     return res;
 }
 
 bool
 jit::PropertyReadIsIdempotent(CompilerConstraintList* constraints,
@@ -5395,18 +5337,22 @@ TryAddTypeBarrierForWrite(TempAllocator&
 
     TemporaryTypeSet* types = aggregateProperty->maybeTypes()->clone(alloc.lifoAlloc());
     if (!types)
         return false;
 
     // If all possible objects can be stored without a barrier, we don't have to
     // guard on the specific object types.
     BarrierKind kind = BarrierKind::TypeSet;
-    if ((*pvalue)->resultTypeSet() && (*pvalue)->resultTypeSet()->objectsAreSubset(types))
-        kind = BarrierKind::TypeTagOnly;
+    if ((*pvalue)->resultTypeSet()) {
+        if ((*pvalue)->resultTypeSet()->objectsAreSubset(types))
+            kind = BarrierKind::TypeTagOnly;
+        else if ((*pvalue)->resultTypeSet()->primitivesAreSubset(types))
+            kind = BarrierKind::ObjectTypesOnly;
+    }
 
     MInstruction* ins = MMonitorTypes::New(alloc, *pvalue, types, kind);
     current->add(ins);
     return true;
 }
 
 static MInstruction*
 AddGroupGuard(TempAllocator& alloc, MBasicBlock* current, MDefinition* obj,
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -12382,17 +12382,17 @@ class MTypeBarrier
     public TypeBarrierPolicy::Data
 {
     BarrierKind barrierKind_;
 
     MTypeBarrier(MDefinition* def, TemporaryTypeSet* types, BarrierKind kind)
       : MUnaryInstruction(def),
         barrierKind_(kind)
     {
-        MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
+        MOZ_ASSERT(kind != BarrierKind::NoBarrier);
 
         MOZ_ASSERT(!types->unknown());
         setResultType(types->getKnownMIRType());
         setResultTypeSet(types);
 
         setGuard();
         setMovable();
     }
@@ -12442,17 +12442,17 @@ class MMonitorTypes
     const TemporaryTypeSet* typeSet_;
     BarrierKind barrierKind_;
 
     MMonitorTypes(MDefinition* def, const TemporaryTypeSet* types, BarrierKind kind)
       : MUnaryInstruction(def),
         typeSet_(types),
         barrierKind_(kind)
     {
-        MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
+        MOZ_ASSERT(kind != BarrierKind::NoBarrier);
 
         setGuard();
         MOZ_ASSERT(!types->unknown());
     }
 
   public:
     INSTRUCTION_HEADER(MonitorTypes)
 
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -30,49 +30,74 @@ using namespace js::jit;
 
 using JS::GenericNaN;
 using JS::ToInt32;
 
 template <typename Source> void
 MacroAssembler::guardTypeSet(const Source& address, const TypeSet* types, BarrierKind kind,
                              Register scratch, Label* miss)
 {
-    MOZ_ASSERT(kind == BarrierKind::TypeTagOnly || kind == BarrierKind::TypeSet);
+    MOZ_ASSERT(kind != BarrierKind::NoBarrier);
     MOZ_ASSERT(!types->unknown());
 
     Label matched;
     TypeSet::Type tests[8] = {
         TypeSet::Int32Type(),
         TypeSet::UndefinedType(),
         TypeSet::BooleanType(),
         TypeSet::StringType(),
         TypeSet::SymbolType(),
         TypeSet::NullType(),
         TypeSet::MagicArgType(),
         TypeSet::AnyObjectType()
     };
 
-    // The double type also implies Int32.
-    // So replace the int32 test with the double one.
-    if (types->hasType(TypeSet::DoubleType())) {
-        MOZ_ASSERT(types->hasType(TypeSet::Int32Type()));
-        tests[0] = TypeSet::DoubleType();
-    }
+    Register tag = extractTag(address, scratch);
+    BranchType lastBranch;
 
-    Register tag = extractTag(address, scratch);
+    if (kind != BarrierKind::ObjectTypesOnly) {
+        // The double type also implies Int32.
+        // So replace the int32 test with the double one.
+        if (types->hasType(TypeSet::DoubleType())) {
+            MOZ_ASSERT(types->hasType(TypeSet::Int32Type()));
+            tests[0] = TypeSet::DoubleType();
+        }
+
+        // Emit all typed tests.
+        for (size_t i = 0; i < mozilla::ArrayLength(tests); i++) {
+            if (!types->hasType(tests[i]))
+                continue;
 
-    // Emit all typed tests.
-    BranchType lastBranch;
-    for (size_t i = 0; i < mozilla::ArrayLength(tests); i++) {
-        if (!types->hasType(tests[i]))
-            continue;
+            if (lastBranch.isInitialized())
+                lastBranch.emit(*this);
+            lastBranch = BranchType(Equal, tag, tests[i], &matched);
+        }
+    } else {
+#ifdef DEBUG
+        // Any non-object will be considered to match the type set. Make sure
+        // such values encountered are actually in the type set.
+
+        if (types->hasType(TypeSet::DoubleType())) {
+            MOZ_ASSERT(types->hasType(TypeSet::Int32Type()));
+            tests[0] = TypeSet::DoubleType();
+        }
 
-        if (lastBranch.isInitialized())
-            lastBranch.emit(*this);
-        lastBranch = BranchType(Equal, tag, tests[i], &matched);
+        Label matchedPrimitive;
+        for (size_t i = 0; i < mozilla::ArrayLength(tests); i++) {
+            if (!types->hasType(tests[i]))
+                continue;
+            BranchType branch(Equal, tag, tests[i], &matchedPrimitive);
+            branch.emit(*this);
+        }
+        branchTestObject(Equal, tag, &matchedPrimitive);
+
+        assumeUnreachable("Unexpected primitive type");
+
+        bind(&matchedPrimitive);
+#endif
     }
 
     // If this is the last check, invert the last branch.
     if (types->hasType(TypeSet::AnyObjectType()) || !types->getObjectCount()) {
         if (!lastBranch.isInitialized()) {
             jump(miss);
             return;
         }
@@ -85,17 +110,17 @@ MacroAssembler::guardTypeSet(const Sourc
         return;
     }
 
     if (lastBranch.isInitialized())
         lastBranch.emit(*this);
 
     // Test specific objects.
     MOZ_ASSERT(scratch != InvalidReg);
-    branchTestObject(NotEqual, tag, miss);
+    branchTestObject(NotEqual, tag, kind == BarrierKind::ObjectTypesOnly ? &matched : miss);
     if (kind != BarrierKind::TypeTagOnly) {
         Register obj = extractObject(address, scratch);
         guardObjectType(obj, types, scratch, miss);
     } else {
 #ifdef DEBUG
         Label fail;
         Register obj = extractObject(address, scratch);
         guardObjectType(obj, types, scratch, &fail);
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -394,16 +394,23 @@ TypeSet::objectsAreSubset(TypeSet* other
         if (!other->hasType(ObjectType(key)))
             return false;
     }
 
     return true;
 }
 
 bool
+TypeSet::primitivesAreSubset(TypeSet* other)
+{
+    uint32_t primitiveFlags = baseFlags() & TYPE_FLAG_PRIMITIVE;
+    return (primitiveFlags & other->baseFlags()) == primitiveFlags;
+}
+
+bool
 TypeSet::isSubset(const TypeSet* other) const
 {
     if ((baseFlags() & other->baseFlags()) != baseFlags())
         return false;
 
     if (unknownObject()) {
         MOZ_ASSERT(other->unknownObject());
     } else {
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -61,17 +61,17 @@ enum : uint32_t {
     TYPE_FLAG_STRING    =  0x20,
     TYPE_FLAG_SYMBOL    =  0x40,
     TYPE_FLAG_LAZYARGS  =  0x80,
     TYPE_FLAG_ANYOBJECT = 0x100,
 
     /* Mask containing all primitives */
     TYPE_FLAG_PRIMITIVE = TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL | TYPE_FLAG_BOOLEAN |
                           TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE | TYPE_FLAG_STRING |
-                          TYPE_FLAG_SYMBOL,
+                          TYPE_FLAG_SYMBOL | TYPE_FLAG_LAZYARGS,
 
     /* Mask/shift for the number of objects in objectSet */
     TYPE_FLAG_OBJECT_COUNT_MASK     = 0x3e00,
     TYPE_FLAG_OBJECT_COUNT_SHIFT    = 9,
     TYPE_FLAG_OBJECT_COUNT_LIMIT    = 7,
     TYPE_FLAG_DOMOBJECT_COUNT_LIMIT =
         TYPE_FLAG_OBJECT_COUNT_MASK >> TYPE_FLAG_OBJECT_COUNT_SHIFT,
 
@@ -478,21 +478,20 @@ class TypeSet
     bool mightBeMIRType(jit::MIRType type);
 
     /*
      * Get whether this type set is known to be a subset of other.
      * This variant doesn't freeze constraints. That variant is called knownSubset
      */
     bool isSubset(const TypeSet* other) const;
 
-    /*
-     * Get whether the objects in this TypeSet are a subset of the objects
-     * in other.
-     */
+    // Return whether this is a subset of other, ignoring primitive or object
+    // types respectively.
     bool objectsAreSubset(TypeSet* other);
+    bool primitivesAreSubset(TypeSet* other);
 
     /* Whether this TypeSet contains exactly the same types as other. */
     bool equals(const TypeSet* other) const {
         return this->isSubset(other) && other->isSubset(this);
     }
 
     bool objectsIntersect(const TypeSet* other) const;
 
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -1969,16 +1969,22 @@ js::TryConvertToUnboxedLayout(ExclusiveC
     }
 
     size_t layoutSize = 0;
     if (isArray) {
         // Don't use an unboxed representation if we couldn't determine an
         // element type for the objects.
         if (UnboxedTypeSize(elementType) == 0)
             return true;
+
+        // Don't use an unboxed representation if objects in the group have
+        // ever had holes in the past. Even if they have been filled in, future
+        // objects that are created might be given holes as well.
+        if (group->flags() & OBJECT_FLAG_NON_PACKED)
+            return true;
     } else {
         if (objectCount <= 1) {
             // If only one of the objects has been created, it is more likely
             // to have new properties added later. This heuristic is not used
             // for array objects, where we might want an unboxed representation
             // even if there is only one large array.
             return true;
         }