☠☠ backed out by a9705e55f06d ☠ ☠ | |
author | Brian Hackett <bhackett1024@gmail.com> |
Sat, 19 Sep 2015 10:40:22 -0600 | |
changeset 263402 | d29fef133d8a0960a57031f71b299fa58aa4d74d |
parent 263401 | dec41eaf2fedc00d0fe809e83fe2985c17426371 |
child 263403 | 0ad4ca92e9a909763f054fee3d111f8274d00b61 |
push id | 65299 |
push user | bhackett@mozilla.com |
push date | Sat, 19 Sep 2015 16:40:30 +0000 |
treeherder | mozilla-inbound@d29fef133d8a [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jandem |
bugs | 1198861 |
milestone | 43.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
|
--- 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; }