Bug 784345 - Fix behavior of holes in ParallelArrays (r=dmandelin)
authorShu-yu Guo <shu@rfrn.org>
Wed, 29 Aug 2012 01:24:18 -0700
changeset 103752 91d39d72ac86b08a20e8039fd90841be17a2adf5
parent 103751 5a03f87a931b88430dc60e12fb74d10304c51833
child 103753 c06b09e067d7e50f36ac41905b3fa52d2c8915c2
push id23376
push userryanvm@gmail.com
push dateThu, 30 Aug 2012 00:15:25 +0000
treeherdermozilla-central@706174d31a02 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdmandelin
bugs784345
milestone18.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 784345 - Fix behavior of holes in ParallelArrays (r=dmandelin)
js/src/builtin/ParallelArray-inl.h
js/src/builtin/ParallelArray.cpp
js/src/builtin/ParallelArray.h
js/src/jit-test/tests/parallelarray/constructor-5.js
js/src/jit-test/tests/parallelarray/element-4.js
js/src/jit-test/tests/parallelarray/holes-1.js
js/src/jit-test/tests/parallelarray/holes-2.js
js/src/jsiter.cpp
--- a/js/src/builtin/ParallelArray-inl.h
+++ b/js/src/builtin/ParallelArray-inl.h
@@ -163,29 +163,16 @@ ParallelArrayObject::outermostDimension(
 
 inline bool
 ParallelArrayObject::isOneDimensional()
 {
     return dimensionArray()->getDenseArrayInitializedLength() == 1;
 }
 
 inline bool
-ParallelArrayObject::inOutermostDimensionRange(uint32_t index)
-{
-    return index < outermostDimension();
-}
-
-inline bool
-ParallelArrayObject::inOutermostDimensionRange(JSContext *cx, HandleId id)
-{
-    uint32_t i;
-    return js_IdIsIndex(id, &i) && inOutermostDimensionRange(i);
-}
-
-inline bool
 ParallelArrayObject::getDimensions(JSContext *cx, IndexVector &dims)
 {
     RootedObject obj(cx, dimensionArray());
     if (!obj)
         return false;
     return DenseArrayToIndexVector(cx, obj, dims);
 }
 
--- a/js/src/builtin/ParallelArray.cpp
+++ b/js/src/builtin/ParallelArray.cpp
@@ -34,39 +34,16 @@ typedef ParallelArrayObject::IndexInfo I
 bool
 ParallelArrayObject::IndexInfo::isInitialized()
 {
     return (dimensions.length() > 0 &&
             indices.capacity() >= dimensions.length() &&
             partialProducts.length() == dimensions.length());
 }
 
-static inline JSObject *
-NewDenseArrayWithType(JSContext *cx, uint32_t length, HandleObject source = NullPtr())
-{
-    RootedObject buffer(cx);
-    if (source)
-        buffer = NewDenseCopiedArray(cx, length, source->getDenseArrayElements());
-    else
-        buffer = NewDenseAllocatedArray(cx, length);
-
-    if (!buffer)
-        return NULL;
-
-    if (!source)
-        buffer->ensureDenseArrayInitializedLength(cx, length, 0);
-
-    RootedTypeObject newtype(cx, GetTypeCallerInitObject(cx, JSProto_Array));
-    if (!newtype)
-        return NULL;
-    buffer->setType(newtype);
-
-    return *buffer.address();
-}
-
 // Check if obj is a parallel array, and if so, cast to pa and initialize
 // the IndexInfo accordingly.
 //
 // This function is designed to be used in conjunction with
 // GetElementFromArrayLikeObject; see below.
 static bool
 MaybeGetParallelArrayObjectAndLength(JSContext *cx, HandleObject obj,
                                      MutableHandle<ParallelArrayObject *> pa,
@@ -90,47 +67,120 @@ MaybeGetParallelArrayObjectAndLength(JSC
 // and iv is initialized according to the dimensions of pa. In this case,
 // we get the element using the ParallelArrayObject.
 //
 // Otherwise we do what is done in GetElement in jsarray.cpp.
 static bool
 GetElementFromArrayLikeObject(JSContext *cx, HandleObject obj, HandleParallelArrayObject pa,
                               IndexInfo &iv, uint32_t i, MutableHandleValue vp)
 {
-    // Are we indexing a parallel array object?
-    if (pa) {
-        // If the array is one dimensional, we can skip using the IndexInfo.
-        if (pa->isOneDimensional() && pa->getElementFromOnlyDimension(cx, i, vp))
-            return true;
+    // Fast path getting an element from parallel and dense arrays. For dense
+    // arrays, we only do this if the prototype doesn't have indexed
+    // properties. In this case holes = undefined.
+    if (pa && pa->getParallelArrayElement(cx, i, &iv, vp))
+        return true;
 
-        JS_ASSERT(iv.isInitialized());
-        JS_ASSERT(iv.indices.length() == 1);
-        iv.indices[0] = i;
-        if (pa->getParallelArrayElement(cx, iv, vp))
-            return true;
-    }
-
-    if (obj->isDenseArray() && i < obj->getDenseArrayInitializedLength()) {
+    if (obj->isDenseArray() && i < obj->getDenseArrayInitializedLength() &&
+        !js_PrototypeHasIndexedProperties(cx, obj))
+    {
         vp.set(obj->getDenseArrayElement(i));
-        if (!vp.isMagic(JS_ARRAY_HOLE))
-            return true;
+        if (vp.isMagic(JS_ARRAY_HOLE))
+            vp.setUndefined();
+        return true;
     }
 
     if (obj->isArguments()) {
         if (obj->asArguments().maybeGetElement(static_cast<uint32_t>(i), vp))
             return true;
     }
 
-    bool present;
-    if (!JSObject::getElementIfPresent(cx, obj, obj, i, vp, &present))
+    // Slow path everything else: objects with indexed properties on the
+    // prototype, non-parallel and dense arrays.
+    return JSObject::getElement(cx, obj, obj, i, vp);
+}
+
+static inline bool
+SetArrayNewType(JSContext *cx, HandleObject obj)
+{
+    RootedTypeObject newtype(cx, GetTypeCallerInitObject(cx, JSProto_Array));
+    if (!newtype)
         return false;
-    if (!present)
-        vp.setUndefined();
+    obj->setType(newtype);
+    return true;
+}
+
+static JSObject *
+NewDenseCopiedArrayWithType(JSContext *cx, uint32_t length, HandleObject source)
+{
+    JS_ASSERT(source);
+
+    RootedObject buffer(cx, NewDenseAllocatedArray(cx, length));
+    if (!buffer)
+        return NULL;
+    JS_ASSERT(buffer->getDenseArrayCapacity() >= length);
+    buffer->setDenseArrayInitializedLength(length);
+
+    uint32_t srclen;
+    uint32_t copyUpTo;
+
+    // Optimize for the common case: if we have a dense array source, copy
+    // whatever we can, truncating to length, and filling the rest with
+    // undefineds. Holes are converted to undefineds eagerly.
+    if (source->isDenseArray() && !js_PrototypeHasIndexedProperties(cx, source)) {
+        const Value *srcvp = source->getDenseArrayElements();
+
+        srclen = source->getDenseArrayInitializedLength();
+        copyUpTo = Min(length, srclen);
+
+        // Convert any existing holes into undefined.
+        Value elem;
+        for (uint32_t i = 0; i < copyUpTo; i++) {
+            elem = srcvp[i].isMagic(JS_ARRAY_HOLE) ? UndefinedValue() : srcvp[i];
+            buffer->initDenseArrayElementWithType(cx, i, elem);
+        }
+    } else {
+        IndexInfo siv(cx);
+        RootedParallelArrayObject sourcePA(cx);
 
-    return true;
+        if (!MaybeGetParallelArrayObjectAndLength(cx, source, &sourcePA, &siv, &srclen))
+            return NULL;
+        copyUpTo = Min(length, srclen);
+
+        // Copy elements pointwise.
+        RootedValue elem(cx);
+        for (uint32_t i = 0; i < copyUpTo; i++) {
+            if (!GetElementFromArrayLikeObject(cx, source, sourcePA, siv, i, &elem))
+                return NULL;
+            buffer->initDenseArrayElementWithType(cx, i, elem);
+        }
+    }
+
+    // Fill the rest with undefineds.
+    for (uint32_t i = copyUpTo; i < length; i++)
+        buffer->initDenseArrayElementWithType(cx, i, UndefinedValue());
+
+    if (!SetArrayNewType(cx, buffer))
+        return NULL;
+
+    return *buffer.address();
+}
+
+static inline JSObject *
+NewDenseArrayWithType(JSContext *cx, uint32_t length)
+{
+    RootedObject buffer(cx, NewDenseAllocatedArray(cx, length));
+    if (!buffer)
+        return NULL;
+
+    buffer->ensureDenseArrayInitializedLength(cx, length, 0);
+
+    if (!SetArrayNewType(cx, buffer))
+        return NULL;
+
+    return *buffer.address();
 }
 
 // Copy an array like object obj into an IndexVector, indices, using
 // ToUint32.
 static inline bool
 ArrayLikeToIndexVector(JSContext *cx, HandleObject obj, IndexVector &indices)
 {
     IndexInfo iv(cx);
@@ -291,24 +341,18 @@ ParallelArrayObject::SequentialMode::map
     if (!cx->stack.pushInvokeArgs(cx, 3, &args))
         return ExecutionFailed;
 
     RootedValue elem(cx);
     for (uint32_t i = 0; i < length; i++) {
         args.setCallee(ObjectValue(*elementalFun));
         args.setThis(UndefinedValue());
 
-        if (source->isOneDimensional()) {
-            if (!source->getElementFromOnlyDimension(cx, i, &elem))
-                return ExecutionFailed;
-        } else {
-            iv.indices[0] = i;
-            if (!source->getParallelArrayElement(cx, iv, &elem))
-                return ExecutionFailed;
-        }
+        if (!source->getParallelArrayElement(cx, i, &iv, &elem))
+            return ExecutionFailed;
 
         // The arguments are in eic(h) order.
         args[0] = elem;
         args[1].setNumber(i);
         args[2].setObject(*source);
 
         if (!Invoke(cx, args))
             return ExecutionFailed;
@@ -333,47 +377,36 @@ ParallelArrayObject::SequentialMode::red
     // The accumulator: the objet petit a.
     //
     // "A VM's accumulator register is Objet petit a: the unattainable object
     // of desire that sets in motion the symbolic movement of interpretation."
     //     -- PLT Žižek
     RootedValue acc(cx);
     IndexInfo iv(cx);
 
-    if (source->isOneDimensional()) {
-        if (!source->getElementFromOnlyDimension(cx, 0, &acc))
-            return ExecutionFailed;
-    } else {
-        if (!iv.initialize(cx, source, 1))
-            return ExecutionFailed;
-        iv.indices[0] = 0;
-        if (!source->getParallelArrayElement(cx, iv, &acc))
-            return ExecutionFailed;
-    }
+    if (!source->isOneDimensional() && !iv.initialize(cx, source, 1))
+        return ExecutionFailed;
+
+    if (!source->getParallelArrayElement(cx, 0, &iv, &acc))
+        return ExecutionFailed;
 
     if (buffer)
         buffer->setDenseArrayElementWithType(cx, 0, acc);
 
     InvokeArgsGuard args;
     if (!cx->stack.pushInvokeArgs(cx, 2, &args))
         return ExecutionFailed;
 
     RootedValue elem(cx);
     for (uint32_t i = 1; i < length; i++) {
         args.setCallee(ObjectValue(*elementalFun));
         args.setThis(UndefinedValue());
 
-        if (source->isOneDimensional()) {
-            if (!source->getElementFromOnlyDimension(cx, i, &elem))
-                return ExecutionFailed;
-        } else {
-            iv.indices[0] = i;
-            if (!source->getParallelArrayElement(cx, iv, &elem))
-                return ExecutionFailed;
-        }
+        if (!source->getParallelArrayElement(cx, i, &iv, &elem))
+            return ExecutionFailed;
 
         // Set the two arguments to the elemental function.
         args[0] = acc;
         args[1] = elem;
 
         if (!Invoke(cx, args))
             return ExecutionFailed;
 
@@ -426,24 +459,18 @@ ParallelArrayObject::SequentialMode::sca
         }
 
         if (targetIndex >= length) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                                  JSMSG_PAR_ARRAY_SCATTER_BOUNDS);
             return ExecutionFailed;
         }
 
-        if (source->isOneDimensional()) {
-            if (!source->getElementFromOnlyDimension(cx, i, &elem))
-                return ExecutionFailed;
-        } else {
-            iv.indices[0] = i;
-            if (!source->getParallelArrayElement(cx, iv, &elem))
-                return ExecutionFailed;
-        }
+        if (!source->getParallelArrayElement(cx, i, &iv, &elem))
+            return ExecutionFailed;
 
         targetElem = buffer->getDenseArrayElement(targetIndex);
 
         // We initialized the dense buffer with holes. If the target element
         // in the source array is not a hole, that means we have set it
         // already and we have a conflict.
         if (!targetElem.isMagic(JS_ARRAY_HOLE)) {
             if (conflictFun) {
@@ -465,17 +492,17 @@ ParallelArrayObject::SequentialMode::sca
                                      JSMSG_PAR_ARRAY_SCATTER_CONFLICT);
                 return ExecutionFailed;
             }
         }
 
         buffer->setDenseArrayElementWithType(cx, targetIndex, elem);
     }
 
-    // Fill holes.
+    // Fill holes with the default value.
     for (uint32_t i = 0; i < length; i++) {
         if (buffer->getDenseArrayElement(i).isMagic(JS_ARRAY_HOLE))
             buffer->setDenseArrayElementWithType(cx, i, defaultValue);
     }
 
     return ExecutionSucceeded;
 }
 
@@ -505,24 +532,18 @@ ParallelArrayObject::SequentialMode::fil
     for (uint32_t i = 0, pos = 0; i < filtersLength; i++) {
         if (!GetElementFromArrayLikeObject(cx, filters, filtersPA, fiv, i, &felem))
             return ExecutionFailed;
 
         // Skip if the filter element isn't truthy.
         if (!ToBoolean(felem))
             continue;
 
-        if (source->isOneDimensional()) {
-            if (!source->getElementFromOnlyDimension(cx, i, &elem))
-                return ExecutionFailed;
-        } else {
-            iv.indices[0] = i;
-            if (!source->getParallelArrayElement(cx, iv, &elem))
-                return ExecutionFailed;
-        }
+        if (!source->getParallelArrayElement(cx, i, &iv, &elem))
+            return ExecutionFailed;
 
         // Set the element on the buffer. If we couldn't stay dense, fail.
         JSObject::EnsureDenseResult result = JSObject::ED_SPARSE;
         result = buffer->ensureDenseArrayElements(cx, pos, 1);
         if (result != JSObject::ED_OK)
             return ExecutionFailed;
         if (i >= buffer->getArrayLength())
             buffer->setDenseArrayLength(pos + 1);
@@ -796,17 +817,17 @@ Class ParallelArrayObject::class_ = {
         getSpecialAttributes,
         setGenericAttributes,
         setPropertyAttributes,
         setElementAttributes,
         setSpecialAttributes,
         deleteProperty,
         deleteElement,
         deleteSpecial,
-        enumerate,
+        NULL,                // enumerate
         NULL,                // typeof
         NULL,                // thisObject
     }
 };
 
 JSObject *
 ParallelArrayObject::initClass(JSContext *cx, JSObject *obj)
 {
@@ -850,32 +871,16 @@ ParallelArrayObject::initClass(JSContext
     {
         return NULL;
     }
 
     return proto;
 }
 
 bool
-ParallelArrayObject::getElementFromOnlyDimension(JSContext *cx, uint32_t index, MutableHandleValue vp)
-{
-    JS_ASSERT(isOneDimensional());
-
-    uint32_t base = bufferOffset();
-    uint32_t end = base + outermostDimension();
-
-    if (base + index >= end)
-        vp.setUndefined();
-    else
-        vp.set(buffer()->getDenseArrayElement(base + index));
-
-    return true;
-}
-
-bool
 ParallelArrayObject::getParallelArrayElement(JSContext *cx, IndexInfo &iv, MutableHandleValue vp)
 {
     JS_ASSERT(iv.isInitialized());
 
     // How many indices we have determine what dimension we are indexing. For
     // example, if we have 2 indices [n,m], we are indexing something on the
     // 2nd dimension.
     uint32_t d = iv.indices.length();
@@ -909,21 +914,51 @@ ParallelArrayObject::getParallelArrayEle
 
     RootedObject buf(cx, buffer());
     IndexVector newDims(cx);
     return (newDims.append(iv.dimensions.begin() + d, iv.dimensions.end()) &&
             create(cx, buf, offset, newDims, vp));
 }
 
 bool
+ParallelArrayObject::getParallelArrayElement(JSContext *cx, uint32_t index, IndexInfo *maybeIV,
+                                             MutableHandleValue vp)
+{
+    // If we are one dimensional, we don't need to use IndexInfo.
+    if (isOneDimensional()) {
+        uint32_t base = bufferOffset();
+        uint32_t end = base + outermostDimension();
+
+        if (base + index >= end)
+            vp.setUndefined();
+        else
+            vp.set(buffer()->getDenseArrayElement(base + index));
+
+        return true;
+    }
+
+    // If we're higher dimensional, an initialized IndexInfo must be provided.
+    JS_ASSERT(maybeIV);
+    JS_ASSERT(maybeIV->isInitialized());
+    JS_ASSERT(maybeIV->indices.length() == 1);
+
+    maybeIV->indices[0] = index;
+    return getParallelArrayElement(cx, *maybeIV, vp);
+}
+
+bool
 ParallelArrayObject::getParallelArrayElement(JSContext *cx, uint32_t index, MutableHandleValue vp)
 {
-    IndexInfo iv(cx);
+    if (isOneDimensional())
+        return getParallelArrayElement(cx, index, NULL, vp);
+
     // Manually initialize to avoid re-rooting 'this', as this code could be
-    // called from inside a loop.
+    // called from inside a loop, though you really should hoist out the
+    // IndexInfo if that's the case.
+    IndexInfo iv(cx);
     if (!getDimensions(cx, iv.dimensions) || !iv.initialize(1))
         return false;
     iv.indices[0] = index;
     return getParallelArrayElement(cx, iv, vp);
 }
 
 bool
 ParallelArrayObject::create(JSContext *cx, MutableHandleValue vp)
@@ -1008,36 +1043,19 @@ ParallelArrayObject::construct(JSContext
 
         // When using an array value we can only make one dimensional arrays.
         IndexVector dims(cx);
         uint32_t length;
         if (!dims.resize(1) || !GetLengthProperty(cx, source, &length))
             return false;
         dims[0] = length;
 
-        RootedObject buffer(cx);
-
-        // If the source is already a dense array, just copy it over
-        // wholesale. Else copy it pointwise.
-        if (source->isDenseArray()) {
-            buffer = NewDenseArrayWithType(cx, length, source);
-            if (!buffer)
-                return false;
-        } else {
-            buffer = NewDenseArrayWithType(cx, length);
-            if (!buffer)
-                return false;
-
-            RootedValue elem(cx);
-            for (uint32_t i = 0; i < length; i++) {
-                if (!JSObject::getElement(cx, source, source, i, &elem))
-                    return false;
-                buffer->setDenseArrayElementWithType(cx, i, elem);
-            }
-        }
+        RootedObject buffer(cx, NewDenseCopiedArrayWithType(cx, length, source));
+        if (!buffer)
+            return false;
 
         return create(cx, buffer, 0, dims, args.rval());
     }
 
     // Second case: initialize using a length/dimensions vector and kernel.
     //
     // If the length is an integer, we build a 1-dimensional parallel
     // array using the kernel.
@@ -1409,47 +1427,16 @@ ParallelArrayObject::get(JSContext *cx, 
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_PAR_ARRAY_BAD_ARG,
                              ".prototype.get");
         return false;
     }
 
     RootedParallelArrayObject obj(cx, as(&args.thisv().toObject()));
     RootedObject indicesObj(cx, &(args[0].toObject()));
 
-    if (obj->isOneDimensional()) {
-        uint32_t length;
-        if (is(indicesObj))
-            length = as(indicesObj)->outermostDimension();
-        else if (!GetLengthProperty(cx, indicesObj, &length))
-            return false;
-
-        // If we're one dimensional, the index vector must also be one
-        // dimensional.
-        if (length != 1) {
-            JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_PAR_ARRAY_BAD_ARG,
-                                 ".prototype.get");
-            return false;
-        }
-
-        RootedValue elem(cx);
-        uint32_t index;
-        if (is(indicesObj)) {
-            if (!as(indicesObj)->getParallelArrayElement(cx, 0, &elem))
-                return false;
-        } else {
-            if (!JSObject::getElement(cx, indicesObj, indicesObj, 0, &elem))
-                return false;
-        }
-
-        if (!ToUint32(cx, elem, &index))
-            return false;
-
-        return obj->getElementFromOnlyDimension(cx, index, args.rval());
-    }
-
     IndexInfo iv(cx);
     if (!iv.initialize(cx, obj, 0))
         return false;
     if (!ArrayLikeToIndexVector(cx, indicesObj, iv.indices))
         return false;
 
     // Throw if the shape of the index vector is wrong.
     if (iv.indices.length() == 0 || iv.indices.length() > iv.dimensions.length()) {
@@ -1527,20 +1514,23 @@ ParallelArrayObject::toStringBufferImpl(
     RootedValue localeElem(cx);
     RootedId id(cx);
 
     const Value *start = buffer->getDenseArrayElements() + offset;
     const Value *end = start + length;
     const Value *elem;
 
     for (elem = start; elem < end; elem++) {
+        // All holes in parallel arrays are eagerly filled with undefined.
+        JS_ASSERT(!elem->isMagic(JS_ARRAY_HOLE));
+
         if (!JS_CHECK_OPERATION_LIMIT(cx))
             return false;
 
-        if (!elem->isMagic(JS_ARRAY_HOLE) && !elem->isNullOrUndefined()) {
+        if (!elem->isNullOrUndefined()) {
             if (useLocale) {
                 tmp = *elem;
                 JSObject *robj = ToObject(cx, tmp);
                 if (!robj)
                     return false;
 
                 id = NameToId(cx->runtime->atomState.toLocaleStringAtom);
                 if (!robj->callMethod(cx, id, 0, NULL, &localeElem) ||
@@ -1611,18 +1601,21 @@ ParallelArrayObject::mark(JSTracer *trc,
     gc::MarkSlot(trc, &obj->getSlotRef(SLOT_DIMENSIONS), "parallelarray.shape");
     gc::MarkSlot(trc, &obj->getSlotRef(SLOT_BUFFER), "parallelarray.buffer");
 }
 
 JSBool
 ParallelArrayObject::lookupGeneric(JSContext *cx, HandleObject obj, HandleId id,
                                    MutableHandleObject objp, MutableHandleShape propp)
 {
-    if (JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom) ||
-        as(obj)->inOutermostDimensionRange(cx, id)) {
+    uint32_t i;
+    if (js_IdIsIndex(id, &i))
+        return lookupElement(cx, obj, i, objp, propp);
+
+    if (JSID_IS_ATOM(id, cx->runtime->atomState.lengthAtom)) {
         MarkNonNativePropertyFound(obj, propp);
         objp.set(obj);
         return true;
     }
 
     RootedObject proto(cx, obj->getProto());
     if (proto)
         return JSObject::lookupGeneric(cx, proto, id, objp, propp);
@@ -1639,26 +1632,23 @@ ParallelArrayObject::lookupProperty(JSCo
     RootedId id(cx, NameToId(name));
     return lookupGeneric(cx, obj, id, objp, propp);
 }
 
 JSBool
 ParallelArrayObject::lookupElement(JSContext *cx, HandleObject obj, uint32_t index,
                                    MutableHandleObject objp, MutableHandleShape propp)
 {
-    if (as(obj)->inOutermostDimensionRange(index)) {
+    // No prototype walking for elements.
+    if (index < as(obj)->outermostDimension()) {
         MarkNonNativePropertyFound(obj, propp);
         objp.set(obj);
         return true;
     }
 
-    RootedObject proto(cx, obj->getProto());
-    if (proto)
-        return JSObject::lookupElement(cx, proto, index, objp, propp);
-
     objp.set(NULL);
     propp.set(NULL);
     return true;
 }
 
 JSBool
 ParallelArrayObject::lookupSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid,
                                    MutableHandleObject objp, MutableHandleShape propp)
@@ -1739,29 +1729,19 @@ ParallelArrayObject::getProperty(JSConte
     vp.setUndefined();
     return true;
 }
 
 JSBool
 ParallelArrayObject::getElement(JSContext *cx, HandleObject obj, HandleObject receiver,
                                 uint32_t index, MutableHandleValue vp)
 {
-    RootedParallelArrayObject source(cx, as(obj));
-    if (source->inOutermostDimensionRange(index)) {
-        if (source->isOneDimensional())
-            return source->getElementFromOnlyDimension(cx, index, vp);
-        return source->getParallelArrayElement(cx, index, vp);
-    }
-
-    RootedObject proto(cx, obj->getProto());
-    if (proto)
-        return JSObject::getElement(cx, proto, receiver, index, vp);
-
-    vp.setUndefined();
-    return true;
+    // Unlike normal arrays, [] for ParallelArray does not walk the prototype
+    // chain and just returns undefined.
+    return as(obj)->getParallelArrayElement(cx, index, vp);
 }
 
 JSBool
 ParallelArrayObject::getSpecial(JSContext *cx, HandleObject obj, HandleObject receiver,
                                 HandleSpecialId sid, MutableHandleValue vp)
 {
     if (!obj->getProto()) {
         vp.setUndefined();
@@ -1900,46 +1880,47 @@ ParallelArrayObject::deleteElement(JSCon
 JSBool
 ParallelArrayObject::deleteSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid,
                                    MutableHandleValue rval, JSBool strict)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_PAR_ARRAY_IMMUTABLE);
     return false;
 }
 
-JSBool
-ParallelArrayObject::enumerate(JSContext *cx, HandleObject obj, JSIterateOp enum_op,
-                               Value *statep, jsid *idp)
+bool
+ParallelArrayObject::enumerate(JSContext *cx, HandleObject obj, unsigned flags,
+                               AutoIdVector *props)
 {
-    JS_ASSERT(is(obj));
     RootedParallelArrayObject source(cx, as(obj));
 
-    uint32_t index;
-    switch (enum_op) {
-      case JSENUMERATE_INIT_ALL:
-      case JSENUMERATE_INIT:
-        statep->setInt32(0);
-        if (idp)
-            *idp = ::INT_TO_JSID(source->outermostDimension());
-        break;
+    if (flags & JSITER_HIDDEN && !props->append(NameToId(cx->runtime->atomState.lengthAtom)))
+        return false;
+
+    // ParallelArray objects have no holes.
+    if (source->outermostDimension() > 0) {
+        for (uint32_t i = 0; i < source->outermostDimension(); i++)
+            props->append(INT_TO_JSID(i));
+    }
+
+    if (flags & JSITER_OWNONLY)
+        return true;
 
-      case JSENUMERATE_NEXT:
-        index = static_cast<uint32_t>(statep->toInt32());
-        if (index < source->outermostDimension()) {
-            *idp = ::INT_TO_JSID(index);
-            statep->setInt32(index + 1);
-        } else {
-            JS_ASSERT(index == source->outermostDimension());
-            statep->setNull();
+    RootedObject proto(cx, obj->getProto());
+    if (proto) {
+        AutoIdVector protoProps(cx);
+        if (!GetPropertyNames(cx, proto, flags, &protoProps))
+            return false;
+
+        // ParallelArray objects do not inherit any indexed properties on the
+        // prototype chain.
+        uint32_t dummy;
+        for (uint32_t i = 0; i < protoProps.length(); i++) {
+            if (!js_IdIsIndex(protoProps[i], &dummy) && !props->append(protoProps[i]))
+                return false;
         }
-        break;
-
-      case JSENUMERATE_DESTROY:
-        statep->setNull();
-        break;
     }
 
     return true;
 }
 
 JSObject *
 js_InitParallelArrayClass(JSContext *cx, JSObject *obj)
 {
--- a/js/src/builtin/ParallelArray.h
+++ b/js/src/builtin/ParallelArray.h
@@ -29,16 +29,22 @@ typedef Handle<ParallelArrayObject *> Ha
 // decide both representation and what is considered parallelizable.
 //
 // Currently ParallelArray objects are backed by dense arrays for ease of
 // GC. For the higher-dimensional case, data is stored in a packed, row-major
 // order representation in the backing dense array. See notes below about
 // IndexInfo in how to convert between scalar offsets into the backing array
 // and a vector of indices.
 //
+// ParallelArray objects are always dense. That is, all holes are eagerly
+// filled in with undefined instead of being JS_ARRAY_HOLE. This results in a
+// break from the behavior of normal JavaScript arrays: if a ParallelArray p
+// is missing an indexed property i, p[i] is _always_ undefined and will never
+// walk up the prototype chain in search of i.
+//
 // Except for the comprehension form, all operations (e.g. map, filter,
 // reduce) operate on the outermost dimension only. That is, those operations
 // only operate on the "rows" of the array. "Element" is used in context of
 // ParallelArray objects to mean any indexable value of a ParallelArray
 // object. For a one dimensional array, elements are always scalar values. For
 // a higher dimensional array, elements could either be scalar values
 // (i.e. leaves) or ParallelArray objects of lesser dimensions
 // (i.e. subarrays).
@@ -133,33 +139,44 @@ class ParallelArrayObject : public JSObj
     static inline bool is(JSObject *obj);
     static inline ParallelArrayObject *as(JSObject *obj);
 
     inline JSObject *dimensionArray();
     inline JSObject *buffer();
     inline uint32_t bufferOffset();
     inline uint32_t outermostDimension();
     inline bool isOneDimensional();
-    inline bool inOutermostDimensionRange(uint32_t index);
-    inline bool inOutermostDimensionRange(JSContext *cx, HandleId id);
     inline bool getDimensions(JSContext *cx, IndexVector &dims);
 
-    // Specialized for one dimensional arrays. Use this if possible.
-    bool getElementFromOnlyDimension(JSContext *cx, uint32_t index, MutableHandleValue vp);
-
-    // The general case; works for arrays of any dimensionality.
+    // The general case; requires an initialized IndexInfo.
     bool getParallelArrayElement(JSContext *cx, IndexInfo &iv, MutableHandleValue vp);
 
-    // Convenience function for getting an element from the outermost
-    // dimension in the general case. This creates a temporary IndexInfo of
-    // length 1 with the 1st index set to the index parameter.
+    // Get the element at index in the outermost dimension. This is a
+    // convenience function designed to require an IndexInfo only if it is
+    // actually needed.
+    //
+    // If the parallel array is multidimensional, then the caller must provide
+    // an IndexInfo initialized to length 1, which is used to access the
+    // array. This argument is modified. If the parallel array is
+    // one-dimensional, then maybeIV may be null.
+    bool getParallelArrayElement(JSContext *cx, uint32_t index, IndexInfo *maybeIV,
+                                 MutableHandleValue vp);
+
+    // Get the element at index in the outermost dimension. This is a
+    // convenience function that initializes a temporary
+    // IndexInfo if the parallel array is multidimensional.
     bool getParallelArrayElement(JSContext *cx, uint32_t index, MutableHandleValue vp);
 
     bool toStringBuffer(JSContext *cx, bool useLocale, StringBuffer &sb);
 
+    // Note that this is not an object op but called directly from the
+    // iteration code, as we enumerate prototypes ourselves.
+    static bool enumerate(JSContext *cx, HandleObject obj, unsigned flags,
+                          AutoIdVector *props);
+
   private:
     enum {
         // The ParallelArray API refers to dimensions as "shape", but to avoid
         // confusion with the internal engine notion of a shape we call it
         // "dimensions" here.
         SLOT_DIMENSIONS = 0,
 
         // Underlying dense array.
@@ -382,18 +399,16 @@ class ParallelArrayObject : public JSObj
     static JSBool deleteGeneric(JSContext *cx, HandleObject obj, HandleId id,
                                 MutableHandleValue rval, JSBool strict);
     static JSBool deleteProperty(JSContext *cx, HandleObject obj, HandlePropertyName name,
                                  MutableHandleValue rval, JSBool strict);
     static JSBool deleteElement(JSContext *cx, HandleObject obj, uint32_t index,
                                 MutableHandleValue rval, JSBool strict);
     static JSBool deleteSpecial(JSContext *cx, HandleObject obj, HandleSpecialId sid,
                                 MutableHandleValue rval, JSBool strict);
-    static JSBool enumerate(JSContext *cx, HandleObject obj, JSIterateOp enum_op,
-                            Value *statep, jsid *idp);
 };
 
 } // namespace js
 
 extern JSObject *
 js_InitParallelArrayClass(JSContext *cx, JSObject *obj);
 
 #endif // ParallelArray_h__
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/parallelarray/constructor-5.js
@@ -0,0 +1,13 @@
+function testCopyBigArray() {
+  // Don't crash
+  var a = new Array(1000 * 1000);
+  var p = new ParallelArray(a);
+  // Holes!
+  var a = new Array(10000);
+  for (var cnt = 0; cnt < a.length; cnt+=2) {
+    a[cnt] = cnt;
+    var p = new ParallelArray(a);
+  }
+}
+
+testCopyBigArray();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/parallelarray/holes-1.js
@@ -0,0 +1,30 @@
+function testHoles() {
+  function f1(a) { return a * 42; }
+  function f2(a,b) { return a * b; }
+  // Don't crash when getting holes out.
+  //
+  // We do the multiplications below instead of asserting against undefined to
+  // force the VM to call a conversion function, which will crash if the value
+  // we got out is a JS_ARRAY_HOLE.
+  var p = new ParallelArray([,1]);
+  assertEq(p[0] * 42, NaN);
+  var m = p.map(f1);
+  assertEq(m[0], NaN);
+  assertEq(m[1], 42);
+  var r = p.reduce(f2);
+  assertEq(r, NaN);
+  var s = p.scan(f2);
+  assertEq(s[0] * 42, NaN);
+  assertEq(s[1], NaN);
+  var k = p.scatter([1,0]);
+  assertEq(k[0], 1);
+  assertEq(k[1] * 42, NaN);
+  var l = p.filter([1,0]);
+  assertEq(l[0] * 42, NaN);
+  var p2 = p.partition(1);
+  assertEq(p2[0][0] * 42, NaN);
+  var g = p.get([0]);
+  assertEq(g * 42, NaN);
+}
+
+testHoles();
rename from js/src/jit-test/tests/parallelarray/element-4.js
rename to js/src/jit-test/tests/parallelarray/holes-2.js
--- a/js/src/jit-test/tests/parallelarray/element-4.js
+++ b/js/src/jit-test/tests/parallelarray/holes-2.js
@@ -1,8 +1,23 @@
 function testElement() {
-  // Test crazy prototype walking
+  // No crazy prototype walking for indexed properties
   ParallelArray.prototype[42] = "foo";
+  ParallelArray.prototype.bar = "bar";
   var p = new ParallelArray([1,2,3,4]);
-  assertEq(p[42], "foo");
+  assertEq(p[42], undefined);
+  assertEq(42 in p, false);
+  assertEq("bar" in p, true);
+  // Don't inherit any indexed properties
+  for (var i in p)
+    assertEq(i !== 42, true);
+  for (var i in p) {
+    if (i % 1 !== 0)
+      assertEq(i, "bar");
+  }
+  ParallelArray.prototype[0] = "foo";
+  var p2 = new ParallelArray([,2]);
+  // ParallelArrays have no holes, so 0 must be 'in' p2
+  assertEq(0 in p2, true);
+  assertEq(p2[0], undefined);
 }
 
 testElement();
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -30,24 +30,27 @@
 #include "jsproxy.h"
 #include "jsscope.h"
 #include "jsscript.h"
 
 #if JS_HAS_XML_SUPPORT
 #include "jsxml.h"
 #endif
 
+#include "builtin/ParallelArray.h"
+
 #include "ds/Sort.h"
 #include "frontend/TokenStream.h"
 #include "gc/Marking.h"
 #include "vm/GlobalObject.h"
 
 #include "jsinferinlines.h"
 #include "jsobjinlines.h"
 
+#include "builtin/ParallelArray-inl.h"
 #include "builtin/Iterator-inl.h"
 #include "vm/Stack-inl.h"
 #include "vm/String-inl.h"
 
 using namespace mozilla;
 using namespace js;
 using namespace js::gc;
 
@@ -221,16 +224,24 @@ Snapshot(JSContext *cx, RawObject obj_, 
             !(clasp->flags & JSCLASS_NEW_ENUMERATE)) {
             if (!clasp->enumerate(cx, pobj))
                 return false;
             if (!EnumerateNativeProperties(cx, obj, pobj, flags, ht, props))
                 return false;
         } else if (pobj->isDenseArray()) {
             if (!EnumerateDenseArrayProperties(cx, obj, pobj, flags, ht, props))
                 return false;
+        } else if (ParallelArrayObject::is(pobj)) {
+            if (!ParallelArrayObject::enumerate(cx, pobj, flags, props))
+                return false;
+            /*
+             * ParallelArray objects enumerate the prototype on their own, so
+             * we are done here.
+             */
+            break;
         } else {
             if (pobj->isProxy()) {
                 AutoIdVector proxyProps(cx);
                 if (flags & JSITER_OWNONLY) {
                     if (flags & JSITER_HIDDEN) {
                         if (!Proxy::getOwnPropertyNames(cx, pobj, proxyProps))
                             return false;
                     } else {