Bug 972581 part 2 -- Add 1-dim mapPar r=shu
authorNicholas D. Matsakis <nmatsakis@mozilla.com>
Thu, 13 Feb 2014 16:38:21 -0500
changeset 170274 108209641936fceec4680b577d288a1062c23af2
parent 170273 a7c61b56251219bcbcfb4b3a685d8bdc0b10f87b
child 170275 29af928ab75bad6a40e8c54ffe17e12f22e88127
push id26284
push usercbook@mozilla.com
push dateTue, 25 Feb 2014 13:28:56 +0000
treeherdermozilla-central@8c1eb349d342 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersshu
bugs972581
milestone30.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 972581 part 2 -- Add 1-dim mapPar r=shu
js/src/Makefile.in
js/src/builtin/Array.js
js/src/builtin/Parallel.js
js/src/builtin/TypedObject.cpp
js/src/builtin/TypedObject.h
js/src/builtin/TypedObject.js
js/src/builtin/Utilities.js
js/src/jit-test/tests/parallel/TypedObj-mapPar-outpointer-struct.js
js/src/jit-test/tests/parallel/TypedObj-mapPar-return-scalar.js
js/src/vm/SelfHosting.cpp
--- a/js/src/Makefile.in
+++ b/js/src/Makefile.in
@@ -372,16 +372,17 @@ selfhosting_srcs := \
   $(srcdir)/builtin/Utilities.js \
   $(srcdir)/builtin/Array.js \
   $(srcdir)/builtin/Date.js \
   $(srcdir)/builtin/Intl.js \
   $(srcdir)/builtin/IntlData.js \
   $(srcdir)/builtin/Iterator.js \
   $(srcdir)/builtin/Map.js \
   $(srcdir)/builtin/Number.js \
+  $(srcdir)/builtin/Parallel.js \
   $(srcdir)/builtin/String.js \
   $(srcdir)/builtin/Set.js \
   $(srcdir)/builtin/TypedObject.js \
   $(NULL)
 
 selfhosted_out_h_deps := \
   $(selfhosting_srcs) \
   $(srcdir)/js.msg \
--- a/js/src/builtin/Array.js
+++ b/js/src/builtin/Array.js
@@ -565,126 +565,16 @@ function ArrayKeys() {
 
 #ifdef ENABLE_PARALLEL_JS
 
 /*
  * Strawman spec:
  *   http://wiki.ecmascript.org/doku.php?id=strawman:data_parallelism
  */
 
-/* The mode asserts options object. */
-#define TRY_PARALLEL(MODE) \
-  ((!MODE || MODE.mode !== "seq"))
-#define ASSERT_SEQUENTIAL_IS_OK(MODE) \
-  do { if (MODE) AssertSequentialIsOK(MODE) } while(false)
-
-/* Safe versions of ARRAY.push(ELEMENT) */
-#define ARRAY_PUSH(ARRAY, ELEMENT) \
-  callFunction(std_Array_push, ARRAY, ELEMENT);
-#define ARRAY_SLICE(ARRAY, ELEMENT) \
-  callFunction(std_Array_slice, ARRAY, ELEMENT);
-
-/**
- * The ParallelSpew intrinsic is only defined in debug mode, so define a dummy
- * if debug is not on.
- */
-#ifndef DEBUG
-#define ParallelSpew(args)
-#endif
-
-#define MAX_SLICE_SHIFT 6
-#define MAX_SLICE_SIZE 64
-#define MAX_SLICES_PER_WORKER 8
-
-/**
- * Determine the number and size of slices.
- */
-function ComputeSlicesInfo(length) {
-  var count = length >>> MAX_SLICE_SHIFT;
-  var numWorkers = ForkJoinNumWorkers();
-  if (count < numWorkers)
-    count = numWorkers;
-  else if (count >= numWorkers * MAX_SLICES_PER_WORKER)
-    count = numWorkers * MAX_SLICES_PER_WORKER;
-
-  // Round the slice size to be a power of 2.
-  var shift = std_Math_max(std_Math_log2(length / count) | 0, 1);
-
-  // Recompute count with the rounded size.
-  count = length >>> shift;
-  if (count << shift !== length)
-    count += 1;
-
-  return { shift: shift, statuses: new Uint8Array(count), lastSequentialId: 0 };
-}
-
-/**
- * Macros to help compute the start and end indices of slices based on id. Use
- * with the object returned by ComputeSliceInfo.
- */
-#define SLICE_START(info, id) \
-    (id << info.shift)
-#define SLICE_END(info, start, length) \
-    std_Math_min(start + (1 << info.shift), length)
-#define SLICE_COUNT(info) \
-    info.statuses.length
-
-/**
- * ForkJoinGetSlice acts as identity when we are not in a parallel section, so
- * pass in the next sequential value when we are in sequential mode. The
- * reason for this odd API is because intrinsics *need* to be called during
- * ForkJoin's warmup to fill the TI info.
- */
-#define GET_SLICE(info, id) \
-    ((id = ForkJoinGetSlice(InParallelSection() ? -1 : NextSequentialSliceId(info, -1))) >= 0)
-
-#define SLICE_STATUS_DONE 1
-
-/**
- * Macro to mark a slice as completed in the info object.
- */
-#define MARK_SLICE_DONE(info, id) \
-    UnsafePutElements(info.statuses, id, SLICE_STATUS_DONE)
-
-/**
- * Reset the status array of the slices info object.
- */
-function SlicesInfoClearStatuses(info) {
-  var statuses = info.statuses;
-  var length = statuses.length;
-  for (var i = 0; i < length; i++)
-    UnsafePutElements(statuses, i, 0);
-  info.lastSequentialId = 0;
-}
-
-/**
- * Compute the slice such that all slices before it (but not including it) are
- * completed.
- */
-function NextSequentialSliceId(info, doneMarker) {
-  var statuses = info.statuses;
-  var length = statuses.length;
-  for (var i = info.lastSequentialId; i < length; i++) {
-    if (statuses[i] === SLICE_STATUS_DONE)
-      continue;
-    info.lastSequentialId = i;
-    return i;
-  }
-  return doneMarker == undefined ? length : doneMarker;
-}
-
-/**
- * Determinism-preserving bounds function.
- */
-function ShrinkLeftmost(info) {
-  return function () {
-    return [NextSequentialSliceId(info), SLICE_COUNT(info)]
-  };
-}
-
 /**
  * Creates a new array by applying |func(e, i, self)| for each element |e|
  * with index |i|.
  */
 function ArrayMapPar(func, mode) {
   if (!IsCallable(func))
     ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(0, func));
 
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/Parallel.js
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Shared utility functions for parallel operations in `Array.js`
+// and `TypedObject.js`.
+
+
+/**
+ * Determine the number and size of slices.
+ */
+function ComputeSlicesInfo(length) {
+  var count = length >>> MAX_SLICE_SHIFT;
+  var numWorkers = ForkJoinNumWorkers();
+  if (count < numWorkers)
+    count = numWorkers;
+  else if (count >= numWorkers * MAX_SLICES_PER_WORKER)
+    count = numWorkers * MAX_SLICES_PER_WORKER;
+
+  // Round the slice size to be a power of 2.
+  var shift = std_Math_max(std_Math_log2(length / count) | 0, 1);
+
+  // Recompute count with the rounded size.
+  count = length >>> shift;
+  if (count << shift !== length)
+    count += 1;
+
+  return { shift: shift, statuses: new Uint8Array(count), lastSequentialId: 0 };
+}
+
+/**
+ * Reset the status array of the slices info object.
+ */
+function SlicesInfoClearStatuses(info) {
+  var statuses = info.statuses;
+  var length = statuses.length;
+  for (var i = 0; i < length; i++)
+    UnsafePutElements(statuses, i, 0);
+  info.lastSequentialId = 0;
+}
+
+/**
+ * Compute the slice such that all slices before it (but not including it) are
+ * completed.
+ */
+function NextSequentialSliceId(info, doneMarker) {
+  var statuses = info.statuses;
+  var length = statuses.length;
+  for (var i = info.lastSequentialId; i < length; i++) {
+    if (statuses[i] === SLICE_STATUS_DONE)
+      continue;
+    info.lastSequentialId = i;
+    return i;
+  }
+  return doneMarker == undefined ? length : doneMarker;
+}
+
+/**
+ * Determinism-preserving bounds function.
+ */
+function ShrinkLeftmost(info) {
+  return function () {
+    return [NextSequentialSliceId(info), SLICE_COUNT(info)]
+  };
+}
+
+
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -1421,16 +1421,18 @@ TypedObject::attach(ArrayBufferObject &b
     InitArrayBufferViewDataPointer(this, &buffer, offset);
     setReservedSlot(JS_TYPEDOBJ_SLOT_BYTEOFFSET, Int32Value(offset));
     setReservedSlot(JS_TYPEDOBJ_SLOT_OWNER, ObjectValue(buffer));
 }
 
 void
 TypedObject::attach(TypedObject &typedObj, int32_t offset)
 {
+    JS_ASSERT(typedObj.typedMem() != NULL);
+
     attach(typedObj.owner(), typedObj.offset() + offset);
 }
 
 // Returns a suitable JS_TYPEDOBJ_SLOT_LENGTH value for an instance of
 // the type `type`. `type` must not be an unsized array.
 static int32_t
 TypedObjLengthFromType(TypeDescr &descr)
 {
@@ -1448,18 +1450,19 @@ TypedObjLengthFromType(TypeDescr &descr)
       case TypeDescr::UnsizedArray:
         MOZ_ASSUME_UNREACHABLE("TypedObjLengthFromType() invoked on unsized type");
     }
     MOZ_ASSUME_UNREACHABLE("Invalid kind");
 }
 
 /*static*/ TypedObject *
 TypedObject::createDerived(JSContext *cx, HandleSizedTypeDescr type,
-                          HandleTypedObject typedObj, size_t offset)
+                           HandleTypedObject typedObj, size_t offset)
 {
+    JS_ASSERT(typedObj->typedMem() != NULL);
     JS_ASSERT(offset <= typedObj->size());
     JS_ASSERT(offset + type->size() <= typedObj->size());
 
     int32_t length = TypedObjLengthFromType(*type);
 
     const js::Class *clasp = typedObj->getClass();
     Rooted<TypedObject*> obj(cx);
     obj = createUnattachedWithClass(cx, clasp, type, length);
@@ -2661,16 +2664,38 @@ js::AttachTypedObject(ThreadSafeContext 
     return true;
 }
 
 JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js::AttachTypedObjectJitInfo,
                                       AttachTypedObjectJitInfo,
                                       js::AttachTypedObject);
 
 bool
+js::SetTypedObjectOffset(ThreadSafeContext *, unsigned argc, Value *vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    JS_ASSERT(argc == 2);
+    JS_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>());
+    JS_ASSERT(args[1].isInt32());
+
+    TypedObject &typedObj = args[0].toObject().as<TypedObject>();
+    int32_t offset = args[1].toInt32();
+
+    JS_ASSERT(typedObj.typedMem() != nullptr); // must be attached already
+
+    typedObj.setPrivate(typedObj.owner().dataPointer() + offset);
+    typedObj.setReservedSlot(JS_TYPEDOBJ_SLOT_BYTEOFFSET, Int32Value(offset));
+    return true;
+}
+
+JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js::SetTypedObjectOffsetJitInfo,
+                                      SetTypedObjectJitInfo,
+                                      js::SetTypedObjectOffset);
+
+bool
 js::ObjectIsTypeDescr(ThreadSafeContext *, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     JS_ASSERT(args.length() == 1);
     JS_ASSERT(args[0].isObject());
     args.rval().setBoolean(args[0].toObject().is<TypeDescr>());
     return true;
 }
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -692,16 +692,25 @@ bool NewDerivedTypedObject(JSContext *cx
  *
  * Moves `typedObj` to point at the memory referenced by `newDatum` with
  * the offset `newOffset`.
  */
 bool AttachTypedObject(ThreadSafeContext *cx, unsigned argc, Value *vp);
 extern const JSJitInfo AttachTypedObjectJitInfo;
 
 /*
+ * Usage: SetTypedObjectOffset(typedObj, offset)
+ *
+ * Changes the offset for `typedObj` within its buffer to `offset`.
+ * `typedObj` must already be attached.
+ */
+bool SetTypedObjectOffset(ThreadSafeContext *cx, unsigned argc, Value *vp);
+extern const JSJitInfo SetTypedObjectOffsetJitInfo;
+
+/*
  * Usage: ObjectIsTypeDescr(obj)
  *
  * True if `obj` is a type object.
  */
 bool ObjectIsTypeDescr(ThreadSafeContext *cx, unsigned argc, Value *vp);
 extern const JSJitInfo ObjectIsTypeDescrJitInfo;
 
 /*
--- a/js/src/builtin/TypedObject.js
+++ b/js/src/builtin/TypedObject.js
@@ -442,16 +442,21 @@ TypedObjectPointer.prototype.getX4 = fun
   return undefined;
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // Setting values
 //
 // The methods in this section modify the data pointed at by `this`.
 
+// Convenience function
+function SetTypedObjectValue(descr, typedObj, offset, fromValue) {
+  new TypedObjectPointer(descr, typedObj, offset).set(fromValue);
+}
+
 // Assigns `fromValue` to the memory pointed at by `this`, adapting it
 // to `typeRepr` as needed. This is the most general entry point and
 // works for any type.
 TypedObjectPointer.prototype.set = function(fromValue) {
   assert(TypedObjectIsAttached(this.typedObj), "set() called with unattached typedObj");
 
   // Fast path: `fromValue` is a typed object with same type
   // representation as the destination. In that case, we can just do a
@@ -929,20 +934,33 @@ function TypedArrayMap(a, b) {
   if (!TypeDescrIsArrayType(thisType))
     return ThrowError(JSMSG_TYPEDOBJECT_BAD_ARGS);
 
   // Arguments: [depth], func
   if (typeof a === "number" && typeof b === "function")
     return MapTypedSeqImpl(this, a, thisType, b);
   else if (typeof a === "function")
     return MapTypedSeqImpl(this, 1, thisType, a);
-  else if (typeof a === "number")
+  return ThrowError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+}
+
+// Warning: user exposed!
+function TypedArrayMapPar(a, b) {
+  if (!IsObject(this) || !ObjectIsTypedObject(this))
     return ThrowError(JSMSG_TYPEDOBJECT_BAD_ARGS);
-  else
+  var thisType = TYPEDOBJ_TYPE_DESCR(this);
+  if (!TypeDescrIsArrayType(thisType))
     return ThrowError(JSMSG_TYPEDOBJECT_BAD_ARGS);
+
+  // Arguments: [depth], func
+  if (typeof a === "number" && typeof b === "function")
+    return MapTypedParImpl(this, a, thisType, b);
+  else if (typeof a === "function")
+    return MapTypedParImpl(this, 1, thisType, a);
+  return ThrowError(JSMSG_TYPEDOBJECT_BAD_ARGS);
 }
 
 // Warning: user exposed!
 function TypedArrayReduce(a, b) {
   // Arguments: func, [initial]
   if (!IsObject(this) || !ObjectIsTypedObject(this))
     return ThrowError(JSMSG_TYPEDOBJECT_BAD_ARGS);
   var thisType = TYPEDOBJ_TYPE_DESCR(this);
@@ -997,21 +1015,16 @@ function TypedObjectArrayTypeBuildPar(a,
 }
 
 // Warning: user exposed!
 function TypedObjectArrayTypeFromPar(a,b,c) {
   return callFunction(TypedObjectArrayTypeFrom, this, a, b, c);
 }
 
 // Warning: user exposed!
-function TypedArrayMapPar(a, b) {
-  return callFunction(TypedArrayMap, this, a, b);
-}
-
-// Warning: user exposed!
 function TypedArrayReducePar(a, b) {
   return callFunction(TypedArrayReduce, this, a, b);
 }
 
 // Warning: user exposed!
 function TypedArrayScatterPar(a, b, c, d) {
   return callFunction(TypedArrayScatter, this, a, b, c, d);
 }
@@ -1031,16 +1044,22 @@ function SET_BIT(data, index) {
   data[word] |= mask;
 }
 function GET_BIT(data, index) {
   var word = index >> 3;
   var mask = 1 << (index & 0x7);
   return (data[word] & mask) != 0;
 }
 
+function TypeDescrIsUnsizedArrayType(t) {
+  assert(IsObject(t) && ObjectIsTypeDescr(t),
+         "TypeDescrIsArrayType called on non-type-object");
+  return DESCR_KIND(t) === JS_TYPEREPR_UNSIZED_ARRAY_KIND;
+}
+
 function TypeDescrIsArrayType(t) {
   assert(IsObject(t) && ObjectIsTypeDescr(t), "TypeDescrIsArrayType called on non-type-object");
 
   var kind = DESCR_KIND(t);
   switch (kind) {
   case JS_TYPEREPR_SIZED_ARRAY_KIND:
   case JS_TYPEREPR_UNSIZED_ARRAY_KIND:
     return true;
@@ -1068,17 +1087,18 @@ function TypeDescrIsSizedArrayType(t) {
   case JS_TYPEREPR_STRUCT_KIND:
     return false;
   default:
     return ThrowError(JSMSG_TYPEDOBJECT_BAD_ARGS);
   }
 }
 
 function TypeDescrIsSimpleType(t) {
-  assert(IsObject(t) && ObjectIsTypeDescr(t), "TypeDescrIsSimpleType called on non-type-object");
+  assert(IsObject(t) && ObjectIsTypeDescr(t),
+         "TypeDescrIsSimpleType called on non-type-object");
 
   var kind = DESCR_KIND(t);
   switch (kind) {
   case JS_TYPEREPR_SCALAR_KIND:
   case JS_TYPEREPR_REFERENCE_KIND:
   case JS_TYPEREPR_X4_KIND:
     return true;
   case JS_TYPEREPR_SIZED_ARRAY_KIND:
@@ -1321,16 +1341,193 @@ function MapTypedSeqImpl(inArray, depth,
   if  (depth == 1) {
     return DoMapTypedSeqDepth1();
   } else {
     return DoMapTypedSeqDepthN();
   }
 
 }
 
+// Implements |map| and |from| methods for typed |inArray|.
+function MapTypedParImpl(inArray, depth, outputType, func) {
+  assert(IsObject(outputType) && ObjectIsTypeDescr(outputType),
+         "Map/From called on non-type-object outputType");
+  assert(IsObject(inArray) && ObjectIsTypedObject(inArray),
+         "Map/From called on non-object or untyped input array.");
+  assert(TypeDescrIsArrayType(outputType),
+         "Map/From called on non array-type outputType");
+
+  var inArrayType = TypeOfTypedObject(inArray);
+
+  if (ShouldForceSequential() ||
+      depth <= 0 ||
+      TO_INT32(depth) !== depth ||
+      !TypeDescrIsArrayType(inArrayType) ||
+      !TypeDescrIsUnsizedArrayType(outputType))
+  {
+    // defer error cases to seq implementation:
+    return MapTypedSeqImpl(inArray, depth, outputType, func);
+  }
+
+  switch (depth) {
+  case 1:
+    return MapTypedParImplDepth1(inArray, inArrayType, outputType, func);
+  default:
+    return MapTypedSeqImpl(inArray, depth, outputType, func);
+  }
+}
+
+function RedirectPointer(typedObj, offset, outputIsScalar) {
+  if (!outputIsScalar || !InParallelSection()) {
+    // ^ Subtle note: always check InParallelSection() last, because
+    // otherwise the other if conditions will not execute during
+    // sequential mode and we will not gather enough type
+    // information.
+
+    // Here `typedObj` represents the input or output pointer we will
+    // pass to the user function. Ideally, we will just update the
+    // offset of `typedObj` in place so that it moves along the
+    // input/output buffer without incurring any allocation costs. But
+    // we can only do this if these changes are invisible to the user.
+    //
+    // Under normal uses, such changes *should* be invisible -- the
+    // in/out pointers are only intended to be used during the
+    // callback and then discarded, but of course in the general case
+    // nothing prevents them from escaping.
+    //
+    // However, if we are in parallel mode, we know that the pointers
+    // will not escape into global state. They could still escape by
+    // being returned into the resulting array, but even that avenue
+    // is impossible if the result array cannot contain objects.
+    //
+    // Therefore, we reuse a pointer if we are both in parallel mode
+    // and we have a transparent output type.  It'd be nice to loosen
+    // this condition later by using fancy ion optimizations that
+    // assume the value won't escape and copy it if it does. But those
+    // don't exist yet. Moreover, checking if the type is transparent
+    // is an overapproximation: users can manually declare opaque
+    // types that nonetheless only contain scalar data.
+
+    typedObj = NewDerivedTypedObject(TYPEDOBJ_TYPE_DESCR(typedObj),
+                                     typedObj, 0);
+  }
+
+  SetTypedObjectOffset(typedObj, offset);
+  return typedObj;
+}
+SetScriptHints(RedirectPointer,         { inline: true });
+
+function MapTypedParImplDepth1(inArray, inArrayType, outArrayType, func) {
+  assert(IsObject(inArrayType) && ObjectIsTypeDescr(inArrayType) &&
+         TypeDescrIsArrayType(inArrayType),
+         "DoMapTypedParDepth1: invalid inArrayType");
+  assert(IsObject(outArrayType) && ObjectIsTypeDescr(outArrayType) &&
+         TypeDescrIsArrayType(outArrayType),
+         "DoMapTypedParDepth1: invalid outArrayType");
+  assert(IsObject(inArray) && ObjectIsTypedObject(inArray),
+         "DoMapTypedParDepth1: invalid inArray");
+
+  // Determine the grain types of the input and output.
+  const inGrainType = inArrayType.elementType;
+  const outGrainType = outArrayType.elementType;
+  const inGrainTypeSize = DESCR_SIZE(inGrainType);
+  const outGrainTypeSize = DESCR_SIZE(outGrainType);
+  const inGrainTypeIsComplex = !TypeDescrIsSimpleType(inGrainType);
+  const outGrainTypeIsComplex = !TypeDescrIsSimpleType(outGrainType);
+
+  const length = inArray.length;
+  const mode = undefined;
+
+  const outArray = new outArrayType(length);
+
+  const outGrainTypeIsTransparent = ObjectIsTransparentTypedObject(outArray);
+
+  // Construct the slices and initial pointers for each worker:
+  const slicesInfo = ComputeSlicesInfo(length);
+  const numWorkers = ForkJoinNumWorkers();
+  assert(numWorkers > 0, "Should have at least the main thread");
+  const pointers = [];
+  for (var i = 0; i < numWorkers; i++) {
+    const inPointer = new TypedObjectPointer(inGrainType, inArray, 0);
+    const inTypedObject = inPointer.getDerivedIf(inGrainTypeIsComplex);
+    const outPointer = new TypedObjectPointer(outGrainType, outArray, 0);
+    const outTypedObject = outPointer.getOpaqueIf(outGrainTypeIsComplex);
+    ARRAY_PUSH(pointers, ({ inTypedObject: inTypedObject,
+                            outTypedObject: outTypedObject }));
+  }
+
+  // Below we will be adjusting offsets within the input to point at
+  // successive entries; we'll need to know the offset of inArray
+  // relative to its owner (which is often but not always 0).
+  const inBaseOffset = TYPEDOBJ_BYTEOFFSET(inArray);
+
+  ForkJoin(mapThread, ShrinkLeftmost(slicesInfo), ForkJoinMode(mode));
+  return outArray;
+
+  function mapThread(workerId, warmup) {
+    assert(TO_INT32(workerId) === workerId,
+           "workerId not int: " + workerId);
+    assert(workerId >= 0 && workerId < pointers.length,
+          "workerId too large: " + workerId + " >= " + pointers.length);
+    assert(!!pointers[workerId],
+          "no pointer data for workerId: " + workerId);
+
+    var sliceId;
+    const { inTypedObject, outTypedObject } = pointers[workerId];
+
+    while (GET_SLICE(slicesInfo, sliceId)) {
+      const indexStart = SLICE_START(slicesInfo, sliceId);
+      const indexEnd = SLICE_END(slicesInfo, indexStart, length);
+
+      var inOffset = inBaseOffset + std_Math_imul(inGrainTypeSize, indexStart);
+      var outOffset = std_Math_imul(outGrainTypeSize, indexStart);
+
+      // Set the target region so that user is only permitted to write
+      // within the range set aside for this slice. This prevents user
+      // from writing to typed objects that escaped from prior slices
+      // during sequential iteration. Note that, for any particular
+      // iteration of the loop below, it's only valid to write to the
+      // memory range corresponding to the index `i` -- however, since
+      // the different iterations cannot communicate typed object
+      // pointers to one another during parallel exec, we need only
+      // fear escaped typed objects from *other* slices, so we can
+      // just set the target region once.
+      const endOffset = std_Math_imul(outGrainTypeSize, indexEnd);
+      SetForkJoinTargetRegion(outArray, outOffset, endOffset);
+
+      for (var i = indexStart; i < indexEnd; i++) {
+        var inVal = (inGrainTypeIsComplex
+                     ? RedirectPointer(inTypedObject, inOffset,
+                                       outGrainTypeIsTransparent)
+                     : inArray[i]);
+        var outVal = (outGrainTypeIsComplex
+                      ? RedirectPointer(outTypedObject, outOffset,
+                                        outGrainTypeIsTransparent)
+                      : undefined);
+        const r = func(inVal, i, inArray, outVal);
+        if (r !== undefined) {
+          if (outGrainTypeIsComplex)
+            SetTypedObjectValue(outGrainType, outArray, outOffset, r);
+          else
+            outArray[i] = r;
+        }
+        inOffset += inGrainTypeSize;
+        outOffset += outGrainTypeSize;
+      }
+
+      MARK_SLICE_DONE(slicesInfo, sliceId);
+      if (warmup)
+        return;
+    }
+  }
+
+  return undefined;
+}
+SetScriptHints(MapTypedParImplDepth1,         { cloneAtCallsite: true });
+
 function ReduceTypedSeqImpl(array, outputType, func, initial) {
   assert(IsObject(array) && ObjectIsTypedObject(array), "Reduce called on non-object or untyped input array.");
   assert(IsObject(outputType) && ObjectIsTypeDescr(outputType), "Reduce called on non-type-object outputType");
 
   var start, value;
 
   if (initial === undefined && array.length < 1)
     // RangeError("reduce requires array of length > 0")
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -85,16 +85,74 @@ var std_Map_has = Map.prototype.has;
 var std_Set_has = Set.prototype.has;
 var std_iterator = '@@iterator'; // FIXME: Change to be a symbol.
 var std_StopIteration = StopIteration;
 var std_Map_iterator = Map.prototype[std_iterator];
 var std_Set_iterator = Set.prototype[std_iterator];
 var std_Map_iterator_next = Object.getPrototypeOf(Map()[std_iterator]()).next;
 var std_Set_iterator_next = Object.getPrototypeOf(Set()[std_iterator]()).next;
 
+/* Safe versions of ARRAY.push(ELEMENT) */
+#define ARRAY_PUSH(ARRAY, ELEMENT) \
+  callFunction(std_Array_push, ARRAY, ELEMENT);
+#define ARRAY_SLICE(ARRAY, ELEMENT) \
+  callFunction(std_Array_slice, ARRAY, ELEMENT);
+
+/********** Parallel JavaScript macros and so on **********/
+
+#ifdef ENABLE_PARALLEL_JS
+
+/* The mode asserts options object. */
+#define TRY_PARALLEL(MODE) \
+  ((!MODE || MODE.mode !== "seq"))
+#define ASSERT_SEQUENTIAL_IS_OK(MODE) \
+  do { if (MODE) AssertSequentialIsOK(MODE) } while(false)
+
+/**
+ * The ParallelSpew intrinsic is only defined in debug mode, so define a dummy
+ * if debug is not on.
+ */
+#ifndef DEBUG
+#define ParallelSpew(args)
+#endif
+
+#define MAX_SLICE_SHIFT 6
+#define MAX_SLICE_SIZE 64
+#define MAX_SLICES_PER_WORKER 8
+
+/**
+ * Macros to help compute the start and end indices of slices based on id. Use
+ * with the object returned by ComputeSliceInfo.
+ */
+#define SLICE_START(info, id) \
+    (id << info.shift)
+#define SLICE_END(info, start, length) \
+    std_Math_min(start + (1 << info.shift), length)
+#define SLICE_COUNT(info) \
+    info.statuses.length
+
+/**
+ * ForkJoinGetSlice acts as identity when we are not in a parallel section, so
+ * pass in the next sequential value when we are in sequential mode. The
+ * reason for this odd API is because intrinsics *need* to be called during
+ * ForkJoin's warmup to fill the TI info.
+ */
+#define GET_SLICE(info, id) \
+    ((id = ForkJoinGetSlice(InParallelSection() ? -1 : NextSequentialSliceId(info, -1))) >= 0)
+
+#define SLICE_STATUS_DONE 1
+
+/**
+ * Macro to mark a slice as completed in the info object.
+ */
+#define MARK_SLICE_DONE(info, id) \
+    UnsafePutElements(info.statuses, id, SLICE_STATUS_DONE)
+
+#endif // ENABLE_PARALLEL_JS
+
 /********** List specification type **********/
 
 
 /* Spec: ECMAScript Language Specification, 5.1 edition, 8.8 */
 function List() {}
 {
   let ListProto = std_Object_create(null);
   ListProto.indexOf = std_Array_indexOf;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/parallel/TypedObj-mapPar-outpointer-struct.js
@@ -0,0 +1,32 @@
+// Test basic mapPar parallel execution using an out
+// pointer to generate a struct return.
+
+if (!this.hasOwnProperty("TypedObject"))
+  quit();
+
+load(libdir + "parallelarray-helpers.js")
+
+var { ArrayType, StructType, uint32 } = TypedObject;
+
+function test() {
+  var L = minItemsTestingThreshold;
+  var Point = new StructType({x: uint32, y: uint32});
+  var Points = Point.array();
+  var points = new Points(L);
+  for (var i = 0; i < L; i++)
+    points[i].x = i;
+
+  assertParallelExecSucceeds(
+    function() points.mapPar(function(p, i, c, out) { out.y = p.x; }),
+    function(points2) {
+      for (var i = 0; i < L; i++) {
+        assertEq(points[i].x, i);
+        assertEq(points[i].y, 0);
+        assertEq(points2[i].x, 0);
+        assertEq(points2[i].y, i);
+      }
+    });
+}
+
+test();
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/parallel/TypedObj-mapPar-return-scalar.js
@@ -0,0 +1,23 @@
+// Test basic mapPar parallel execution.
+
+if (!this.hasOwnProperty("TypedObject"))
+  quit();
+
+load(libdir + "parallelarray-helpers.js")
+
+var { ArrayType, StructType, uint32 } = TypedObject;
+
+function test() {
+  var L = minItemsTestingThreshold;
+  var Uints = uint32.array(L);
+  var uints1 = new Uints();
+  assertParallelExecSucceeds(
+    function() uints1.mapPar(function(e) e + 1),
+    function(uints2) {
+      for (var i = 0; i < L; i++)
+        assertEq(uints1[i] + 1, uints2[i]);
+    });
+}
+
+test();
+
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -687,16 +687,19 @@ static const JSFunctionSpec intrinsic_fu
           js::NewOpaqueTypedObject,
           1, 0),
     JS_FN("NewDerivedTypedObject",
           js::NewDerivedTypedObject,
           3, 0),
     JS_FNINFO("AttachTypedObject",
               JSNativeThreadSafeWrapper<js::AttachTypedObject>,
               &js::AttachTypedObjectJitInfo, 5, 0),
+    JS_FNINFO("SetTypedObjectOffset",
+              JSNativeThreadSafeWrapper<js::SetTypedObjectOffset>,
+              &js::SetTypedObjectOffsetJitInfo, 2, 0),
     JS_FNINFO("ObjectIsTypeDescr",
               JSNativeThreadSafeWrapper<js::ObjectIsTypeDescr>,
               &js::ObjectIsTypeDescrJitInfo, 5, 0),
     JS_FNINFO("ObjectIsTransparentTypedObject",
               JSNativeThreadSafeWrapper<js::ObjectIsTransparentTypedObject>,
               &js::ObjectIsTransparentTypedObjectJitInfo, 5, 0),
     JS_FNINFO("TypedObjectIsAttached",
               JSNativeThreadSafeWrapper<js::TypedObjectIsAttached>,