Bug 1201869 - Part 2: Make an array sparse when exceeds the limit of dense array length. r=Waldo
☠☠ backed out by f97d1d74be6a ☠ ☠
authorTooru Fujisawa <arai_a@mac.com>
Sat, 05 Sep 2015 13:24:02 +0900
changeset 297339 84e1d41336a5068d8d569bcfda4bac3e678fe08a
parent 297338 7757ecad90b7f7322f662a27a90919fffdab5e39
child 297340 15f4976a4937ca83384b3f90317028c349b4ea13
push id5392
push userraliiev@mozilla.com
push dateMon, 14 Dec 2015 20:08:23 +0000
treeherdermozilla-beta@16ce8562a975 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs1201869
milestone44.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 1201869 - Part 2: Make an array sparse when exceeds the limit of dense array length. r=Waldo
js/src/frontend/Parser.cpp
js/src/jit-test/tests/arrays/dense-from-sparse.js
js/src/jit/CodeGenerator.cpp
js/src/jit/MCallOptimize.cpp
js/src/jit/MIR.cpp
js/src/jit/RangeAnalysis.cpp
js/src/jsgc.h
js/src/jsobjinlines.h
js/src/vm/NativeObject.cpp
js/src/vm/NativeObject.h
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -8762,17 +8762,17 @@ Parser<ParseHandler>::arrayInitializer(Y
         handler.setListFlag(literal, PNX_NONCONST);
     } else {
         tokenStream.ungetToken();
 
         bool spread = false, missingTrailingComma = false;
         uint32_t index = 0;
         TokenStream::Modifier modifier = TokenStream::Operand;
         for (; ; index++) {
-            if (index == NativeObject::NELEMENTS_LIMIT) {
+            if (index >= NativeObject::MAX_DENSE_ELEMENTS_COUNT) {
                 report(ParseError, false, null(), JSMSG_ARRAY_INIT_TOO_BIG);
                 return null();
             }
 
             TokenKind tt;
             if (!tokenStream.peekToken(&tt, TokenStream::Operand))
                 return null();
             if (tt == TOK_RB)
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/arrays/dense-from-sparse.js
@@ -0,0 +1,39 @@
+// Appending elements to a dense array should make the array sparse when the
+// length exceeds the limit, instead of throwing OOM error.
+
+function test() {
+  const MAX_DENSE_ELEMENTS_ALLOCATION = (1 << 28) - 1;
+  const VALUES_PER_HEADER = 2;
+  const MAX_DENSE_ELEMENTS_COUNT = MAX_DENSE_ELEMENTS_ALLOCATION - VALUES_PER_HEADER;
+  const SPARSE_DENSITY_RATIO = 8;
+  const MIN_DENSE = MAX_DENSE_ELEMENTS_COUNT / SPARSE_DENSITY_RATIO;
+  const MARGIN = 16;
+
+  let a = [];
+  // Fill the beginning of array to make it keep dense until length exceeds
+  // MAX_DENSE_ELEMENTS_COUNT.
+  for (let i = 0; i < MIN_DENSE; i++)
+    a[i] = i;
+
+  // Skip from MIN_DENSE to MAX_DENSE_ELEMENTS_COUNT - MARGIN, to reduce the
+  // time taken by test.
+
+  // Fill the ending of array to make it sparse at MAX_DENSE_ELEMENTS_COUNT.
+  for (let i = MAX_DENSE_ELEMENTS_COUNT - MARGIN; i < MAX_DENSE_ELEMENTS_COUNT + MARGIN; i++)
+    a[i] = i;
+
+  // Make sure the last element is defined.
+  assertEq(a.length, MAX_DENSE_ELEMENTS_COUNT + MARGIN);
+  assertEq(a[a.length - 1], MAX_DENSE_ELEMENTS_COUNT + MARGIN - 1);
+
+  // Make sure elements around MAX_DENSE_ELEMENTS_COUNT are also defined.
+  assertEq(a[MAX_DENSE_ELEMENTS_COUNT - 1], MAX_DENSE_ELEMENTS_COUNT - 1);
+  assertEq(a[MAX_DENSE_ELEMENTS_COUNT], MAX_DENSE_ELEMENTS_COUNT);
+  assertEq(a[MAX_DENSE_ELEMENTS_COUNT + 1], MAX_DENSE_ELEMENTS_COUNT + 1);
+}
+
+var config = getBuildConfiguration();
+// Takes too long time on debug build.
+if (!config.debug) {
+  test();
+}
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -4298,17 +4298,17 @@ CodeGenerator::visitHypot(LHypot* lir)
 void
 CodeGenerator::visitNewArray(LNewArray* lir)
 {
     Register objReg = ToRegister(lir->output());
     Register tempReg = ToRegister(lir->temp());
     JSObject* templateObject = lir->mir()->templateObject();
     DebugOnly<uint32_t> length = lir->mir()->length();
 
-    MOZ_ASSERT(length < NativeObject::NELEMENTS_LIMIT);
+    MOZ_ASSERT(length <= NativeObject::MAX_DENSE_ELEMENTS_COUNT);
 
     if (lir->mir()->shouldUseVM()) {
         visitNewArrayCallVM(lir);
         return;
     }
 
     OutOfLineNewArray* ool = new(alloc()) OutOfLineNewArray(lir);
     addOutOfLineCode(ool, lir->mir());
@@ -6941,17 +6941,17 @@ CodeGenerator::visitOutOfLineStoreElemen
     Int32Key key = ToInt32Key(index);
 
     if (unboxedType == JSVAL_TYPE_MAGIC) {
         // Check array capacity.
         masm.branchKey(Assembler::BelowOrEqual, Address(elements, ObjectElements::offsetOfCapacity()),
                        key, &callStub);
 
         // Update initialized length. The capacity guard above ensures this won't overflow,
-        // due to NELEMENTS_LIMIT.
+        // due to MAX_DENSE_ELEMENTS_COUNT.
         masm.bumpKey(&key, 1);
         masm.storeKey(key, Address(elements, ObjectElements::offsetOfInitializedLength()));
 
         // Update length if length < initializedLength.
         Label dontUpdate;
         masm.branchKey(Assembler::AboveOrEqual, Address(elements, ObjectElements::offsetOfLength()),
                        key, &dontUpdate);
         masm.storeKey(key, Address(elements, ObjectElements::offsetOfLength()));
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -437,18 +437,19 @@ IonBuilder::inlineArray(CallInfo& callIn
             return InliningStatus_Inlined;
         }
 
         // The next several checks all may fail due to range conditions.
         trackOptimizationOutcome(TrackedOutcome::ArrayRange);
 
         // Negative lengths generate a RangeError, unhandled by the inline path.
         initLength = arg->constantValue().toInt32();
-        if (initLength >= NativeObject::NELEMENTS_LIMIT)
+        if (initLength > NativeObject::MAX_DENSE_ELEMENTS_COUNT)
             return InliningStatus_NotInlined;
+        MOZ_ASSERT(initLength <= INT32_MAX);
 
         // Make sure initLength matches the template object's length. This is
         // not guaranteed to be the case, for instance if we're inlining the
         // MConstant may come from an outer script.
         if (initLength != GetAnyBoxedOrUnboxedArrayLength(templateObject))
             return InliningStatus_NotInlined;
 
         // Don't inline large allocations.
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -4111,17 +4111,17 @@ MNewArray::shouldUseVM() const
     if (!templateObject())
         return true;
 
     if (templateObject()->is<UnboxedArrayObject>()) {
         MOZ_ASSERT(templateObject()->as<UnboxedArrayObject>().capacity() >= length());
         return !templateObject()->as<UnboxedArrayObject>().hasInlineElements();
     }
 
-    MOZ_ASSERT(length() < NativeObject::NELEMENTS_LIMIT);
+    MOZ_ASSERT(length() <= NativeObject::MAX_DENSE_ELEMENTS_COUNT);
 
     size_t arraySlots =
         gc::GetGCKindSlots(templateObject()->asTenured().getAllocKind()) - ObjectElements::VALUES_PER_HEADER;
 
     return length() > arraySlots;
 }
 
 bool
--- a/js/src/jit/RangeAnalysis.cpp
+++ b/js/src/jit/RangeAnalysis.cpp
@@ -1729,17 +1729,17 @@ MArrayLength::computeRange(TempAllocator
     // nodes when the value is known to be int32 (see the
     // OBJECT_FLAG_LENGTH_OVERFLOW flag).
     setRange(Range::NewUInt32Range(alloc, 0, INT32_MAX));
 }
 
 void
 MInitializedLength::computeRange(TempAllocator& alloc)
 {
-    setRange(Range::NewUInt32Range(alloc, 0, NativeObject::NELEMENTS_LIMIT));
+    setRange(Range::NewUInt32Range(alloc, 0, NativeObject::MAX_DENSE_ELEMENTS_COUNT));
 }
 
 void
 MTypedArrayLength::computeRange(TempAllocator& alloc)
 {
     setRange(Range::NewUInt32Range(alloc, 0, INT32_MAX));
 }
 
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -167,28 +167,31 @@ GetGCObjectKind(size_t numSlots)
 {
     if (numSlots >= SLOTS_TO_THING_KIND_LIMIT)
         return AllocKind::OBJECT16;
     return slotsToThingKind[numSlots];
 }
 
 /* As for GetGCObjectKind, but for dense array allocation. */
 static inline AllocKind
-GetGCArrayKind(size_t numSlots)
+GetGCArrayKind(size_t numElements)
 {
     /*
      * Dense arrays can use their fixed slots to hold their elements array
      * (less two Values worth of ObjectElements header), but if more than the
      * maximum number of fixed slots is needed then the fixed slots will be
      * unused.
      */
     JS_STATIC_ASSERT(ObjectElements::VALUES_PER_HEADER == 2);
-    if (numSlots > NativeObject::NELEMENTS_LIMIT || numSlots + 2 >= SLOTS_TO_THING_KIND_LIMIT)
+    if (numElements > NativeObject::MAX_DENSE_ELEMENTS_COUNT ||
+        numElements + ObjectElements::VALUES_PER_HEADER >= SLOTS_TO_THING_KIND_LIMIT)
+    {
         return AllocKind::OBJECT2;
-    return slotsToThingKind[numSlots + 2];
+    }
+    return slotsToThingKind[numElements + ObjectElements::VALUES_PER_HEADER];
 }
 
 static inline AllocKind
 GetGCObjectFixedSlotsKind(size_t numFixedSlots)
 {
     MOZ_ASSERT(numFixedSlots < SLOTS_TO_THING_KIND_LIMIT);
     return slotsToThingKind[numFixedSlots];
 }
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -786,33 +786,33 @@ inline T*
 NewObjectWithGroup(ExclusiveContext* cx, HandleObjectGroup group,
                    NewObjectKind newKind = GenericObject)
 {
     gc::AllocKind allocKind = gc::GetGCObjectKind(group->clasp());
     return NewObjectWithGroup<T>(cx, group, allocKind, newKind);
 }
 
 /*
- * As for gc::GetGCObjectKind, where numSlots is a guess at the final size of
+ * As for gc::GetGCObjectKind, where numElements is a guess at the final size of
  * the object, zero if the final size is unknown. This should only be used for
  * objects that do not require any fixed slots.
  */
 static inline gc::AllocKind
-GuessObjectGCKind(size_t numSlots)
+GuessObjectGCKind(size_t numElements)
 {
-    if (numSlots)
-        return gc::GetGCObjectKind(numSlots);
+    if (numElements)
+        return gc::GetGCObjectKind(numElements);
     return gc::AllocKind::OBJECT4;
 }
 
 static inline gc::AllocKind
-GuessArrayGCKind(size_t numSlots)
+GuessArrayGCKind(size_t numElements)
 {
-    if (numSlots)
-        return gc::GetGCArrayKind(numSlots);
+    if (numElements)
+        return gc::GetGCArrayKind(numElements);
     return gc::AllocKind::OBJECT8;
 }
 
 // Returns ESClass_Other if the value isn't an object, or if the object
 // isn't of one of the enumerated classes.  Otherwise returns the appropriate
 // class.
 inline bool
 GetClassOfValue(JSContext* cx, HandleValue v, ESClassValue* classValue)
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -1,18 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "vm/NativeObject-inl.h"
 
+#include "mozilla/ArrayUtils.h"
 #include "mozilla/Casting.h"
-#include "mozilla/CheckedInt.h"
 
 #include "jswatchpoint.h"
 
 #include "gc/Marking.h"
 #include "js/Value.h"
 #include "vm/Debugger.h"
 #include "vm/TypedArrayCommon.h"
 
@@ -393,17 +393,17 @@ NativeObject::growSlots(ExclusiveContext
     MOZ_ASSERT_IF(!is<ArrayObject>(), newCount >= SLOT_CAPACITY_MIN);
 
     /*
      * Slot capacities are determined by the span of allocated objects. Due to
      * the limited number of bits to store shape slots, object growth is
      * throttled well before the slot capacity can overflow.
      */
     NativeObject::slotsSizeMustNotOverflow();
-    MOZ_ASSERT(newCount < NELEMENTS_LIMIT);
+    MOZ_ASSERT(newCount <= MAX_SLOTS_COUNT);
 
     if (!oldCount) {
         MOZ_ASSERT(!slots_);
         slots_ = AllocateObjectBuffer<HeapSlot>(cx, this, newCount);
         if (!slots_)
             return false;
         Debug_SetSlotRangeToCrashOnTouch(slots_, newCount);
         return true;
@@ -515,17 +515,17 @@ bool
 NativeObject::willBeSparseElements(uint32_t requiredCapacity, uint32_t newElementsHint)
 {
     MOZ_ASSERT(isNative());
     MOZ_ASSERT(requiredCapacity > MIN_SPARSE_INDEX);
 
     uint32_t cap = getDenseCapacity();
     MOZ_ASSERT(requiredCapacity >= cap);
 
-    if (requiredCapacity >= NELEMENTS_LIMIT)
+    if (requiredCapacity > MAX_DENSE_ELEMENTS_COUNT)
         return true;
 
     uint32_t minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO;
     if (newElementsHint >= minimalDenseCount)
         return false;
     minimalDenseCount -= newElementsHint;
 
     if (minimalDenseCount > cap)
@@ -589,17 +589,17 @@ NativeObject::maybeDensifySparseElements
             }
         }
         shape = shape->previous();
     }
 
     if (numDenseElements * SPARSE_DENSITY_RATIO < newInitializedLength)
         return DenseElementResult::Incomplete;
 
-    if (newInitializedLength >= NELEMENTS_LIMIT)
+    if (newInitializedLength > MAX_DENSE_ELEMENTS_COUNT)
         return DenseElementResult::Incomplete;
 
     /*
      * This object meets all necessary restrictions, convert all indexed
      * properties into dense elements.
      */
 
     if (!obj->maybeCopyElementsForWrite(cx))
@@ -667,17 +667,17 @@ NativeObject::maybeDensifySparseElements
 //   * minimize the number of unused elements beyond an array's length, and
 //   * provide at least SLOT_CAPACITY_MIN elements no matter what (so adding
 //     the first several elements to small arrays only needs one allocation).
 //
 // Note: the structure and behavior of this method follow along with
 // UnboxedArrayObject::chooseCapacityIndex. Changes to the allocation strategy
 // in one should generally be matched by the other.
 /* static */ uint32_t
-NativeObject::goodAllocated(uint32_t reqAllocated, uint32_t length = 0)
+NativeObject::goodElementsAllocationAmount(uint32_t reqAllocated, uint32_t length = 0)
 {
     // Handle "small" requests primarily by doubling.
     const uint32_t Mebi = 1 << 20;
     if (reqAllocated < Mebi) {
         uint32_t goodAmount = mozilla::AssertedCast<uint32_t>(RoundUpPow2(reqAllocated));
 
         // If |goodAmount| would be 2/3 or more of the array's length, adjust
         // it (up or down) to be equal to the array's length.  This avoids
@@ -707,78 +707,77 @@ NativeObject::goodAllocated(uint32_t req
     //
     // The bucket size table below was generated with this JavaScript (and
     // manual reformatting):
     //
     //   for (let n = 1, i = 0; i < 34; i++) {
     //     print('0x' + (n * (1 << 20)).toString(16) + ', ');
     //     n = Math.ceil(n * 1.125);
     //   }
-    //   print('NELEMENTS_LIMIT - 1');
+    //   print('MAX_DENSE_ELEMENTS_ALLOCATION');
     //
-    // Dense array elements can't exceed |NELEMENTS_LIMIT|, so
-    // |NELEMENTS_LIMIT - 1| is the biggest allowed length.
+    // Dense array elements can't exceed |MAX_DENSE_ELEMENTS_ALLOCATION|, so
+    // |MAX_DENSE_ELEMENTS_ALLOCATION| is the biggest allowed length.
     static const uint32_t BigBuckets[] = {
         0x100000, 0x200000, 0x300000, 0x400000, 0x500000, 0x600000, 0x700000,
         0x800000, 0x900000, 0xb00000, 0xd00000, 0xf00000, 0x1100000, 0x1400000,
         0x1700000, 0x1a00000, 0x1e00000, 0x2200000, 0x2700000, 0x2c00000,
         0x3200000, 0x3900000, 0x4100000, 0x4a00000, 0x5400000, 0x5f00000,
         0x6b00000, 0x7900000, 0x8900000, 0x9b00000, 0xaf00000, 0xc500000,
-        0xde00000, 0xfa00000, NELEMENTS_LIMIT - 1
+        0xde00000, 0xfa00000, MAX_DENSE_ELEMENTS_ALLOCATION
     };
+    MOZ_ASSERT(BigBuckets[ArrayLength(BigBuckets) - 2] <= MAX_DENSE_ELEMENTS_ALLOCATION);
 
     // Pick the first bucket that'll fit |reqAllocated|.
     for (uint32_t b : BigBuckets) {
         if (b >= reqAllocated)
             return b;
     }
 
     // Otherwise, return the maximum bucket size.
-    return NELEMENTS_LIMIT - 1;
+    return MAX_DENSE_ELEMENTS_ALLOCATION;
 }
 
 bool
 NativeObject::growElements(ExclusiveContext* cx, uint32_t reqCapacity)
 {
+    if (reqCapacity > MAX_DENSE_ELEMENTS_COUNT) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
     MOZ_ASSERT(nonProxyIsExtensible());
     MOZ_ASSERT(canHaveNonEmptyElements());
     if (denseElementsAreCopyOnWrite())
         MOZ_CRASH();
 
     uint32_t oldCapacity = getDenseCapacity();
     MOZ_ASSERT(oldCapacity < reqCapacity);
 
-    using mozilla::CheckedInt;
-
-    CheckedInt<uint32_t> checkedOldAllocated =
-        CheckedInt<uint32_t>(oldCapacity) + ObjectElements::VALUES_PER_HEADER;
-    CheckedInt<uint32_t> checkedReqAllocated =
-        CheckedInt<uint32_t>(reqCapacity) + ObjectElements::VALUES_PER_HEADER;
-    if (!checkedOldAllocated.isValid() || !checkedReqAllocated.isValid())
-        return false;
-
-    uint32_t reqAllocated = checkedReqAllocated.value();
-    uint32_t oldAllocated = checkedOldAllocated.value();
+    uint32_t reqAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER;
+    uint32_t oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER;
+    MOZ_ASSERT(oldAllocated <= MAX_DENSE_ELEMENTS_ALLOCATION);
 
     uint32_t newAllocated;
     if (is<ArrayObject>() && !as<ArrayObject>().lengthIsWritable()) {
         MOZ_ASSERT(reqCapacity <= as<ArrayObject>().length());
         // Preserve the |capacity <= length| invariant for arrays with
         // non-writable length.  See also js::ArraySetLength which initially
         // enforces this requirement.
         newAllocated = reqAllocated;
     } else {
-        newAllocated = goodAllocated(reqAllocated, getElementsHeader()->length);
+        newAllocated = goodElementsAllocationAmount(reqAllocated, getElementsHeader()->length);
     }
 
     uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
     MOZ_ASSERT(newCapacity > oldCapacity && newCapacity >= reqCapacity);
 
-    if (newCapacity >= NELEMENTS_LIMIT)
-        return false;
+    // If newCapacity exceeds MAX_DENSE_ELEMENTS_COUNT, the array should become
+    // sparse.
+    MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
 
     uint32_t initlen = getDenseInitializedLength();
 
     HeapSlot* oldHeaderSlots = reinterpret_cast<HeapSlot*>(getElementsHeader());
     HeapSlot* newHeaderSlots;
     if (hasDynamicElements()) {
         newHeaderSlots = ReallocateObjectBuffer<HeapSlot>(cx, this, oldHeaderSlots, oldAllocated, newAllocated);
         if (!newHeaderSlots)
@@ -809,23 +808,23 @@ NativeObject::shrinkElements(ExclusiveCo
     if (!hasDynamicElements())
         return;
 
     uint32_t oldCapacity = getDenseCapacity();
     MOZ_ASSERT(reqCapacity < oldCapacity);
 
     uint32_t oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER;
     uint32_t reqAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER;
-    uint32_t newAllocated = goodAllocated(reqAllocated);
+    uint32_t newAllocated = goodElementsAllocationAmount(reqAllocated);
     if (newAllocated == oldAllocated)
         return;  // Leave elements at its old size.
 
     MOZ_ASSERT(newAllocated > ObjectElements::VALUES_PER_HEADER);
     uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
-    MOZ_ASSERT(newCapacity < NELEMENTS_LIMIT);
+    MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
 
     HeapSlot* oldHeaderSlots = reinterpret_cast<HeapSlot*>(getElementsHeader());
     HeapSlot* newHeaderSlots = ReallocateObjectBuffer<HeapSlot>(cx, this, oldHeaderSlots,
                                                                 oldAllocated, newAllocated);
     if (!newHeaderSlots) {
         cx->recoverFromOutOfMemory();
         return;  // Leave elements at its old size.
     }
@@ -840,22 +839,22 @@ NativeObject::CopyElementsForWrite(Exclu
 {
     MOZ_ASSERT(obj->denseElementsAreCopyOnWrite());
 
     // The original owner of a COW elements array should never be modified.
     MOZ_ASSERT(obj->getElementsHeader()->ownerObject() != obj);
 
     uint32_t initlen = obj->getDenseInitializedLength();
     uint32_t allocated = initlen + ObjectElements::VALUES_PER_HEADER;
-    uint32_t newAllocated = goodAllocated(allocated);
+    uint32_t newAllocated = goodElementsAllocationAmount(allocated);
 
     uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
 
-    if (newCapacity >= NELEMENTS_LIMIT)
-        return false;
+    // COPY_ON_WRITE flags is set only if obj is a dense array.
+    MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
 
     JSObject::writeBarrierPre(obj->getElementsHeader()->ownerObject());
 
     HeapSlot* newHeaderSlots = AllocateObjectBuffer<HeapSlot>(cx, obj, newAllocated);
     if (!newHeaderSlots)
         return false;
     ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
     js_memcpy(newheader, obj->getElementsHeader(),
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -551,18 +551,23 @@ class NativeObject : public JSObject
   public:
     bool generateOwnShape(ExclusiveContext* cx, Shape* newShape = nullptr) {
         return replaceWithNewEquivalentShape(cx, lastProperty(), newShape);
     }
 
     bool shadowingShapeChange(ExclusiveContext* cx, const Shape& shape);
     bool clearFlag(ExclusiveContext* cx, BaseShape::Flag flag);
 
+    // The maximum number of slots in an object.
+    // |MAX_SLOTS_COUNT * sizeof(JS::Value)| shouldn't overflow
+    // int32_t (see slotsSizeMustNotOverflow).
+    static const uint32_t MAX_SLOTS_COUNT = (1 << 28) - 1;
+
     static void slotsSizeMustNotOverflow() {
-        static_assert((NativeObject::NELEMENTS_LIMIT - 1) <= INT32_MAX / sizeof(JS::Value),
+        static_assert(NativeObject::MAX_SLOTS_COUNT <= INT32_MAX / sizeof(JS::Value),
                       "every caller of this method requires that a slot "
                       "number (or slot count) count multiplied by "
                       "sizeof(Value) can't overflow uint32_t (and sometimes "
                       "int32_t, too)");
     }
 
     uint32_t numFixedSlots() const {
         return reinterpret_cast<const shadow::Object*>(this)->numFixedSlots();
@@ -877,21 +882,29 @@ class NativeObject : public JSObject
      */
     static uint32_t dynamicSlotsCount(uint32_t nfixed, uint32_t span, const Class* clasp);
     static uint32_t dynamicSlotsCount(Shape* shape) {
         return dynamicSlotsCount(shape->numFixedSlots(), shape->slotSpan(), shape->getObjectClass());
     }
 
     /* Elements accessors. */
 
-    /* Upper bound on the number of elements in an object. */
-    static const uint32_t NELEMENTS_LIMIT = JS_BIT(28);
+    // The maximum size, in sizeof(Value), of the allocation used for an
+    // object's dense elements.  (This includes space used to store an
+    // ObjectElements instance.)
+    // |MAX_DENSE_ELEMENTS_ALLOCATION * sizeof(JS::Value)| shouldn't overflow
+    // int32_t (see elementsSizeMustNotOverflow).
+    static const uint32_t MAX_DENSE_ELEMENTS_ALLOCATION = (1 << 28) - 1;
+
+    // The maximum number of usable dense elements in an object.
+    static const uint32_t MAX_DENSE_ELEMENTS_COUNT =
+        MAX_DENSE_ELEMENTS_ALLOCATION - ObjectElements::VALUES_PER_HEADER;
 
     static void elementsSizeMustNotOverflow() {
-        static_assert((NativeObject::NELEMENTS_LIMIT - 1) <= INT32_MAX / sizeof(JS::Value),
+        static_assert(NativeObject::MAX_DENSE_ELEMENTS_COUNT <= INT32_MAX / sizeof(JS::Value),
                       "every caller of this method require that an element "
                       "count multiplied by sizeof(Value) can't overflow "
                       "uint32_t (and sometimes int32_t ,too)");
     }
 
     ObjectElements * getElementsHeader() const {
         return ObjectElements::fromElements(elements_);
     }
@@ -899,17 +912,17 @@ class NativeObject : public JSObject
     /* Accessors for elements. */
     bool ensureElements(ExclusiveContext* cx, uint32_t capacity) {
         MOZ_ASSERT(!denseElementsAreCopyOnWrite());
         if (capacity > getDenseCapacity())
             return growElements(cx, capacity);
         return true;
     }
 
-    static uint32_t goodAllocated(uint32_t n, uint32_t length);
+    static uint32_t goodElementsAllocationAmount(uint32_t n, uint32_t length);
     bool growElements(ExclusiveContext* cx, uint32_t newcap);
     void shrinkElements(ExclusiveContext* cx, uint32_t cap);
     void setDynamicElements(ObjectElements* header) {
         MOZ_ASSERT(!hasDynamicElements());
         elements_ = header->elements();
         MOZ_ASSERT(hasDynamicElements());
     }