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 105786 91d39d72ac86b08a20e8039fd90841be17a2adf5
parent 105785 5a03f87a931b88430dc60e12fb74d10304c51833
child 105787 c06b09e067d7e50f36ac41905b3fa52d2c8915c2
push id55
push usershu@rfrn.org
push dateThu, 30 Aug 2012 01:33:09 +0000
reviewersdmandelin
bugs784345
milestone18.0a1
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 {