Bug 880208 - Add UnsafeGet and UnsafeGetImmutable intrinsics r=djvj
authorNicholas D. Matsakis <nmatsakis@mozilla.com>
Thu, 06 Jun 2013 11:01:15 -0400
changeset 135050 18c1fd169792b182935cbe930e8b5ea1c0ed0908
parent 135049 52c875b9c520f19cbe9ed33f7cc11be689bc8d61
child 135051 ccd298a9db28814e7d9bd5b6ae60a80ee9984eb9
push id29502
push usernmatsakis@mozilla.com
push dateFri, 14 Jun 2013 10:16:23 +0000
treeherdermozilla-inbound@18c1fd169792 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdjvj
bugs880208
milestone24.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 880208 - Add UnsafeGet and UnsafeGetImmutable intrinsics r=djvj
js/src/builtin/ParallelArray.js
js/src/ion/IonBuilder.cpp
js/src/ion/IonBuilder.h
js/src/ion/MCallOptimize.cpp
js/src/ion/MIR.h
js/src/jscntxt.h
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/ParallelArray.js
+++ b/js/src/builtin/ParallelArray.js
@@ -372,17 +372,17 @@ function ParallelArrayBuild(self, shape,
  * element |e| with index |i|. Note that
  * this always operates on the outermost dimension only.
  */
 function ParallelArrayMap(func, mode) {
   // FIXME(bug 844887): Check |this instanceof ParallelArray|
   // FIXME(bug 844887): Check |IsCallable(func)|
 
   var self = this;
-  var length = self.shape[0];
+  var length = UnsafeGetImmutableElement(self.shape, 0);
   var buffer = NewDenseArray(length);
 
   parallel: for (;;) { // see ParallelArrayBuild() to explain why for(;;) etc
     if (ShouldForceSequential())
       break parallel;
     if (!TRY_PARALLEL(mode))
       break parallel;
 
@@ -427,17 +427,17 @@ function ParallelArrayMap(func, mode) {
  * Reduces the elements in a parallel array's outermost dimension
  * using the given reduction function.
  */
 function ParallelArrayReduce(func, mode) {
   // FIXME(bug 844887): Check |this instanceof ParallelArray|
   // FIXME(bug 844887): Check |IsCallable(func)|
 
   var self = this;
-  var length = self.shape[0];
+  var length = UnsafeGetImmutableElement(self.shape, 0);
 
   if (length === 0)
     ThrowError(JSMSG_PAR_ARRAY_REDUCE_EMPTY);
 
   parallel: for (;;) { // see ParallelArrayBuild() to explain why for(;;) etc
     if (ShouldForceSequential())
       break parallel;
     if (!TRY_PARALLEL(mode))
@@ -514,17 +514,17 @@ function ParallelArrayReduce(func, mode)
  * of elements |0..i|. This is the generalization
  * of partial sum.
  */
 function ParallelArrayScan(func, mode) {
   // FIXME(bug 844887): Check |this instanceof ParallelArray|
   // FIXME(bug 844887): Check |IsCallable(func)|
 
   var self = this;
-  var length = self.shape[0];
+  var length = UnsafeGetImmutableElement(self.shape, 0);
 
   if (length === 0)
     ThrowError(JSMSG_PAR_ARRAY_REDUCE_EMPTY);
 
   var buffer = NewDenseArray(length);
 
   parallel: for (;;) { // see ParallelArrayBuild() to explain why for(;;) etc
     if (ShouldForceSequential())
@@ -721,17 +721,17 @@ function ParallelArrayScan(func, mode) {
 function ParallelArrayScatter(targets, defaultValue, conflictFunc, length, mode) {
   // FIXME(bug 844887): Check |this instanceof ParallelArray|
   // FIXME(bug 844887): Check targets is array-like
   // FIXME(bug 844887): Check |IsCallable(conflictFunc)|
 
   var self = this;
 
   if (length === undefined)
-    length = self.shape[0];
+    length = UnsafeGetImmutableElement(self.shape, 0);
 
   // The Divide-Scatter-Vector strategy:
   // 1. Slice |targets| array of indices ("scatter-vector") into N
   //    parts.
   // 2. Each of the N threads prepares an output buffer and a
   //    write-log.
   // 3. Each thread scatters according to one of the N parts into its
   //    own output buffer, tracking written indices in the write-log
@@ -972,17 +972,17 @@ function ParallelArrayScatter(targets, d
  * The familiar filter() operation applied across the outermost
  * dimension.
  */
 function ParallelArrayFilter(func, mode) {
   // FIXME(bug 844887): Check |this instanceof ParallelArray|
   // FIXME(bug 844887): Check |IsCallable(func)|
 
   var self = this;
-  var length = self.shape[0];
+  var length = UnsafeGetImmutableElement(self.shape, 0);
 
   parallel: for (;;) { // see ParallelArrayBuild() to explain why for(;;) etc
     if (ShouldForceSequential())
       break parallel;
     if (!TRY_PARALLEL(mode))
       break parallel;
 
     var chunks = ComputeNumChunks(length);
@@ -1146,60 +1146,63 @@ function ParallelArrayFlatten() {
 //
 
 /**
  * Specialized variant of get() for one-dimensional case
  */
 function ParallelArrayGet1(i) {
   if (i === undefined)
     return undefined;
+
+  // We could use UnsafeGetImmutableElement here, but I am not doing
+  // so (for the moment) since using the default path enables
+  // bounds-check hoisting, which is (currently) not possible
+  // otherwise.
   return this.buffer[this.offset + i];
 }
 
 /**
  * Specialized variant of get() for two-dimensional case
  */
 function ParallelArrayGet2(x, y) {
-  var xDimension = this.shape[0];
-  var yDimension = this.shape[1];
-  if (x === undefined)
-    return undefined;
-  if (x >= xDimension)
+  var xDimension = UnsafeGetImmutableElement(this.shape, 0);
+  var yDimension = UnsafeGetImmutableElement(this.shape, 1);
+  if (x === undefined || TO_INT32(x) !== x || x >= xDimension)
     return undefined;
   if (y === undefined)
     return NewParallelArray(ParallelArrayView, [yDimension], this.buffer, this.offset + x * yDimension);
-  if (y >= yDimension)
+  if (TO_INT32(y) !== y || y >= yDimension)
     return undefined;
   var offset = y + x * yDimension;
-  return this.buffer[this.offset + offset];
+  return UnsafeGetImmutableElement(this.buffer, this.offset + offset);
 }
 
 /**
  * Specialized variant of get() for three-dimensional case
  */
 function ParallelArrayGet3(x, y, z) {
-  var xDimension = this.shape[0];
-  var yDimension = this.shape[1];
-  var zDimension = this.shape[2];
+  var xDimension = UnsafeGetImmutableElement(this.shape, 0);
+  var yDimension = UnsafeGetImmutableElement(this.shape, 1);
+  var zDimension = UnsafeGetImmutableElement(this.shape, 2);
   if (x === undefined)
     return undefined;
   if (x >= xDimension)
     return undefined;
   if (y === undefined)
     return NewParallelArray(ParallelArrayView, [yDimension, zDimension],
                             this.buffer, this.offset + x * yDimension * zDimension);
   if (y >= yDimension)
     return undefined;
   if (z === undefined)
     return NewParallelArray(ParallelArrayView, [zDimension],
                             this.buffer, this.offset + y * zDimension + x * yDimension * zDimension);
   if (z >= zDimension)
     return undefined;
   var offset = z + y*zDimension + x * yDimension * zDimension;
-  return this.buffer[this.offset + offset];
+  return UnsafeGetImmutableElement(this.buffer, this.offset + offset);
 }
 
 /**
  * Generalized version of get() for N-dimensional case
  */
 function ParallelArrayGetN(...coords) {
   if (coords.length == 0)
     return undefined;
@@ -1223,17 +1226,17 @@ function ParallelArrayGetN(...coords) {
     var shape = callFunction(std_Array_slice, this.shape, cDimensionality);
     return NewParallelArray(ParallelArrayView, shape, this.buffer, offset);
   }
   return this.buffer[offset];
 }
 
 /** The length property yields the outermost dimension */
 function ParallelArrayLength() {
-  return this.shape[0];
+  return UnsafeGetImmutableElement(this.shape, 0);
 }
 
 function ParallelArrayToString() {
   var l = this.length;
   if (l == 0)
     return "";
 
   var open, close;
--- a/js/src/ion/IonBuilder.cpp
+++ b/js/src/ion/IonBuilder.cpp
@@ -6259,18 +6259,21 @@ bool
 IonBuilder::jsop_getelem()
 {
     MDefinition *obj = current->peek(-2);
     MDefinition *index = current->peek(-1);
 
     if (ElementAccessIsDenseNative(obj, index)) {
         // Don't generate a fast path if there have been bounds check failures
         // and this access might be on a sparse property.
-        if (!ElementAccessHasExtraIndexedProperty(cx, obj) || !failedBoundsCheck_)
-            return jsop_getelem_dense();
+        if (!ElementAccessHasExtraIndexedProperty(cx, obj) || !failedBoundsCheck_) {
+            MDefinition *id = current->pop();
+            MDefinition *obj = current->pop();
+            return jsop_getelem_dense(GetElem_Normal, obj, id);
+        }
     }
 
     int arrayType = TypedArray::TYPE_MAX;
     if (ElementAccessIsTypedArray(obj, index, &arrayType))
         return jsop_getelem_typed(arrayType);
 
     if (obj->type() == MIRType_String)
         return jsop_getelem_string();
@@ -6325,37 +6328,35 @@ IonBuilder::jsop_getelem()
         if (knownType != JSVAL_TYPE_UNKNOWN && knownType != JSVAL_TYPE_DOUBLE)
             ins->setResultType(MIRTypeFromValueType(knownType));
     }
 
     return pushTypeBarrier(ins, types, barrier);
 }
 
 bool
-IonBuilder::jsop_getelem_dense()
-{
-    MDefinition *id = current->pop();
-    MDefinition *obj = current->pop();
-
+IonBuilder::jsop_getelem_dense(GetElemSafety safety, MDefinition *obj, MDefinition *id)
+{
     types::StackTypeSet *types = types::TypeScript::BytecodeTypes(script(), pc);
 
     if (JSOp(*pc) == JSOP_CALLELEM && !id->mightBeType(MIRType_String) && types->noConstraints()) {
         // Indexed call on an element of an array. Populate the observed types
         // with any objects that could be in the array, to avoid extraneous
         // type barriers.
         AddObjectsForPropertyRead(cx, obj, NULL, types);
     }
 
     bool barrier = PropertyReadNeedsTypeBarrier(cx, obj, NULL, types);
     bool needsHoleCheck = !ElementAccessIsPacked(cx, obj);
 
     // Reads which are on holes in the object do not have to bail out if
     // undefined values have been observed at this access site and the access
     // cannot hit another indexed property on the object or its prototypes.
     bool readOutOfBounds =
+        safety == GetElem_Normal &&
         types->hasType(types::Type::UndefinedType()) &&
         !ElementAccessHasExtraIndexedProperty(cx, obj);
 
     JSValueType knownType = JSVAL_TYPE_UNKNOWN;
     if (!barrier)
         knownType = GetElemKnownType(needsHoleCheck, types);
 
     // Ensure id is an integer.
@@ -6382,34 +6383,45 @@ IonBuilder::jsop_getelem_dense()
         !readOutOfBounds &&
         !needsHoleCheck &&
         knownType == JSVAL_TYPE_DOUBLE &&
         objTypes &&
         objTypes->convertDoubleElements(cx) == types::StackTypeSet::AlwaysConvertToDoubles;
     if (loadDouble)
         elements = addConvertElementsToDoubles(elements);
 
-    MInitializedLength *initLength = MInitializedLength::New(elements);
-    current->add(initLength);
-
     MInstruction *load;
 
     if (!readOutOfBounds) {
         // This load should not return undefined, so likely we're reading
         // in-bounds elements, and the array is packed or its holes are not
         // read. This is the best case: we can separate the bounds check for
         // hoisting.
-        id = addBoundsCheck(id, initLength);
-
-        load = MLoadElement::New(elements, id, needsHoleCheck, loadDouble);
+        switch (safety) {
+          case GetElem_Normal: {
+              MInitializedLength *initLength = MInitializedLength::New(elements);
+              current->add(initLength);
+              id = addBoundsCheck(id, initLength);
+              break;
+          }
+
+          case GetElem_Unsafe: break;
+          case GetElem_UnsafeImmutable: break;
+        }
+
+        bool knownImmutable = (safety == GetElem_UnsafeImmutable);
+        load = MLoadElement::New(elements, id, needsHoleCheck, loadDouble,
+                                 knownImmutable);
         current->add(load);
     } else {
         // This load may return undefined, so assume that we *can* read holes,
         // or that we can read out-of-bounds accesses. In this case, the bounds
         // check is part of the opcode.
+        MInitializedLength *initLength = MInitializedLength::New(elements);
+        current->add(initLength);
         load = MLoadElementHole::New(elements, id, initLength, needsHoleCheck);
         current->add(load);
 
         // If maybeUndefined was true, the typeset must have undefined, and
         // then either additional types or a barrier. This means we should
         // never have a typed version of LoadElementHole.
         JS_ASSERT(knownType == JSVAL_TYPE_UNKNOWN);
     }
--- a/js/src/ion/IonBuilder.h
+++ b/js/src/ion/IonBuilder.h
@@ -36,16 +36,30 @@ class IonBuilder : public MIRGenerator
         SetElem_Normal,
 
         // Write due to UnsafeSetElement:
         // - assumed to be in bounds,
         // - not checked for data races
         SetElem_Unsafe,
     };
 
+    enum GetElemSafety {
+        // Normal read like a[b]
+        GetElem_Normal,
+
+        // Read due to UnsafeGetElement:
+        // - assumed to be in bounds,
+        GetElem_Unsafe,
+
+        // Read due to UnsafeGetImmutableElement:
+        // - assumed to be in bounds,
+        // - assumed not to alias any stores
+        GetElem_UnsafeImmutable,
+    };
+
     struct DeferredEdge : public TempObject
     {
         MBasicBlock *block;
         DeferredEdge *next;
 
         DeferredEdge(MBasicBlock *block, DeferredEdge *next)
           : block(block), next(next)
         { }
@@ -386,17 +400,17 @@ class IonBuilder : public MIRGenerator
     bool jsop_loophead(jsbytecode *pc);
     bool jsop_compare(JSOp op);
     bool getStaticName(HandleObject staticObject, HandlePropertyName name, bool *psucceeded);
     bool setStaticName(HandleObject staticObject, HandlePropertyName name);
     bool jsop_getname(HandlePropertyName name);
     bool jsop_intrinsic(HandlePropertyName name);
     bool jsop_bindname(PropertyName *name);
     bool jsop_getelem();
-    bool jsop_getelem_dense();
+    bool jsop_getelem_dense(GetElemSafety safety, MDefinition *object, MDefinition *index);
     bool jsop_getelem_typed(int arrayType);
     bool jsop_getelem_typed_static(bool *psucceeded);
     bool jsop_getelem_string();
     bool jsop_setelem();
     bool jsop_setelem_dense(types::StackTypeSet::DoubleConversion conversion,
                             SetElemSafety safety,
                             MDefinition *object, MDefinition *index, MDefinition *value);
     bool jsop_setelem_typed(int arrayType,
@@ -481,16 +495,18 @@ class IonBuilder : public MIRGenerator
 
     // RegExp natives.
     InliningStatus inlineRegExpTest(CallInfo &callInfo);
 
     // Parallel Array.
     InliningStatus inlineUnsafeSetElement(CallInfo &callInfo);
     bool inlineUnsafeSetDenseArrayElement(CallInfo &callInfo, uint32_t base);
     bool inlineUnsafeSetTypedArrayElement(CallInfo &callInfo, uint32_t base, int arrayType);
+    InliningStatus inlineUnsafeGetElement(CallInfo &callInfo,
+                                          GetElemSafety safety);
     InliningStatus inlineForceSequentialOrInParallelSection(CallInfo &callInfo);
     InliningStatus inlineNewDenseArray(CallInfo &callInfo);
     InliningStatus inlineNewDenseArrayForSequentialExecution(CallInfo &callInfo);
     InliningStatus inlineNewDenseArrayForParallelExecution(CallInfo &callInfo);
     InliningStatus inlineNewParallelArray(CallInfo &callInfo);
     InliningStatus inlineParallelArray(CallInfo &callInfo);
     InliningStatus inlineParallelArrayTail(CallInfo &callInfo,
                                            HandleFunction target,
--- a/js/src/ion/MCallOptimize.cpp
+++ b/js/src/ion/MCallOptimize.cpp
@@ -85,16 +85,20 @@ IonBuilder::inlineNativeCall(CallInfo &c
     if (native == regexp_exec && !CallResultEscapes(pc))
         return inlineRegExpTest(callInfo);
     if (native == regexp_test)
         return inlineRegExpTest(callInfo);
 
     // Array intrinsics.
     if (native == intrinsic_UnsafeSetElement)
         return inlineUnsafeSetElement(callInfo);
+    if (native == intrinsic_UnsafeGetElement)
+        return inlineUnsafeGetElement(callInfo, GetElem_Unsafe);
+    if (native == intrinsic_UnsafeGetImmutableElement)
+        return inlineUnsafeGetElement(callInfo, GetElem_UnsafeImmutable);
     if (native == intrinsic_NewDenseArray)
         return inlineNewDenseArray(callInfo);
 
     // Utility intrinsics.
     if (native == intrinsic_ThrowError)
         return inlineThrowError(callInfo);
     if (native == intrinsic_IsCallable)
         return inlineIsCallable(callInfo);
@@ -947,19 +951,20 @@ IonBuilder::inlineUnsafeSetElement(CallI
 {
     uint32_t argc = callInfo.argc();
     if (argc < 3 || (argc % 3) != 0 || callInfo.constructing())
         return InliningStatus_NotInlined;
 
     /* Important:
      *
      * Here we inline each of the stores resulting from a call to
-     * %UnsafeSetElement().  It is essential that these stores occur
+     * UnsafeSetElement().  It is essential that these stores occur
      * atomically and cannot be interrupted by a stack or recursion
      * check.  If this is not true, race conditions can occur.
+     * See definition of UnsafeSetElement() for more details.
      */
 
     for (uint32_t base = 0; base < argc; base += 3) {
         uint32_t arri = base + 0;
         uint32_t idxi = base + 1;
         uint32_t elemi = base + 2;
 
         MDefinition *obj = callInfo.getArg(arri);
@@ -1049,16 +1054,40 @@ IonBuilder::inlineUnsafeSetTypedArrayEle
 
     if (!jsop_setelem_typed(arrayType, SetElem_Unsafe, obj, id, elem))
         return false;
 
     return true;
 }
 
 IonBuilder::InliningStatus
+IonBuilder::inlineUnsafeGetElement(CallInfo &callInfo,
+                                   GetElemSafety safety)
+{
+    JS_ASSERT(safety != GetElem_Normal);
+
+    uint32_t argc = callInfo.argc();
+    if (argc < 2 || callInfo.constructing())
+        return InliningStatus_NotInlined;
+    const uint32_t obj = 0;
+    const uint32_t index = 1;
+    if (!ElementAccessIsDenseNative(callInfo.getArg(obj),
+                                    callInfo.getArg(index)))
+        return InliningStatus_NotInlined;
+    if (ElementAccessHasExtraIndexedProperty(cx, callInfo.getArg(obj)))
+        return InliningStatus_NotInlined;
+    callInfo.unwrapArgs();
+    if (!jsop_getelem_dense(safety,
+                            callInfo.getArg(obj),
+                            callInfo.getArg(index)))
+        return InliningStatus_Error;
+    return InliningStatus_Inlined;
+}
+
+IonBuilder::InliningStatus
 IonBuilder::inlineForceSequentialOrInParallelSection(CallInfo &callInfo)
 {
     if (callInfo.constructing())
         return InliningStatus_NotInlined;
 
     ExecutionMode executionMode = info().executionMode();
     switch (executionMode) {
       case SequentialExecution:
--- a/js/src/ion/MIR.h
+++ b/js/src/ion/MIR.h
@@ -4505,34 +4505,38 @@ class MBoundsCheckLower
 // Load a value from a dense array's element vector and does a hole check if the
 // array is not known to be packed.
 class MLoadElement
   : public MBinaryInstruction,
     public SingleObjectPolicy
 {
     bool needsHoleCheck_;
     bool loadDoubles_;
-
-    MLoadElement(MDefinition *elements, MDefinition *index, bool needsHoleCheck, bool loadDoubles)
+    bool knownImmutable_; // load of data that is known to be immutable
+
+    MLoadElement(MDefinition *elements, MDefinition *index, bool needsHoleCheck, bool loadDoubles, bool knownImmutable)
       : MBinaryInstruction(elements, index),
         needsHoleCheck_(needsHoleCheck),
-        loadDoubles_(loadDoubles)
+        loadDoubles_(loadDoubles),
+        knownImmutable_(knownImmutable)
     {
         setResultType(MIRType_Value);
         setMovable();
         JS_ASSERT(elements->type() == MIRType_Elements);
         JS_ASSERT(index->type() == MIRType_Int32);
     }
 
   public:
     INSTRUCTION_HEADER(LoadElement)
 
     static MLoadElement *New(MDefinition *elements, MDefinition *index,
-                             bool needsHoleCheck, bool loadDoubles) {
-        return new MLoadElement(elements, index, needsHoleCheck, loadDoubles);
+                             bool needsHoleCheck, bool loadDoubles,
+                             bool knownImmutable) {
+        return new MLoadElement(elements, index, needsHoleCheck, loadDoubles,
+                                knownImmutable);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
     MDefinition *elements() const {
         return getOperand(0);
     }
@@ -4544,16 +4548,19 @@ class MLoadElement
     }
     bool loadDoubles() const {
         return loadDoubles_;
     }
     bool fallible() const {
         return needsHoleCheck();
     }
     AliasSet getAliasSet() const {
+        if (knownImmutable_)
+            return AliasSet::None();
+
         return AliasSet::Load(AliasSet::Element);
     }
 };
 
 // Load a value from a dense array's element vector. If the index is
 // out-of-bounds, or the indexed slot has a hole, undefined is returned
 // instead.
 class MLoadElementHole
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -2355,16 +2355,19 @@ class ContextAllocPolicy
     void reportAllocOverflow() const { js_ReportAllocationOverflow(cx_); }
 };
 
 JSBool intrinsic_ToObject(JSContext *cx, unsigned argc, Value *vp);
 JSBool intrinsic_IsCallable(JSContext *cx, unsigned argc, Value *vp);
 JSBool intrinsic_ThrowError(JSContext *cx, unsigned argc, Value *vp);
 JSBool intrinsic_NewDenseArray(JSContext *cx, unsigned argc, Value *vp);
 JSBool intrinsic_UnsafeSetElement(JSContext *cx, unsigned argc, Value *vp);
+JSBool intrinsic_UnsafeGetElement(JSContext *cx, unsigned argc, Value *vp);
+JSBool intrinsic_UnsafeGetImmutableElement(JSContext *cx, unsigned argc,
+                                           Value *vp);
 JSBool intrinsic_ShouldForceSequential(JSContext *cx, unsigned argc, Value *vp);
 JSBool intrinsic_NewParallelArray(JSContext *cx, unsigned argc, Value *vp);
 
 #ifdef DEBUG
 JSBool intrinsic_Dump(JSContext *cx, unsigned argc, Value *vp);
 #endif
 
 } /* namespace js */
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -336,25 +336,36 @@ js::intrinsic_NewDenseArray(JSContext *c
         break;
     }
     return false;
 }
 
 /*
  * UnsafeSetElement(arr0, idx0, elem0, ..., arrN, idxN, elemN): For
  * each set of (arr, idx, elem) arguments that are passed, performs
- * the assignment |arr[idx] = elem|. |arr| must be either a dense array
+ * the assignment `arr[idx] = elem`. `arr` must be either a dense array
  * or a typed array.
  *
- * If |arr| is a dense array, the index must be an int32 less than the
- * initialized length of |arr|. Use |%EnsureDenseResultArrayElements|
- * to ensure that the initialized length is long enough.
+ * If `arr` is a dense array, the index must be an int32 less than the
+ * initialized length of `arr`. Use `NewDenseAllocatedArray` to ensure
+ * that the initialized length is long enough.
+ *
+ * If `arr` is a typed array, the index must be an int32 less than the
+ * length of `arr`.
  *
- * If |arr| is a typed array, the index must be an int32 less than the
- * length of |arr|.
+ * The reason that `UnsafeSetElement` takes multiple
+ * array/index/element triples is not for convenience but rather for
+ * semantic reasons: there are a few places in the parallel code where
+ * correctness relies on the fact that *all of the assignments occur
+ * or none of them*. This occurs in operations like reduce or fold
+ * which mutate the same data in place. That is, we do not want to
+ * bail out or interrupt in between the individual assignments. To
+ * convey this notion, we place all the assignments together into one
+ * `UnsafeSetElement` call. It is preferable to use multiple calls if
+ * it is not important that the assignments occur all-or-nothing.
  */
 JSBool
 js::intrinsic_UnsafeSetElement(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if ((args.length() % 3) != 0) {
         JS_ReportError(cx, "Incorrect number of arguments, not divisible by 3");
@@ -375,27 +386,66 @@ js::intrinsic_UnsafeSetElement(JSContext
         uint32_t idx = args[idxi].toInt32();
 
         if (arrobj->isNative()) {
             JS_ASSERT(idx < arrobj->getDenseInitializedLength());
             JSObject::setDenseElementWithType(cx, arrobj, idx, args[elemi]);
         } else {
             JS_ASSERT(idx < TypedArray::length(arrobj));
             RootedValue tmp(cx, args[elemi]);
-            // XXX: Always non-strict.
             if (!JSObject::setElement(cx, arrobj, arrobj, idx, &tmp, false))
                 return false;
         }
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 /*
+ * UnsafeGetElement(arr, idx)=elem:
+ *
+ * Loads an element from an array.  Requires that `arr` be a dense
+ * array and `idx` be in bounds.  In ion compiled code, no bounds
+ * check will be emitted.
+ */
+JSBool
+js::intrinsic_UnsafeGetElement(JSContext *cx, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+
+    const uint32_t arri = 0;
+    const uint32_t idxi = 1;
+
+    JS_ASSERT(args[arri].isObject());
+    JS_ASSERT(args[idxi].isInt32());
+
+    RootedObject arrobj(cx, &args[arri].toObject());
+    uint32_t idx = args[idxi].toInt32();
+
+    JS_ASSERT(args[arri].toObject().isNative());
+    JS_ASSERT(idx < arrobj->getDenseInitializedLength());
+    args.rval().set(arrobj->getDenseElement(idx));
+    return true;
+}
+
+/*
+ * UnsafeGetImmutableElement(arr, idx)=elem:
+ *
+ * Same as `UnsafeGetElement(arr, idx)`, except that the array is
+ * known by the self-hosting code to be immutable. Therefore, ion
+ * compilation can reorder this load freely with respect to stores.
+ */
+JSBool
+js::intrinsic_UnsafeGetImmutableElement(JSContext *cx, unsigned argc, Value *vp)
+{
+    return intrinsic_UnsafeGetElement(cx, argc, vp);
+}
+
+/*
  * ParallelTestsShouldPass(): Returns false if we are running in a
  * mode (such as --ion-eager) that is known to cause additional
  * bailouts or disqualifications for parallel array tests.
  *
  * This is needed because the parallel tests generally assert that,
  * under normal conditions, they will run without bailouts or
  * compilation failures, but this does not hold under "stress-testing"
  * conditions like --ion-eager or --no-ti.  However, running the tests
@@ -463,16 +513,18 @@ const JSFunctionSpec intrinsic_functions
     JS_FN("DecompileArg",         intrinsic_DecompileArg,         2,0),
     JS_FN("RuntimeDefaultLocale", intrinsic_RuntimeDefaultLocale, 0,0),
 
     JS_FN("ForkJoin",             intrinsic_ForkJoin,             2,0),
     JS_FN("ForkJoinSlices",       intrinsic_ForkJoinSlices,       0,0),
     JS_FN("NewParallelArray",     intrinsic_NewParallelArray,     3,0),
     JS_FN("NewDenseArray",        intrinsic_NewDenseArray,        1,0),
     JS_FN("UnsafeSetElement",     intrinsic_UnsafeSetElement,     3,0),
+    JS_FN("UnsafeGetElement",     intrinsic_UnsafeGetElement, 2,0),
+    JS_FN("UnsafeGetImmutableElement", intrinsic_UnsafeGetImmutableElement, 2,0),
     JS_FN("ShouldForceSequential", intrinsic_ShouldForceSequential, 0,0),
     JS_FN("ParallelTestsShouldPass", intrinsic_ParallelTestsShouldPass, 0,0),
 
     // See builtin/Intl.h for descriptions of the intl_* functions.
     JS_FN("intl_availableCalendars", intl_availableCalendars, 1,0),
     JS_FN("intl_availableCollations", intl_availableCollations, 1,0),
     JS_FN("intl_Collator", intl_Collator, 2,0),
     JS_FN("intl_Collator_availableLocales", intl_Collator_availableLocales, 0,0),