Bug 903193 - Part 6: Add ParallelExecution path to ArraySetLength. (r=bhackett)
authorShu-yu Guo <shu@rfrn.org>
Tue, 08 Oct 2013 15:14:04 -0700
changeset 150130 f7e302573f54086f28dacf9aa89fe25a14b22a3a
parent 150129 4da4d90efca25ec7218e17af72f08a34ce0a8ab5
child 150131 cdbc4812c4ee64f4749d4f96bc09fae7ed381479
push id25423
push usercbook@mozilla.com
push dateWed, 09 Oct 2013 08:10:49 +0000
treeherdermozilla-central@2a8a173a0ae1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbhackett
bugs903193
milestone27.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 903193 - Part 6: Add ParallelExecution path to ArraySetLength. (r=bhackett)
js/src/jsarray.cpp
js/src/jsarray.h
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinferinlines.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/vm/ObjectImpl.h
js/src/vm/Shape.cpp
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -394,102 +394,132 @@ array_length_setter(JSContext *cx, Handl
     if (!obj->is<ArrayObject>()) {
         return JSObject::defineProperty(cx, obj, cx->names().length, vp,
                                         nullptr, nullptr, JSPROP_ENUMERATE);
     }
 
     Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
     MOZ_ASSERT(arr->lengthIsWritable(),
                "setter shouldn't be called if property is non-writable");
-    return ArraySetLength(cx, arr, id, JSPROP_PERMANENT, vp, strict);
+    return ArraySetLength<SequentialExecution>(cx, arr, id, JSPROP_PERMANENT, vp, strict);
 }
 
 struct ReverseIndexComparator
 {
     bool operator()(const uint32_t& a, const uint32_t& b, bool *lessOrEqualp) {
         MOZ_ASSERT(a != b, "how'd we get duplicate indexes?");
         *lessOrEqualp = b <= a;
         return true;
     }
 };
 
+template <ExecutionMode mode>
 bool
-js::CanonicalizeArrayLengthValue(JSContext *cx, HandleValue v, uint32_t *newLen)
+js::CanonicalizeArrayLengthValue(typename ExecutionModeTraits<mode>::ContextType cx,
+                                 HandleValue v, uint32_t *newLen)
 {
-    if (!ToUint32(cx, v, newLen))
-        return false;
-
     double d;
-    if (!ToNumber(cx, v, &d))
-        return false;
+
+    if (mode == ParallelExecution) {
+        if (v.isObject())
+            return false;
+
+        if (!NonObjectToUint32(cx, v, newLen))
+            return false;
+
+        if (!NonObjectToNumber(cx, v, &d))
+            return false;
+    } else {
+        if (!ToUint32(cx->asJSContext(), v, newLen))
+            return false;
+
+        if (!ToNumber(cx->asJSContext(), v, &d))
+            return false;
+    }
+
     if (d == *newLen)
         return true;
 
     if (cx->isJSContext())
         JS_ReportErrorNumber(cx->asJSContext(), js_GetErrorMessage, nullptr,
                              JSMSG_BAD_ARRAY_LENGTH);
     return false;
 }
 
+template bool
+js::CanonicalizeArrayLengthValue<SequentialExecution>(JSContext *cx,
+                                                      HandleValue v, uint32_t *newLen);
+template bool
+js::CanonicalizeArrayLengthValue<ParallelExecution>(ForkJoinSlice *slice,
+                                                    HandleValue v, uint32_t *newLen);
+
 /* ES6 20130308 draft 8.4.2.4 ArraySetLength */
+template <ExecutionMode mode>
 bool
-js::ArraySetLength(JSContext *cx, Handle<ArrayObject*> arr, HandleId id, unsigned attrs,
-                   HandleValue value, bool setterIsStrict)
+js::ArraySetLength(typename ExecutionModeTraits<mode>::ContextType cxArg,
+                   Handle<ArrayObject*> arr, HandleId id,
+                   unsigned attrs, HandleValue value, bool setterIsStrict)
 {
-    MOZ_ASSERT(id == NameToId(cx->names().length));
+    MOZ_ASSERT(cxArg->isThreadLocal(arr));
+    MOZ_ASSERT(id == NameToId(cxArg->names().length));
 
     /* Steps 1-2 are irrelevant in our implementation. */
 
     /* Steps 3-5. */
     uint32_t newLen;
-    if (!CanonicalizeArrayLengthValue(cx, value, &newLen))
+    if (!CanonicalizeArrayLengthValue<mode>(cxArg, value, &newLen))
         return false;
 
     // Abort if we're being asked to change enumerability or configurability.
     // (The length property of arrays is non-configurable, so such attempts
     // must fail.)  This behavior is spread throughout the ArraySetLength spec
     // algorithm, but we only need check it once as our array implementation
     // is internally so different from the spec algorithm.  (ES5 and ES6 define
     // behavior by delegating to the default define-own-property algorithm --
     // OrdinaryDefineOwnProperty in ES6, the default [[DefineOwnProperty]] in
     // ES5 -- but we reimplement all the conflict-detection bits ourselves here
     // so that we can use a customized length representation.)
     if (!(attrs & JSPROP_PERMANENT) || (attrs & JSPROP_ENUMERATE)) {
         if (!setterIsStrict)
             return true;
-        return Throw(cx, id, JSMSG_CANT_REDEFINE_PROP);
+        // Bail for strict mode in parallel execution, as we need to go back
+        // to sequential mode to throw the error.
+        if (mode == ParallelExecution)
+            return false;
+        return Throw(cxArg->asJSContext(), id, JSMSG_CANT_REDEFINE_PROP);
     }
 
     /* Steps 6-7. */
     bool lengthIsWritable = arr->lengthIsWritable();
 #ifdef DEBUG
     {
-        RootedShape lengthShape(cx, arr->nativeLookup(cx, id));
+        RootedShape lengthShape(cxArg, arr->nativeLookupPure(id));
         MOZ_ASSERT(lengthShape);
         MOZ_ASSERT(lengthShape->writable() == lengthIsWritable);
     }
 #endif
 
     uint32_t oldLen = arr->length();
 
     /* Steps 8-9 for arrays with non-writable length. */
     if (!lengthIsWritable) {
         if (newLen == oldLen)
             return true;
 
-        if (!cx->isJSContext())
+        if (!cxArg->isJSContext())
             return false;
 
         if (setterIsStrict) {
-            return JS_ReportErrorFlagsAndNumber(cx->asJSContext(),
+            return JS_ReportErrorFlagsAndNumber(cxArg->asJSContext(),
                                                 JSREPORT_ERROR, js_GetErrorMessage, nullptr,
                                                 JSMSG_CANT_REDEFINE_ARRAY_LENGTH);
         }
 
-        return JSObject::reportReadOnly(cx->asJSContext(), id, JSREPORT_STRICT | JSREPORT_WARNING);
+        return JSObject::reportReadOnly(cxArg->asJSContext(), id,
+                                        JSREPORT_STRICT | JSREPORT_WARNING);
     }
 
     /* Step 8. */
     bool succeeded = true;
     do {
         // The initialized length and capacity of an array only need updating
         // when non-hole elements are added or removed, which doesn't happen
         // when array length stays the same or increases.
@@ -507,24 +537,31 @@ js::ArraySetLength(JSContext *cx, Handle
         // separate from non-indexed storage.
         if (!arr->isIndexed()) {
             uint32_t oldCapacity = arr->getDenseCapacity();
             uint32_t oldInitializedLength = arr->getDenseInitializedLength();
             MOZ_ASSERT(oldCapacity >= oldInitializedLength);
             if (oldInitializedLength > newLen)
                 arr->setDenseInitializedLength(newLen);
             if (oldCapacity > newLen)
-                arr->shrinkElements(cx, newLen);
+                arr->shrinkElements(cxArg, newLen);
 
             // We've done the work of deleting any dense elements needing
             // deletion, and there are no sparse elements.  Thus we can skip
             // straight to defining the length.
             break;
         }
 
+        // Bail from parallel execution if need to perform step 15, which is
+        // unsafe and isn't a common case.
+        if (mode == ParallelExecution)
+            return false;
+
+        JSContext *cx = cxArg->asJSContext();
+
         // Step 15.
         //
         // Attempt to delete all elements above the new length, from greatest
         // to least.  If any of these deletions fails, we're supposed to define
         // the length to one greater than the index that couldn't be deleted,
         // *with the property attributes specified*.  This might convert the
         // length to be not the value specified, yet non-writable.  (You may be
         // forgiven for thinking these are interesting semantics.)  Example:
@@ -623,27 +660,39 @@ js::ArraySetLength(JSContext *cx, Handle
     } while (false);
 
     /* Steps 12, 16. */
 
     // Yes, we totally drop a non-stub getter/setter from a defineProperty
     // API call on the floor here.  Given that getter/setter will go away in
     // the long run, with accessors replacing them both internally and at the
     // API level, just run with this.
-    RootedShape lengthShape(cx, arr->nativeLookup(cx, id));
-    if (!JSObject::changeProperty(cx, arr, lengthShape, attrs,
-                                  JSPROP_PERMANENT | JSPROP_READONLY | JSPROP_SHARED,
-                                  array_length_getter, array_length_setter))
+    RootedShape lengthShape(cxArg, mode == ParallelExecution
+                            ? arr->nativeLookupPure(id)
+                            : arr->nativeLookup(cxArg->asJSContext(), id));
+    if (!JSObject::changeProperty<mode>(cxArg, arr, lengthShape, attrs,
+                                        JSPROP_PERMANENT | JSPROP_READONLY | JSPROP_SHARED,
+                                        array_length_getter, array_length_setter))
     {
         return false;
     }
 
-    RootedValue v(cx, NumberValue(newLen));
-    AddTypePropertyId(cx, arr, id, v);
-    ArrayObject::setLength(cx, arr, newLen);
+    RootedValue v(cxArg, NumberValue(newLen));
+    if (mode == ParallelExecution) {
+        // Adding the property type or overflowing int32 requires changing TI
+        // state.
+        if (!HasTypePropertyId(arr, id, v) || newLen > INT32_MAX)
+            return false;
+        arr->setLengthInt32(newLen);
+    } else {
+        JSContext *cx = cxArg->asJSContext();
+        AddTypePropertyId(cx, arr, id, v);
+        ArrayObject::setLength(cx, arr, newLen);
+    }
+
 
     // All operations past here until the |!succeeded| code must be infallible,
     // so that all element fields remain properly synchronized.
 
     // Trim the initialized length, if needed, to preserve the <= length
     // invariant.  (Capacity was already reduced during element deletion, if
     // necessary.)
     ObjectElements *header = arr->getElementsHeader();
@@ -654,31 +703,43 @@ js::ArraySetLength(JSContext *cx, Handle
 
         // When an array's length becomes non-writable, writes to indexes
         // greater than or equal to the length don't change the array.  We
         // handle this with a check for non-writable length in most places.
         // But in JIT code every check counts -- so we piggyback the check on
         // the already-required range check for |index < capacity| by making
         // capacity of arrays with non-writable length never exceed the length.
         if (arr->getDenseCapacity() > newLen) {
-            arr->shrinkElements(cx, newLen);
+            arr->shrinkElements(cxArg, newLen);
             arr->getElementsHeader()->capacity = newLen;
         }
     }
 
     if (setterIsStrict && !succeeded) {
+        // We can't have arrived here under ParallelExecution, as we have
+        // returned from the function before step 15 above.
+        JSContext *cx = cxArg->asJSContext();
         RootedId elementId(cx);
         if (!IndexToId(cx, newLen - 1, &elementId))
             return false;
         return arr->reportNotConfigurable(cx, elementId);
     }
 
     return true;
 }
 
+template bool
+js::ArraySetLength<SequentialExecution>(JSContext *cx, Handle<ArrayObject*> arr,
+                                        HandleId id, unsigned attrs, HandleValue value,
+                                        bool setterIsStrict);
+template bool
+js::ArraySetLength<ParallelExecution>(ForkJoinSlice *slice, Handle<ArrayObject*> arr,
+                                      HandleId id, unsigned attrs, HandleValue value,
+                                      bool setterIsStrict);
+
 bool
 js::WouldDefinePastNonwritableLength(ThreadSafeContext *cx,
                                      HandleObject obj, uint32_t index, bool strict,
                                      bool *definesPast)
 {
     if (!obj->is<ArrayObject>()) {
         *definesPast = false;
         return true;
--- a/js/src/jsarray.h
+++ b/js/src/jsarray.h
@@ -78,19 +78,23 @@ NewDenseCopiedArray(JSContext *cx, uint3
 extern bool
 WouldDefinePastNonwritableLength(ThreadSafeContext *cx,
                                  HandleObject obj, uint32_t index, bool strict,
                                  bool *definesPast);
 
 /*
  * Canonicalize |vp| to a uint32_t value potentially suitable for use as an
  * array length.
+ *
+ * For parallel execution we can only canonicalize non-object values.
  */
+template <ExecutionMode mode>
 extern bool
-CanonicalizeArrayLengthValue(JSContext *cx, HandleValue v, uint32_t *canonicalized);
+CanonicalizeArrayLengthValue(typename ExecutionModeTraits<mode>::ContextType cx,
+                             HandleValue v, uint32_t *canonicalized);
 
 extern bool
 GetLengthProperty(JSContext *cx, HandleObject obj, uint32_t *lengthp);
 
 extern bool
 SetLengthProperty(JSContext *cx, HandleObject obj, double length);
 
 extern bool
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -2512,16 +2512,25 @@ TypeObject::markPropertyConfigured(Exclu
 
     id = IdToTypeId(id);
 
     TypeSet *types = getProperty(cx, id);
     if (types)
         types->setConfiguredProperty(cx);
 }
 
+bool
+TypeObject::isPropertyConfigured(jsid id)
+{
+    TypeSet *types = maybeGetProperty(id);
+    if (types)
+        return types->configuredProperty();
+    return false;
+}
+
 void
 TypeObject::markStateChange(ExclusiveContext *cxArg)
 {
     if (unknownProperties())
         return;
 
     AutoEnterAnalysis enter(cxArg);
     TypeSet *types = maybeGetProperty(JSID_EMPTY);
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -1059,16 +1059,17 @@ struct TypeObject : gc::BarrieredCell<Ty
     void addPropertyType(ExclusiveContext *cx, const char *name, const Value &value);
     void markPropertyConfigured(ExclusiveContext *cx, jsid id);
     void markStateChange(ExclusiveContext *cx);
     void setFlags(ExclusiveContext *cx, TypeObjectFlags flags);
     void markUnknown(ExclusiveContext *cx);
     void clearAddendum(ExclusiveContext *cx);
     void clearNewScriptAddendum(ExclusiveContext *cx);
     void clearTypedObjectAddendum(ExclusiveContext *cx);
+    bool isPropertyConfigured(jsid id);
 
     void print();
 
     inline void clearProperties();
     inline void sweep(FreeOp *fop);
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -528,16 +528,22 @@ MarkTypePropertyConfigured(ExclusiveCont
 {
     if (cx->typeInferenceEnabled()) {
         id = IdToTypeId(id);
         if (TrackPropertyTypes(cx, obj, id))
             obj->type()->markPropertyConfigured(cx, id);
     }
 }
 
+inline bool
+IsTypePropertyIdMarkedConfigured(JSObject *obj, jsid id)
+{
+    return obj->type()->isPropertyConfigured(id);
+}
+
 /* Mark a state change on a particular object. */
 inline void
 MarkObjectStateChange(ExclusiveContext *cx, JSObject *obj)
 {
     if (cx->typeInferenceEnabled() && !obj->hasLazyType() && !obj->type()->unknownProperties())
         obj->type()->markStateChange(cx);
 }
 
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -879,17 +879,17 @@ DefinePropertyOnArray(JSContext *cx, Han
         // But canonicalization may throw a RangeError (or other exception, if
         // the value is an object with user-defined conversion semantics)
         // before other attributes are checked.  So as long as our internal
         // defineProperty hook doesn't match the ECMA one, this duplicate
         // checking can't be helped.
         RootedValue v(cx);
         if (desc.hasValue()) {
             uint32_t newLen;
-            if (!CanonicalizeArrayLengthValue(cx, desc.value(), &newLen))
+            if (!CanonicalizeArrayLengthValue<SequentialExecution>(cx, desc.value(), &newLen))
                 return false;
             v.setNumber(newLen);
         } else {
             v.setNumber(arr->length());
         }
 
         if (desc.hasConfigurable() && desc.configurable())
             return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval);
@@ -903,17 +903,17 @@ DefinePropertyOnArray(JSContext *cx, Han
         if (!arr->lengthIsWritable()) {
             if (desc.hasWritable() && desc.writable())
                 return Reject(cx, id, JSMSG_CANT_REDEFINE_PROP, throwError, rval);
         } else {
             if (desc.hasWritable() && !desc.writable())
                 attrs = attrs | JSPROP_READONLY;
         }
 
-        return ArraySetLength(cx, arr, id, attrs, v, throwError);
+        return ArraySetLength<SequentialExecution>(cx, arr, id, attrs, v, throwError);
     }
 
     /* Step 3. */
     uint32_t index;
     if (js_IdIsIndex(id, &index)) {
         /* Step 3b. */
         uint32_t oldLen = arr->length();
 
@@ -3527,22 +3527,20 @@ DefinePropertyOrElement(typename Executi
             obj->setDenseElementMaybeConvertDouble(index, value);
             return CallAddPropertyHookDense<mode>(cx, obj->getClass(), obj, index, value);
         }
     }
 
     if (obj->is<ArrayObject>()) {
         Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
         if (id == NameToId(cx->names().length)) {
-            if (mode == ParallelExecution)
+            if (mode == SequentialExecution && !cx->shouldBeJSContext())
                 return false;
-
-            if (!cx->shouldBeJSContext())
-                return false;
-            return ArraySetLength(cx->asJSContext(), arr, id, attrs, value, setterIsStrict);
+            return ArraySetLength<mode>(ExecutionModeTraits<mode>::toContextType(cx), arr, id,
+                                        attrs, value, setterIsStrict);
         }
 
         uint32_t index;
         if (js_IdIsIndex(id, &index)) {
             bool definesPast;
             if (!WouldDefinePastNonwritableLength(cx, arr, index, setterIsStrict, &definesPast))
                 return false;
             if (definesPast) {
@@ -3629,24 +3627,24 @@ js::DefineNativeProperty(ExclusiveContex
             return false;
         if (shape && pobj == obj) {
             if (IsImplicitDenseElement(shape)) {
                 if (!JSObject::sparsifyDenseElement(cx, obj, JSID_TO_INT(id)))
                     return false;
                 shape = obj->nativeLookup(cx, id);
             }
             if (shape->isAccessorDescriptor()) {
-                shape = JSObject::changeProperty(cx, obj, shape, attrs,
-                                                 JSPROP_GETTER | JSPROP_SETTER,
-                                                 (attrs & JSPROP_GETTER)
-                                                 ? getter
-                                                 : shape->getter(),
-                                                 (attrs & JSPROP_SETTER)
-                                                 ? setter
-                                                 : shape->setter());
+                shape = JSObject::changeProperty<SequentialExecution>(cx, obj, shape, attrs,
+                                                                      JSPROP_GETTER | JSPROP_SETTER,
+                                                                      (attrs & JSPROP_GETTER)
+                                                                      ? getter
+                                                                      : shape->getter(),
+                                                                      (attrs & JSPROP_SETTER)
+                                                                      ? setter
+                                                                      : shape->setter());
                 if (!shape)
                     return false;
             } else {
                 shape = nullptr;
             }
         } else {
             shape = nullptr;
         }
@@ -4808,22 +4806,18 @@ baseops::SetPropertyHelper(typename Exec
         if (mode == ParallelExecution)
             obj->setDenseElementIfHasType(index, vp);
         else
             JSObject::setDenseElementWithType(cxArg->asJSContext(), obj, index, vp);
         return true;
     }
 
     if (obj->is<ArrayObject>() && id == NameToId(cxArg->names().length)) {
-        if (mode == ParallelExecution)
-            return false;
-
-        JSContext *cx = cxArg->asJSContext();
-        Rooted<ArrayObject*> arr(cx, &obj->as<ArrayObject>());
-        return ArraySetLength(cx, arr, id, attrs, vp, strict);
+        Rooted<ArrayObject*> arr(cxArg, &obj->as<ArrayObject>());
+        return ArraySetLength<mode>(cxArg, arr, id, attrs, vp, strict);
     }
 
     if (!shape) {
         bool extensible;
         if (mode == ParallelExecution) {
             if (obj->is<ProxyObject>())
                 return false;
             extensible = obj->nonProxyIsExtensible();
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -894,19 +894,21 @@ class JSObject : public js::ObjectImpl
     static inline js::Shape *
     putProperty(typename js::ExecutionModeTraits<mode>::ExclusiveContextType cx,
                 JS::HandleObject obj, js::PropertyName *name,
                 JSPropertyOp getter, JSStrictPropertyOp setter,
                 uint32_t slot, unsigned attrs,
                 unsigned flags, int shortid);
 
     /* Change the given property into a sibling with the same id in this scope. */
-    static js::Shape *changeProperty(js::ExclusiveContext *cx, js::HandleObject obj,
-                                     js::HandleShape shape, unsigned attrs, unsigned mask,
-                                     JSPropertyOp getter, JSStrictPropertyOp setter);
+    template <js::ExecutionMode mode>
+    static js::Shape *
+    changeProperty(typename js::ExecutionModeTraits<mode>::ExclusiveContextType cx,
+                   js::HandleObject obj, js::HandleShape shape, unsigned attrs, unsigned mask,
+                   JSPropertyOp getter, JSStrictPropertyOp setter);
 
     static inline bool changePropertyAttributes(JSContext *cx, js::HandleObject obj,
                                                 js::HandleShape shape, unsigned attrs);
 
     /* Remove the property named by id from this object. */
     bool removeProperty(js::ExclusiveContext *cx, jsid id);
 
     /* Clear the scope, making it empty. */
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -29,17 +29,18 @@ JSObject::setGenericAttributes(JSContext
     js::GenericAttributesOp op = obj->getOps()->setGenericAttributes;
     return (op ? op : js::baseops::SetAttributes)(cx, obj, id, attrsp);
 }
 
 /* static */ inline bool
 JSObject::changePropertyAttributes(JSContext *cx, js::HandleObject obj,
                                    js::HandleShape shape, unsigned attrs)
 {
-    return !!changeProperty(cx, obj, shape, attrs, 0, shape->getter(), shape->setter());
+    return !!changeProperty<js::SequentialExecution>(cx, obj, shape, attrs, 0,
+                                                     shape->getter(), shape->setter());
 }
 
 /* static */ inline bool
 JSObject::deleteProperty(JSContext *cx, js::HandleObject obj, js::HandlePropertyName name,
                          bool *succeeded)
 {
     JS::RootedId id(cx, js::NameToId(name));
     js::types::AddTypePropertyId(cx, obj, id, js::types::Type::UndefinedType());
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -671,19 +671,21 @@ class ArrayBufferObject;
 /*
  * ES6 20130308 draft 8.4.2.4 ArraySetLength.
  *
  * |id| must be "length", |attrs| are the attributes to be used for the newly-
  * changed length property, |value| is the value for the new length, and
  * |setterIsStrict| indicates whether invalid changes will cause a TypeError
  * to be thrown.
  */
+template <ExecutionMode mode>
 extern bool
-ArraySetLength(JSContext *cx, Handle<ArrayObject*> obj, HandleId id, unsigned attrs,
-               HandleValue value, bool setterIsStrict);
+ArraySetLength(typename ExecutionModeTraits<mode>::ContextType cx,
+               Handle<ArrayObject*> obj, HandleId id,
+               unsigned attrs, HandleValue value, bool setterIsStrict);
 
 /*
  * Elements header used for all native objects. The elements component of such
  * objects offers an efficient representation for all or some of the indexed
  * properties of the object, using a flat array of Values rather than a shape
  * hierarchy stored in the object's slots. This structure is immediately
  * followed by an array of elements, with the elements member in an object
  * pointing to the beginning of that array (the end of this structure).
@@ -763,19 +765,21 @@ class ObjectElements
 
   private:
     friend class ::JSObject;
     friend class ObjectImpl;
     friend class ArrayObject;
     friend class ArrayBufferObject;
     friend class Nursery;
 
+    template <ExecutionMode mode>
     friend bool
-    ArraySetLength(JSContext *cx, Handle<ArrayObject*> obj, HandleId id, unsigned attrs,
-                   HandleValue value, bool setterIsStrict);
+    ArraySetLength(typename ExecutionModeTraits<mode>::ContextType cx,
+                   Handle<ArrayObject*> obj, HandleId id,
+                   unsigned attrs, HandleValue value, bool setterIsStrict);
 
     /* See Flags enum above. */
     uint32_t flags;
 
     /*
      * Number of initialized elements. This is <= the capacity, and for arrays
      * is <= the length. Memory for elements above the initialized length is
      * uninitialized, but values between the initialized length and the proper
--- a/js/src/vm/Shape.cpp
+++ b/js/src/vm/Shape.cpp
@@ -899,31 +899,47 @@ JSObject::putProperty<SequentialExecutio
                                            unsigned flags, int shortid);
 template /* static */ Shape *
 JSObject::putProperty<ParallelExecution>(ForkJoinSlice *cx,
                                          HandleObject obj, HandleId id,
                                          PropertyOp getter, StrictPropertyOp setter,
                                          uint32_t slot, unsigned attrs,
                                          unsigned flags, int shortid);
 
+template <ExecutionMode mode>
 /* static */ Shape *
-JSObject::changeProperty(ExclusiveContext *cx, HandleObject obj, HandleShape shape, unsigned attrs,
+JSObject::changeProperty(typename ExecutionModeTraits<mode>::ExclusiveContextType cx,
+                         HandleObject obj, HandleShape shape, unsigned attrs,
                          unsigned mask, PropertyOp getter, StrictPropertyOp setter)
 {
-    JS_ASSERT(obj->nativeContains(cx, shape));
+    JS_ASSERT(cx->isThreadLocal(obj));
+    JS_ASSERT(obj->nativeContainsPure(shape));
 
     attrs |= shape->attrs & mask;
 
     /* Allow only shared (slotless) => unshared (slotful) transition. */
     JS_ASSERT(!((attrs ^ shape->attrs) & JSPROP_SHARED) ||
               !(attrs & JSPROP_SHARED));
 
-    types::MarkTypePropertyConfigured(cx, obj, shape->propid());
-    if (attrs & (JSPROP_GETTER | JSPROP_SETTER))
-        types::AddTypePropertyId(cx, obj, shape->propid(), types::Type::UnknownType());
+    if (mode == ParallelExecution) {
+        if (!types::IsTypePropertyIdMarkedConfigured(obj, shape->propid()))
+            return nullptr;
+    } else {
+        types::MarkTypePropertyConfigured(cx->asExclusiveContext(), obj, shape->propid());
+    }
+
+    if (attrs & (JSPROP_GETTER | JSPROP_SETTER)) {
+        if (mode == ParallelExecution) {
+            if (!types::HasTypePropertyId(obj, shape->propid(), types::Type::UnknownType()))
+                return nullptr;
+        } else {
+            types::AddTypePropertyId(cx->asExclusiveContext(), obj, shape->propid(),
+                                     types::Type::UnknownType());
+        }
+    }
 
     if (getter == JS_PropertyStub)
         getter = nullptr;
     if (setter == JS_StrictPropertyStub)
         setter = nullptr;
 
     if (!CheckCanChangeAttrs(cx, obj, shape, &attrs))
         return nullptr;
@@ -933,24 +949,35 @@ JSObject::changeProperty(ExclusiveContex
 
     /*
      * Let JSObject::putProperty handle this |overwriting| case, including
      * the conservation of shape->slot (if it's valid). We must not call
      * removeProperty because it will free an allocated shape->slot, and
      * putProperty won't re-allocate it.
      */
     RootedId propid(cx, shape->propid());
-    Shape *newShape = putProperty<SequentialExecution>(cx, obj, propid, getter, setter,
-                                                       shape->maybeSlot(), attrs, shape->flags,
-                                                       shape->maybeShortid());
+    Shape *newShape = putProperty<mode>(cx, obj, propid, getter, setter,
+                                        shape->maybeSlot(), attrs, shape->flags,
+                                        shape->maybeShortid());
 
     obj->checkShapeConsistency();
     return newShape;
 }
 
+template /* static */ Shape *
+JSObject::changeProperty<SequentialExecution>(ExclusiveContext *cx,
+                                              HandleObject obj, HandleShape shape,
+                                              unsigned attrs, unsigned mask,
+                                              PropertyOp getter, StrictPropertyOp setter);
+template /* static */ Shape *
+JSObject::changeProperty<ParallelExecution>(ForkJoinSlice *slice,
+                                            HandleObject obj, HandleShape shape,
+                                            unsigned attrs, unsigned mask,
+                                            PropertyOp getter, StrictPropertyOp setter);
+
 bool
 JSObject::removeProperty(ExclusiveContext *cx, jsid id_)
 {
     RootedId id(cx, id_);
     RootedObject self(cx, this);
 
     Shape **spp;
     RootedShape shape(cx, Shape::search(cx, lastProperty(), id, &spp));