Bug 1040593 (attempt 2) - Partly allocate elements for |new Array(N)| when N > 2048. r=jandem,bhackett.
authorNicholas Nethercote <nnethercote@mozilla.com>
Wed, 27 Aug 2014 23:35:57 -0700
changeset 203753 0f2020c52ad7e805692d84a5db9e297868f18694
parent 203752 635055d16df6cbca72dc7bb793732a400558ec4a
child 203754 c72942afe172ed0d10e197e75b614b35970da2eb
push id27435
push userryanvm@gmail.com
push dateFri, 05 Sep 2014 15:34:32 +0000
treeherdermozilla-central@d934dc4a99ac [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem, bhackett
bugs1040593
milestone35.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 1040593 (attempt 2) - Partly allocate elements for |new Array(N)| when N > 2048. r=jandem,bhackett.
js/src/builtin/RegExp.cpp
js/src/builtin/TestingFunctions.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/MCallOptimize.cpp
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/Recover.cpp
js/src/jit/Recover.h
js/src/jit/ScalarReplacement.cpp
js/src/jit/VMFunctions.cpp
js/src/jsapi.cpp
js/src/jsarray.cpp
js/src/jsarray.h
js/src/jsobj.cpp
js/src/jsreflect.cpp
js/src/vm/ArrayObject.h
js/src/vm/Debugger.cpp
js/src/vm/DebuggerMemory.cpp
js/src/vm/Interpreter.cpp
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -40,17 +40,17 @@ js::CreateRegExpMatchResult(JSContext *c
     /* Get the templateObject that defines the shape and type of the output object */
     JSObject *templateObject = cx->compartment()->regExps.getOrCreateMatchResultTemplateObject(cx);
     if (!templateObject)
         return false;
 
     size_t numPairs = matches.length();
     JS_ASSERT(numPairs > 0);
 
-    RootedObject arr(cx, NewDenseAllocatedArrayWithTemplate(cx, numPairs, templateObject));
+    RootedObject arr(cx, NewDenseFullyAllocatedArrayWithTemplate(cx, numPairs, templateObject));
     if (!arr)
         return false;
 
     /* Store a Value for each pair. */
     for (size_t i = 0; i < numPairs; i++) {
         const MatchPair &pair = matches[i];
 
         if (pair.isUndefined()) {
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1899,17 +1899,17 @@ FindPath(JSContext *cx, unsigned argc, j
     //     edge: <string describing outgoing edge from node>
     //   }
     //
     // or, if the node is some internal thing that isn't a proper JavaScript
     // value:
     //
     //   { node: undefined, edge: <string> }
     size_t length = nodes.length();
-    RootedObject result(cx, NewDenseAllocatedArray(cx, length));
+    RootedObject result(cx, NewDenseFullyAllocatedArray(cx, length));
     if (!result)
         return false;
     result->ensureDenseInitializedLength(cx, 0, length);
 
     // Walk |nodes| and |edges| in the stored order, and construct the result
     // array in start-to-target order.
     for (size_t i = 0; i < length; i++) {
         // Build an object describing the node and edge.
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -3921,17 +3921,17 @@ ParseNode::getConstantValue(ExclusiveCon
             count = pn_count - 1;
             pn = pn_head->pn_next;
         } else {
             JS_ASSERT(isOp(JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST));
             count = pn_count;
             pn = pn_head;
         }
 
-        RootedObject obj(cx, NewDenseAllocatedArray(cx, count, nullptr, MaybeSingletonObject));
+        RootedObject obj(cx, NewDenseFullyAllocatedArray(cx, count, nullptr, MaybeSingletonObject));
         if (!obj)
             return false;
 
         unsigned idx = 0;
         RootedId id(cx);
         for (; pn; idx++, pn = pn->pn_next) {
             if (!pn->getConstantValue(cx, allowObjects, &value))
                 return false;
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -5576,17 +5576,17 @@ IonBuilder::jsop_newarray(uint32_t count
         return abort("New array has unknown properties");
     }
 
     MConstant *templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     current->add(templateConst);
 
     MNewArray *ins = MNewArray::New(alloc(), constraints(), count, templateConst,
                                     templateObject->type()->initialHeap(constraints()),
-                                    MNewArray::NewArray_Allocating);
+                                    NewArray_FullyAllocating);
     current->add(ins);
     current->push(ins);
 
     types::TemporaryTypeSet::DoubleConversion conversion =
         ins->resultTypeSet()->convertDoubleElements(constraints());
 
     if (conversion == types::TemporaryTypeSet::AlwaysConvertToDoubles)
         templateObject->setShouldConvertDoubleElements();
@@ -8308,17 +8308,17 @@ IonBuilder::jsop_rest()
     unsigned numFormals = info().nargs() - 1;
     unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0;
 
     MConstant *templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     current->add(templateConst);
 
     MNewArray *array = MNewArray::New(alloc(), constraints(), numRest, templateConst,
                                       templateObject->type()->initialHeap(constraints()),
-                                      MNewArray::NewArray_Allocating);
+                                      NewArray_FullyAllocating);
     current->add(array);
 
     if (numRest == 0) {
         // No more updating to do. (Note that in this one case the length from
         // the template object is already correct.)
         current->push(array);
         return true;
     }
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -278,27 +278,27 @@ IonBuilder::inlineMathFunction(CallInfo 
     current->push(ins);
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningStatus
 IonBuilder::inlineArray(CallInfo &callInfo)
 {
     uint32_t initLength = 0;
-    MNewArray::AllocatingBehaviour allocating = MNewArray::NewArray_Unallocating;
+    AllocatingBehaviour allocating = NewArray_Unallocating;
 
     JSObject *templateObject = inspector->getTemplateObjectForNative(pc, js_Array);
     if (!templateObject)
         return InliningStatus_NotInlined;
     JS_ASSERT(templateObject->is<ArrayObject>());
 
     // Multiple arguments imply array initialization, not just construction.
     if (callInfo.argc() >= 2) {
         initLength = callInfo.argc();
-        allocating = MNewArray::NewArray_Allocating;
+        allocating = NewArray_FullyAllocating;
 
         types::TypeObjectKey *type = types::TypeObjectKey::get(templateObject);
         if (!type->unknownProperties()) {
             types::HeapTypeSetKey elemTypes = type->property(JSID_VOID);
 
             for (uint32_t i = 0; i < initLength; i++) {
                 MDefinition *value = callInfo.getArg(i);
                 if (!TypeSetIncludes(elemTypes.maybeTypes(), value->type(), value->resultTypeSet())) {
@@ -323,18 +323,21 @@ IonBuilder::inlineArray(CallInfo &callIn
             return InliningStatus_NotInlined;
 
         // 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 != templateObject->as<ArrayObject>().length())
             return InliningStatus_NotInlined;
 
-        if (initLength <= ArrayObject::EagerAllocationMaxLength)
-            allocating = MNewArray::NewArray_Allocating;
+        // Don't inline large allocations.
+        if (initLength > ArrayObject::EagerAllocationMaxLength)
+            return InliningStatus_NotInlined;
+
+        allocating = NewArray_FullyAllocating;
     }
 
     callInfo.setImplicitlyUsedUnchecked();
 
     types::TemporaryTypeSet::DoubleConversion conversion =
         getInlineReturnTypeSet()->convertDoubleElements(constraints());
     if (conversion == types::TemporaryTypeSet::AlwaysConvertToDoubles)
         templateObject->setShouldConvertDoubleElements();
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -3028,20 +3028,19 @@ MObjectState::Copy(TempAllocator &alloc,
 bool
 MNewArray::shouldUseVM() const
 {
     JS_ASSERT(count() < JSObject::NELEMENTS_LIMIT);
 
     size_t arraySlots =
         gc::GetGCKindSlots(templateObject()->tenuredGetAllocKind()) - ObjectElements::VALUES_PER_HEADER;
 
-    // Allocate space using the VMCall
-    // when mir hints it needs to get allocated immediately,
-    // but only when data doesn't fit the available array slots.
-    bool allocating = isAllocating() && count() > arraySlots;
+    // Allocate space using the VMCall when mir hints it needs to get allocated
+    // immediately, but only when data doesn't fit the available array slots.
+    bool allocating = allocatingBehaviour() != NewArray_Unallocating && count() > arraySlots;
 
     return templateObject()->hasSingletonType() || allocating;
 }
 
 bool
 MLoadFixedSlot::mightAlias(const MDefinition *store) const
 {
     if (store->isStoreFixedSlot() && store->toStoreFixedSlot()->slot() != slot())
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -2110,22 +2110,16 @@ class AlwaysTenured
 typedef AlwaysTenured<JSObject*> AlwaysTenuredObject;
 typedef AlwaysTenured<JSFunction*> AlwaysTenuredFunction;
 typedef AlwaysTenured<JSScript*> AlwaysTenuredScript;
 typedef AlwaysTenured<PropertyName*> AlwaysTenuredPropertyName;
 typedef AlwaysTenured<Shape*> AlwaysTenuredShape;
 
 class MNewArray : public MUnaryInstruction
 {
-  public:
-    enum AllocatingBehaviour {
-        NewArray_Allocating,
-        NewArray_Unallocating
-    };
-
   private:
     // Number of space to allocate for the array.
     uint32_t count_;
 
     // Heap where the array should be allocated.
     gc::InitialHeap initialHeap_;
     // Allocate space at initialization or not
     AllocatingBehaviour allocating_;
@@ -2160,18 +2154,18 @@ class MNewArray : public MUnaryInstructi
     JSObject *templateObject() const {
         return &getOperand(0)->toConstant()->value().toObject();
     }
 
     gc::InitialHeap initialHeap() const {
         return initialHeap_;
     }
 
-    bool isAllocating() const {
-        return allocating_ == NewArray_Allocating;
+    AllocatingBehaviour allocatingBehaviour() const {
+        return allocating_;
     }
 
     // Returns true if the code generator should call through to the
     // VM rather than the fast path.
     bool shouldUseVM() const;
 
     // NewArray is marked as non-effectful because all our allocations are
     // either lazy when we are using "new Array(length)" or bounded by the
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -976,24 +976,24 @@ RNewObject::recover(JSContext *cx, Snaps
 }
 
 bool
 MNewArray::writeRecoverData(CompactBufferWriter &writer) const
 {
     MOZ_ASSERT(canRecoverOnBailout());
     writer.writeUnsigned(uint32_t(RInstruction::Recover_NewArray));
     writer.writeUnsigned(count());
-    writer.writeByte(isAllocating());
+    writer.writeByte(uint8_t(allocatingBehaviour()));
     return true;
 }
 
 RNewArray::RNewArray(CompactBufferReader &reader)
 {
     count_ = reader.readUnsigned();
-    isAllocating_ = reader.readByte();
+    allocatingBehaviour_ = AllocatingBehaviour(reader.readByte());
 }
 
 bool
 RNewArray::recover(JSContext *cx, SnapshotIterator &iter) const
 {
     RootedObject templateObject(cx, &iter.read().toObject());
     RootedValue result(cx);
     RootedTypeObject type(cx);
@@ -1001,17 +1001,17 @@ RNewArray::recover(JSContext *cx, Snapsh
     // Use AutoEnterAnalysis to avoid invoking the object metadata callback
     // while bailing out, which could try to walk the stack.
     types::AutoEnterAnalysis enter(cx);
 
     // See CodeGenerator::visitNewArrayCallVM
     if (!templateObject->hasSingletonType())
         type = templateObject->type();
 
-    JSObject *resultObject = NewDenseArray(cx, count_, type, isAllocating_);
+    JSObject *resultObject = NewDenseArray(cx, count_, type, allocatingBehaviour_);
     if (!resultObject)
         return false;
 
     result.setObject(*resultObject);
     iter.storeInstructionResult(result);
     return true;
 }
 
--- a/js/src/jit/Recover.h
+++ b/js/src/jit/Recover.h
@@ -4,16 +4,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jit_Recover_h
 #define jit_Recover_h
 
 #include "mozilla/Attributes.h"
 
+#include "jsarray.h"
+
 #include "jit/Snapshots.h"
 
 struct JSContext;
 
 namespace js {
 namespace jit {
 
 #define RECOVER_OPCODE_LIST(_)                  \
@@ -500,17 +502,17 @@ class RNewObject MOZ_FINAL : public RIns
 
     bool recover(JSContext *cx, SnapshotIterator &iter) const;
 };
 
 class RNewArray MOZ_FINAL : public RInstruction
 {
   private:
     uint32_t count_;
-    bool isAllocating_;
+    AllocatingBehaviour allocatingBehaviour_;
 
   public:
     RINSTRUCTION_HEADER_(NewArray)
 
     virtual uint32_t numOperands() const {
         return 1;
     }
 
--- a/js/src/jit/ScalarReplacement.cpp
+++ b/js/src/jit/ScalarReplacement.cpp
@@ -503,17 +503,17 @@ static bool
 IsArrayEscaped(MInstruction *ins)
 {
     MOZ_ASSERT(ins->type() == MIRType_Object);
     MOZ_ASSERT(ins->isNewArray());
     uint32_t count = ins->toNewArray()->count();
 
     // The array is probably too large to be represented efficiently with
     // MArrayState, and we do not want to make huge allocations during bailouts.
-    if (!ins->toNewArray()->isAllocating()) {
+    if (ins->toNewArray()->allocatingBehaviour() == NewArray_Unallocating) {
         JitSpewDef(JitSpew_Escape, "Array is not allocated\n", ins);
         return true;
     }
 
     if (count >= 16) {
         JitSpewDef(JitSpew_Escape, "Array has too many elements\n", ins);
         return true;
     }
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -310,17 +310,17 @@ IteratorMore(JSContext *cx, HandleObject
 
 JSObject*
 NewInitArray(JSContext *cx, uint32_t count, types::TypeObject *typeArg)
 {
     RootedTypeObject type(cx, typeArg);
     NewObjectKind newKind = !type ? SingletonObject : GenericObject;
     if (type && type->shouldPreTenure())
         newKind = TenuredObject;
-    RootedObject obj(cx, NewDenseAllocatedArray(cx, count, nullptr, newKind));
+    RootedObject obj(cx, NewDenseFullyAllocatedArray(cx, count, nullptr, newKind));
     if (!obj)
         return nullptr;
 
     if (type)
         obj->setType(type);
 
     return obj;
 }
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -3768,17 +3768,17 @@ JS_NewArrayObject(JSContext *cx, const J
 
 JS_PUBLIC_API(JSObject *)
 JS_NewArrayObject(JSContext *cx, size_t length)
 {
     JS_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
 
-    return NewDenseAllocatedArray(cx, length);
+    return NewDenseFullyAllocatedArray(cx, length);
 }
 
 JS_PUBLIC_API(bool)
 JS_IsArrayObject(JSContext *cx, JS::HandleObject obj)
 {
     assertSameCompartment(cx, obj);
     return ObjectClassIs(obj, ESClass_Array, cx);
 }
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -6,16 +6,18 @@
 
 #include "jsarray.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/MathAlgorithms.h"
 
+#include <algorithm>
+
 #include "jsapi.h"
 #include "jsatom.h"
 #include "jscntxt.h"
 #include "jsfriendapi.h"
 #include "jsfun.h"
 #include "jsiter.h"
 #include "jsnum.h"
 #include "jsobj.h"
@@ -160,17 +162,17 @@ ToId(JSContext *cx, uint32_t index, Muta
 }
 
 /*
  * If the property at the given index exists, get its value into location
  * pointed by vp and set *hole to false. Otherwise set *hole to true and *vp
  * to JSVAL_VOID. This function assumes that the location pointed by vp is
  * properly rooted and can be used as GC-protected storage for temporaries.
  */
-template<typename IndexType>
+template <typename IndexType>
 static inline bool
 DoGetElement(JSContext *cx, HandleObject obj, HandleObject receiver,
              IndexType index, bool *hole, MutableHandleValue vp)
 {
     RootedId id(cx);
     if (!ToId(cx, index, &id))
         return false;
 
@@ -185,31 +187,31 @@ DoGetElement(JSContext *cx, HandleObject
     } else {
         if (!JSObject::getGeneric(cx, obj, receiver, id, vp))
             return false;
         *hole = false;
     }
     return true;
 }
 
-template<typename IndexType>
+template <typename IndexType>
 static void
 AssertGreaterThanZero(IndexType index)
 {
     JS_ASSERT(index >= 0);
     JS_ASSERT(index == floor(index));
 }
 
 template<>
 void
 AssertGreaterThanZero(uint32_t index)
 {
 }
 
-template<typename IndexType>
+template <typename IndexType>
 static bool
 GetElement(JSContext *cx, HandleObject obj, HandleObject receiver,
            IndexType index, bool *hole, MutableHandleValue vp)
 {
     AssertGreaterThanZero(index);
     if (obj->isNative() && index < obj->getDenseInitializedLength()) {
         vp.set(obj->getDenseElement(uint32_t(index)));
         if (!vp.isMagic(JS_ELEMENTS_HOLE)) {
@@ -222,17 +224,17 @@ GetElement(JSContext *cx, HandleObject o
             *hole = false;
             return true;
         }
     }
 
     return DoGetElement(cx, obj, receiver, index, hole, vp);
 }
 
-template<typename IndexType>
+template <typename IndexType>
 static inline bool
 GetElement(JSContext *cx, HandleObject obj, IndexType index, bool *hole, MutableHandleValue vp)
 {
     return GetElement(cx, obj, obj, index, hole, vp);
 }
 
 static bool
 GetElementsSlow(JSContext *cx, HandleObject aobj, uint32_t length, Value *vp)
@@ -1733,17 +1735,17 @@ MatchNumericComparator(JSContext *cx, co
         return Match_LeftMinusRight;
 
     if (arg0 == 1 && arg1 == 0)
         return Match_RightMinusLeft;
 
     return Match_None;
 }
 
-template<typename K, typename C>
+template <typename K, typename C>
 static inline bool
 MergeSortByKey(K keys, size_t len, K scratch, C comparator, AutoValueVector *vec)
 {
     MOZ_ASSERT(vec->length() >= len);
 
     /* Sort keys. */
     if (!MergeSort(keys, len, scratch, comparator))
         return false;
@@ -2440,17 +2442,17 @@ js::array_splice_impl(JSContext *cx, uns
     if (CanOptimizeForDenseStorage(obj, actualStart, actualDeleteCount, cx)) {
         if (returnValueIsUsed) {
             arr = NewDenseCopiedArray(cx, actualDeleteCount, obj, actualStart);
             if (!arr)
                 return false;
             TryReuseArrayType(obj, arr);
         }
     } else {
-        arr = NewDenseAllocatedArray(cx, actualDeleteCount);
+        arr = NewDenseFullyAllocatedArray(cx, actualDeleteCount);
         if (!arr)
             return false;
         TryReuseArrayType(obj, arr);
 
         RootedValue fromValue(cx);
         for (uint32_t k = 0; k < actualDeleteCount; k++) {
             bool hole;
             if (!CheckForInterrupt(cx) ||
@@ -2749,17 +2751,17 @@ array_slice(JSContext *cx, unsigned argc
             end = (uint32_t)d;
         }
     }
 
     if (begin > end)
         begin = end;
 
     Rooted<ArrayObject*> narr(cx);
-    narr = NewDenseAllocatedArray(cx, end - begin);
+    narr = NewDenseFullyAllocatedArray(cx, end - begin);
     if (!narr)
         return false;
     TryReuseArrayType(obj, narr);
 
     if (obj->is<ArrayObject>() && !ObjectMayHaveExtraIndexedProperties(obj)) {
         if (obj->getDenseInitializedLength() > begin) {
             uint32_t numSourceElements = obj->getDenseInitializedLength() - begin;
             uint32_t initLength = Min(numSourceElements, end - begin);
@@ -2838,17 +2840,17 @@ array_filter(JSContext *cx, unsigned arg
     RootedObject callable(cx, ValueToCallable(cx, args[0], args.length() - 1));
     if (!callable)
         return false;
 
     /* Step 5. */
     RootedValue thisv(cx, args.length() >= 2 ? args[1] : UndefinedValue());
 
     /* Step 6. */
-    RootedObject arr(cx, NewDenseAllocatedArray(cx, 0));
+    RootedObject arr(cx, NewDenseFullyAllocatedArray(cx, 0));
     if (!arr)
         return false;
     TypeObject *newtype = GetTypeCallerInitObject(cx, JSProto_Array);
     if (!newtype)
         return false;
     arr->setType(newtype);
 
     /* Step 7. */
@@ -3079,21 +3081,23 @@ js_Array(JSContext *cx, unsigned argc, V
         length = ToUint32(d);
         if (d != double(length)) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
             return false;
         }
     }
 
     /*
-     * Allocate dense elements eagerly for small arrays, to avoid reallocating
-     * elements when filling the array.
+     * Allocate up to |EagerAllocationMaxLength| dense elements eagerly, to
+     * avoid reallocating elements when filling the array.
      */
-    bool allocateArray = length <= ArrayObject::EagerAllocationMaxLength;
-    RootedObject obj(cx, NewDenseArray(cx, length, type, allocateArray));
+    AllocatingBehaviour allocating = (length <= ArrayObject::EagerAllocationMaxLength)
+                                   ? NewArray_FullyAllocating
+                                   : NewArray_PartlyAllocating;
+    RootedObject obj(cx, NewDenseArray(cx, length, type, allocating));
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
 static JSObject *
@@ -3173,17 +3177,17 @@ EnsureNewArrayElements(ExclusiveContext 
     if (!obj->ensureElements(cx, length))
         return false;
 
     JS_ASSERT_IF(cap, !obj->hasDynamicElements());
 
     return true;
 }
 
-template<bool allocateCapacity>
+template <uint32_t maxLength>
 static MOZ_ALWAYS_INLINE ArrayObject *
 NewArray(ExclusiveContext *cxArg, uint32_t length,
          JSObject *protoArg, NewObjectKind newKind = GenericObject)
 {
     gc::AllocKind allocKind = GuessArrayGCKind(length);
     JS_ASSERT(CanBeFinalizedInBackground(allocKind, &ArrayObject::class_));
     allocKind = GetBackgroundAllocKind(allocKind);
 
@@ -3196,18 +3200,21 @@ NewArray(ExclusiveContext *cxArg, uint32
         {
             gc::InitialHeap heap = GetInitialHeap(newKind, &ArrayObject::class_);
             JSObject *obj = cache.newObjectFromHit<NoGC>(cx, entry, heap);
             if (obj) {
                 /* Fixup the elements pointer and length, which may be incorrect. */
                 ArrayObject *arr = &obj->as<ArrayObject>();
                 arr->setFixedElements();
                 arr->setLength(cx, length);
-                if (allocateCapacity && !EnsureNewArrayElements(cx, arr, length))
+                if (maxLength > 0 &&
+                    !EnsureNewArrayElements(cx, arr, std::min(maxLength, length)))
+                {
                     return nullptr;
+                }
                 return arr;
             } else {
                 RootedObject proto(cxArg, protoArg);
                 obj = cache.newObjectFromHit<CanGC>(cx, entry, heap);
                 JS_ASSERT(!obj);
                 protoArg = proto;
             }
         }
@@ -3254,57 +3261,70 @@ NewArray(ExclusiveContext *cxArg, uint32
     if (newKind == SingletonObject && !JSObject::setSingletonType(cxArg, arr))
         return nullptr;
 
     if (entry != -1) {
         cxArg->asJSContext()->runtime()->newObjectCache.fillGlobal(entry, &ArrayObject::class_,
                                                                    cxArg->global(), allocKind, arr);
     }
 
-    if (allocateCapacity && !EnsureNewArrayElements(cxArg, arr, length))
+    if (maxLength > 0 && !EnsureNewArrayElements(cxArg, arr, std::min(maxLength, length)))
         return nullptr;
 
     probes::CreateObject(cxArg, arr);
     return arr;
 }
 
 ArrayObject * JS_FASTCALL
 js::NewDenseEmptyArray(JSContext *cx, JSObject *proto /* = nullptr */,
                        NewObjectKind newKind /* = GenericObject */)
 {
-    return NewArray<false>(cx, 0, proto, newKind);
+    return NewArray<0>(cx, 0, proto, newKind);
 }
 
 ArrayObject * JS_FASTCALL
-js::NewDenseAllocatedArray(ExclusiveContext *cx, uint32_t length, JSObject *proto /* = nullptr */,
-                           NewObjectKind newKind /* = GenericObject */)
+js::NewDenseFullyAllocatedArray(ExclusiveContext *cx, uint32_t length,
+                                JSObject *proto /* = nullptr */,
+                                NewObjectKind newKind /* = GenericObject */)
 {
-    return NewArray<true>(cx, length, proto, newKind);
+    return NewArray<JSObject::NELEMENTS_LIMIT>(cx, length, proto, newKind);
+}
+
+ArrayObject * JS_FASTCALL
+js::NewDensePartlyAllocatedArray(ExclusiveContext *cx, uint32_t length,
+                                 JSObject *proto /* = nullptr */,
+                                 NewObjectKind newKind /* = GenericObject */)
+{
+    return NewArray<ArrayObject::EagerAllocationMaxLength>(cx, length, proto, newKind);
 }
 
 ArrayObject * JS_FASTCALL
 js::NewDenseUnallocatedArray(ExclusiveContext *cx, uint32_t length, JSObject *proto /* = nullptr */,
                              NewObjectKind newKind /* = GenericObject */)
 {
-    return NewArray<false>(cx, length, proto, newKind);
+    return NewArray<0>(cx, length, proto, newKind);
 }
 
 ArrayObject * JS_FASTCALL
-js::NewDenseArray(ExclusiveContext *cx, uint32_t length, HandleTypeObject type, bool allocateArray)
+js::NewDenseArray(ExclusiveContext *cx, uint32_t length, HandleTypeObject type,
+                  AllocatingBehaviour allocating)
 {
     NewObjectKind newKind = !type ? SingletonObject : GenericObject;
     if (type && type->shouldPreTenure())
         newKind = TenuredObject;
 
-    // Allocate dense elements eagerly for small arrays, to avoid reallocating
-    // elements when filling the array.
-    ArrayObject *arr = allocateArray
-                       ? NewDenseAllocatedArray(cx, length, nullptr, newKind)
-                       : NewDenseUnallocatedArray(cx, length, nullptr, newKind);
-
+    ArrayObject *arr;
+    if (allocating == NewArray_Unallocating) {
+        arr = NewDenseUnallocatedArray(cx, length, nullptr, newKind);
+    } else if (allocating == NewArray_PartlyAllocating) {
+        arr = NewDensePartlyAllocatedArray(cx, length, nullptr, newKind);
+    } else {
+        JS_ASSERT(allocating == NewArray_FullyAllocating);
+        arr = NewDenseFullyAllocatedArray(cx, length, nullptr, newKind);
+    }
     if (!arr)
         return nullptr;
 
     if (type)
         arr->setType(type);
 
     // If the length calculation overflowed, make sure that is marked for the
     // new type.
@@ -3315,17 +3335,17 @@ js::NewDenseArray(ExclusiveContext *cx, 
 }
 
 ArrayObject *
 js::NewDenseCopiedArray(JSContext *cx, uint32_t length, HandleObject src, uint32_t elementOffset,
                         JSObject *proto /* = nullptr */)
 {
     JS_ASSERT(!src->isIndexed());
 
-    ArrayObject* arr = NewArray<true>(cx, length, proto);
+    ArrayObject* arr = NewArray<JSObject::NELEMENTS_LIMIT>(cx, length, proto);
     if (!arr)
         return nullptr;
 
     JS_ASSERT(arr->getDenseCapacity() >= length);
 
     const Value* vp = src->getDenseElements() + elementOffset;
     arr->setDenseInitializedLength(vp ? length : 0);
 
@@ -3335,32 +3355,32 @@ js::NewDenseCopiedArray(JSContext *cx, u
     return arr;
 }
 
 // values must point at already-rooted Value objects
 ArrayObject *
 js::NewDenseCopiedArray(JSContext *cx, uint32_t length, const Value *values,
                         JSObject *proto /* = nullptr */, NewObjectKind newKind /* = GenericObject */)
 {
-    ArrayObject* arr = NewArray<true>(cx, length, proto);
+    ArrayObject* arr = NewArray<JSObject::NELEMENTS_LIMIT>(cx, length, proto);
     if (!arr)
         return nullptr;
 
     JS_ASSERT(arr->getDenseCapacity() >= length);
 
     arr->setDenseInitializedLength(values ? length : 0);
 
     if (values)
         arr->initDenseElements(0, values, length);
 
     return arr;
 }
 
 ArrayObject *
-js::NewDenseAllocatedArrayWithTemplate(JSContext *cx, uint32_t length, JSObject *templateObject)
+js::NewDenseFullyAllocatedArrayWithTemplate(JSContext *cx, uint32_t length, JSObject *templateObject)
 {
     gc::AllocKind allocKind = GuessArrayGCKind(length);
     JS_ASSERT(CanBeFinalizedInBackground(allocKind, &ArrayObject::class_));
     allocKind = GetBackgroundAllocKind(allocKind);
 
     RootedTypeObject type(cx, templateObject->type());
     RootedShape shape(cx, templateObject->lastProperty());
 
--- a/js/src/jsarray.h
+++ b/js/src/jsarray.h
@@ -43,48 +43,63 @@ namespace js {
 
 class ArrayObject;
 
 /* Create a dense array with no capacity allocated, length set to 0. */
 extern ArrayObject * JS_FASTCALL
 NewDenseEmptyArray(JSContext *cx, JSObject *proto = nullptr,
                    NewObjectKind newKind = GenericObject);
 
-/* Create a dense array with length and capacity == 'length', initialized length set to 0. */
-extern ArrayObject * JS_FASTCALL
-NewDenseAllocatedArray(ExclusiveContext *cx, uint32_t length, JSObject *proto = nullptr,
-                       NewObjectKind newKind = GenericObject);
-
 /*
  * Create a dense array with a set length, but without allocating space for the
  * contents. This is useful, e.g., when accepting length from the user.
  */
 extern ArrayObject * JS_FASTCALL
 NewDenseUnallocatedArray(ExclusiveContext *cx, uint32_t length, JSObject *proto = nullptr,
                          NewObjectKind newKind = GenericObject);
 
 /*
+ * Create a dense array with length and capacity == |length|, initialized length set to 0,
+ * but with only |EagerAllocationMaxLength| elements allocated.
+ */
+extern ArrayObject * JS_FASTCALL
+NewDensePartlyAllocatedArray(ExclusiveContext *cx, uint32_t length, JSObject *proto = nullptr,
+                             NewObjectKind newKind = GenericObject);
+
+/* Create a dense array with length and capacity == 'length', initialized length set to 0. */
+extern ArrayObject * JS_FASTCALL
+NewDenseFullyAllocatedArray(ExclusiveContext *cx, uint32_t length, JSObject *proto = nullptr,
+                            NewObjectKind newKind = GenericObject);
+
+enum AllocatingBehaviour {
+    NewArray_Unallocating,
+    NewArray_PartlyAllocating,
+    NewArray_FullyAllocating
+};
+
+/*
  * Create a dense array with a set length, but only allocates space for the
  * contents if the length is not excessive.
  */
 extern ArrayObject * JS_FASTCALL
-NewDenseArray(ExclusiveContext *cx, uint32_t length, HandleTypeObject type, bool allocateArray);
+NewDenseArray(ExclusiveContext *cx, uint32_t length, HandleTypeObject type,
+              AllocatingBehaviour allocating);
 
 /* Create a dense array with a copy of the dense array elements in src. */
 extern ArrayObject *
 NewDenseCopiedArray(JSContext *cx, uint32_t length, HandleObject src, uint32_t elementOffset, JSObject *proto = nullptr);
 
 /* Create a dense array from the given array values, which must be rooted */
 extern ArrayObject *
 NewDenseCopiedArray(JSContext *cx, uint32_t length, const Value *values, JSObject *proto = nullptr,
                     NewObjectKind newKind = GenericObject);
 
 /* Create a dense array based on templateObject with the given length. */
 extern ArrayObject *
-NewDenseAllocatedArrayWithTemplate(JSContext *cx, uint32_t length, JSObject *templateObject);
+NewDenseFullyAllocatedArrayWithTemplate(JSContext *cx, uint32_t length, JSObject *templateObject);
 
 /* Create a dense array with the same copy-on-write elements as another object. */
 extern JSObject *
 NewDenseCopyOnWriteArray(JSContext *cx, HandleObject templateObject, gc::InitialHeap heap);
 
 /*
  * Determines whether a write to the given element on |obj| should fail because
  * |obj| is an Array with a non-writable length, and writing that element would
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -2367,17 +2367,17 @@ js::CloneObjectLiteral(JSContext *cx, Ha
         return NewReshapedObject(cx, typeObj, parent, kind, shape);
     }
 
     JS_ASSERT(srcObj->is<ArrayObject>());
     JS_ASSERT(srcObj->denseElementsAreCopyOnWrite());
     JS_ASSERT(srcObj->getElementsHeader()->ownerObject() == srcObj);
 
     size_t length = srcObj->as<ArrayObject>().length();
-    RootedObject res(cx, NewDenseAllocatedArray(cx, length, nullptr, MaybeSingletonObject));
+    RootedObject res(cx, NewDenseFullyAllocatedArray(cx, length, nullptr, MaybeSingletonObject));
     if (!res)
         return nullptr;
 
     RootedId id(cx);
     RootedValue value(cx);
     for (size_t i = 0; i < length; i++) {
         // The only markable values in copy on write arrays are atoms, which
         // can be freely copied between compartments.
--- a/js/src/jsreflect.cpp
+++ b/js/src/jsreflect.cpp
@@ -719,17 +719,17 @@ NodeBuilder::newNode(ASTType type, Token
 bool
 NodeBuilder::newArray(NodeVector &elts, MutableHandleValue dst)
 {
     const size_t len = elts.length();
     if (len > UINT32_MAX) {
         js_ReportAllocationOverflow(cx);
         return false;
     }
-    RootedObject array(cx, NewDenseAllocatedArray(cx, uint32_t(len)));
+    RootedObject array(cx, NewDenseFullyAllocatedArray(cx, uint32_t(len)));
     if (!array)
         return false;
 
     for (size_t i = 0; i < len; i++) {
         RootedValue val(cx, elts[i]);
 
         JS_ASSERT_IF(val.isMagic(), val.whyMagic() == JS_SERIALIZE_NO_NODE);
 
--- a/js/src/vm/ArrayObject.h
+++ b/js/src/vm/ArrayObject.h
@@ -9,18 +9,20 @@
 
 #include "jsobj.h"
 
 namespace js {
 
 class ArrayObject : public JSObject
 {
   public:
-    // Array(x) eagerly allocates dense elements if x <= this value.
-    static const uint32_t EagerAllocationMaxLength = 2048;
+    // Array(x) eagerly allocates dense elements if x <= this value. Without
+    // the subtraction the max would roll over to the next power-of-two (4096)
+    // due to the way that growElements() and goodAllocated() work.
+    static const uint32_t EagerAllocationMaxLength = 2048 - ObjectElements::VALUES_PER_HEADER;
 
     static const Class class_;
 
     bool lengthIsWritable() const {
         return !getElementsHeader()->hasNonwritableArrayLength();
     }
 
     uint32_t length() const {
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -2178,17 +2178,17 @@ Debugger::hasDebuggee(JSContext *cx, uns
     args.rval().setBoolean(!!dbg->debuggees.lookup(global));
     return true;
 }
 
 bool
 Debugger::getDebuggees(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_DEBUGGER(cx, argc, vp, "getDebuggees", args, dbg);
-    RootedObject arrobj(cx, NewDenseAllocatedArray(cx, dbg->debuggees.count()));
+    RootedObject arrobj(cx, NewDenseFullyAllocatedArray(cx, dbg->debuggees.count()));
     if (!arrobj)
         return false;
     arrobj->ensureDenseInitializedLength(cx, 0, dbg->debuggees.count());
     unsigned i = 0;
     for (GlobalObjectSet::Enum e(dbg->debuggees); !e.empty(); e.popFront()) {
         RootedValue v(cx, ObjectValue(*e.front()));
         if (!dbg->wrapDebuggeeValue(cx, &v))
             return false;
@@ -2933,17 +2933,17 @@ Debugger::findScripts(JSContext *cx, uns
      * the JS array as we go, because we mustn't allocate JS objects or GC
      * while we use the CellIter.
      */
     AutoScriptVector scripts(cx);
 
     if (!query.findScripts(&scripts))
         return false;
 
-    RootedObject result(cx, NewDenseAllocatedArray(cx, scripts.length()));
+    RootedObject result(cx, NewDenseFullyAllocatedArray(cx, scripts.length()));
     if (!result)
         return false;
 
     result->ensureDenseInitializedLength(cx, 0, scripts.length());
 
     for (size_t i = 0; i < scripts.length(); i++) {
         JSObject *scriptObject = dbg->wrapScript(cx, scripts[i]);
         if (!scriptObject)
@@ -5212,17 +5212,17 @@ DebuggerObject_getParameterNames(JSConte
     RootedFunction fun(cx, &obj->as<JSFunction>());
 
     /* Only hand out parameter info for debuggee functions. */
     if (!dbg->observesGlobal(&fun->global())) {
         args.rval().setUndefined();
         return true;
     }
 
-    RootedObject result(cx, NewDenseAllocatedArray(cx, fun->nargs()));
+    RootedObject result(cx, NewDenseFullyAllocatedArray(cx, fun->nargs()));
     if (!result)
         return false;
     result->ensureDenseInitializedLength(cx, 0, fun->nargs());
 
     if (fun->isInterpreted()) {
         RootedScript script(cx, GetOrCreateFunctionScript(cx, fun));
         if (!script)
             return false;
--- a/js/src/vm/DebuggerMemory.cpp
+++ b/js/src/vm/DebuggerMemory.cpp
@@ -186,17 +186,17 @@ DebuggerMemory::drainAllocationsLog(JSCo
     if (!dbg->trackingAllocationSites) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_TRACKING_ALLOCATIONS,
                              "drainAllocationsLog");
         return false;
     }
 
     size_t length = dbg->allocationsLogLength;
 
-    RootedObject result(cx, NewDenseAllocatedArray(cx, length));
+    RootedObject result(cx, NewDenseFullyAllocatedArray(cx, length));
     if (!result)
         return false;
     result->ensureDenseInitializedLength(cx, 0, length);
 
     for (size_t i = 0; i < length; i++) {
         Debugger::AllocationSite *allocSite = dbg->allocationsLog.popFirst();
         result->setDenseElement(i, ObjectOrNullValue(allocSite->frame));
         js_delete(allocSite);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -3066,17 +3066,17 @@ CASE(JSOP_NEWINIT)
 }
 END_CASE(JSOP_NEWINIT)
 
 CASE(JSOP_NEWARRAY)
 {
     unsigned count = GET_UINT24(REGS.pc);
     RootedObject &obj = rootObject0;
     NewObjectKind newKind = UseNewTypeForInitializer(script, REGS.pc, &ArrayObject::class_);
-    obj = NewDenseAllocatedArray(cx, count, nullptr, newKind);
+    obj = NewDenseFullyAllocatedArray(cx, count, nullptr, newKind);
     if (!obj || !SetInitializerObjectType(cx, script, REGS.pc, obj, newKind))
         goto error;
 
     PUSH_OBJECT(*obj);
 }
 END_CASE(JSOP_NEWARRAY)
 
 CASE(JSOP_NEWARRAY_COPYONWRITE)
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -404,17 +404,17 @@ js::intrinsic_NewDenseArray(JSContext *c
     // Check that index is an int32
     if (!args[0].isInt32()) {
         JS_ReportError(cx, "Expected int32 as second argument");
         return false;
     }
     uint32_t length = args[0].toInt32();
 
     // Make a new buffer and initialize it up to length.
-    RootedObject buffer(cx, NewDenseAllocatedArray(cx, length));
+    RootedObject buffer(cx, NewDenseFullyAllocatedArray(cx, length));
     if (!buffer)
         return false;
 
     types::TypeObject *newtype = types::GetTypeCallerInitObject(cx, JSProto_Array);
     if (!newtype)
         return false;
     buffer->setType(newtype);