Bug 933317 - Improve write guard to consider out pointers r=shu
authorNicholas D. Matsakis <nmatsakis@mozilla.com>
Tue, 07 Jan 2014 21:35:32 -0500
changeset 163445 d633e3ff201329c99a71bfc1311edc28a061cb99
parent 163444 0e1092eaa8671ece696c7b910bb812f4e6d18e21
child 163446 eb70f67d384a2de272246337052f7225ee84ed4e
push id38478
push usernmatsakis@mozilla.com
push dateWed, 15 Jan 2014 03:06:26 +0000
treeherdermozilla-inbound@d633e3ff2013 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersshu
bugs933317
milestone29.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 933317 - Improve write guard to consider out pointers r=shu
js/src/builtin/TypedObject.cpp
js/src/builtin/TypedObject.h
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/LIR-Common.h
js/src/jit/LOpcodes.h
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jit/ParallelFunctions.cpp
js/src/jit/ParallelFunctions.h
js/src/jit/ParallelSafetyAnalysis.cpp
js/src/vm/ForkJoin.cpp
js/src/vm/ForkJoin.h
js/src/vm/ObjectImpl.h
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -1470,24 +1470,24 @@ TypedDatum::createUnattachedWithClass(JS
                 return nullptr;
             }
         }
     }
 
     return static_cast<TypedDatum*>(&*obj);
 }
 
-/*static*/ void
+void
 TypedDatum::attach(uint8_t *memory)
 {
     setPrivate(memory);
     setReservedSlot(JS_DATUM_SLOT_OWNER, ObjectValue(*this));
 }
 
-/*static*/ void
+void
 TypedDatum::attach(JSObject &datum, uint32_t offset)
 {
     JS_ASSERT(IsTypedDatum(datum));
     JS_ASSERT(datum.getReservedSlot(JS_DATUM_SLOT_OWNER).isObject());
 
     // find the location in memory
     uint8_t *mem = TypedMem(datum) + offset;
 
@@ -2239,16 +2239,25 @@ TypedDatum::datumTypeRepresentation() co
 }
 
 uint8_t *
 TypedDatum::typedMem() const
 {
     return TypedMem(*this);
 }
 
+TypedDatum *
+TypedDatum::owner() const
+{
+    JSObject *owner = getReservedSlot(JS_DATUM_SLOT_OWNER).toObjectOrNull();
+    if (!owner)
+        return nullptr;
+    return &AsTypedDatum(*owner);
+}
+
 /******************************************************************************
  * Typed Objects
  */
 
 const Class TypedObject::class_ = {
     "TypedObject",
     Class::NON_NATIVE |
     JSCLASS_HAS_RESERVED_SLOTS(JS_DATUM_SLOTS) |
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -395,16 +395,17 @@ class TypedDatum : public JSObject
     // If `this` is the owner of the memory, use this.
     void attach(uint8_t *mem);
 
     // Otherwise, use this to attach to memory referenced by another datum.
     void attach(JSObject &datum, uint32_t offset);
 
     TypeRepresentation *datumTypeRepresentation() const;
     uint8_t *typedMem() const;
+    TypedDatum *owner() const;
 };
 
 class TypedObject : public TypedDatum
 {
   public:
     static const Class class_;
 
     // Creates a new typed object whose memory is freshly allocated
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -1491,25 +1491,25 @@ CodeGenerator::visitForkJoinSlice(LForkJ
 
     masm.setupUnalignedABICall(0, tempReg);
     masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ForkJoinSlicePar));
     JS_ASSERT(ToRegister(lir->output()) == ReturnReg);
     return true;
 }
 
 bool
-CodeGenerator::visitGuardThreadLocalObject(LGuardThreadLocalObject *lir)
+CodeGenerator::visitGuardThreadExclusive(LGuardThreadExclusive *lir)
 {
     JS_ASSERT(gen->info().executionMode() == ParallelExecution);
 
     const Register tempReg = ToRegister(lir->getTempReg());
     masm.setupUnalignedABICall(2, tempReg);
     masm.passABIArg(ToRegister(lir->forkJoinSlice()));
     masm.passABIArg(ToRegister(lir->object()));
-    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, IsThreadLocalObject));
+    masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, ParallelWriteGuard));
 
     OutOfLineAbortPar *bail = oolAbortPar(ParallelBailoutIllegalWrite, lir);
     if (!bail)
         return false;
 
     // branch to the OOL failure code if false is returned
     masm.branchIfFalseBool(ReturnReg, bail->entry());
     return true;
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -202,17 +202,17 @@ class CodeGenerator : public CodeGenerat
     bool emitConcat(LInstruction *lir, Register lhs, Register rhs, Register output);
     bool visitConcat(LConcat *lir);
     bool visitConcatPar(LConcatPar *lir);
     bool visitCharCodeAt(LCharCodeAt *lir);
     bool visitFromCharCode(LFromCharCode *lir);
     bool visitStringSplit(LStringSplit *lir);
     bool visitFunctionEnvironment(LFunctionEnvironment *lir);
     bool visitForkJoinSlice(LForkJoinSlice *lir);
-    bool visitGuardThreadLocalObject(LGuardThreadLocalObject *lir);
+    bool visitGuardThreadExclusive(LGuardThreadExclusive *lir);
     bool visitCallGetProperty(LCallGetProperty *lir);
     bool visitCallGetElement(LCallGetElement *lir);
     bool visitCallSetElement(LCallSetElement *lir);
     bool visitCallInitElementArray(LCallInitElementArray *lir);
     bool visitThrow(LThrow *lir);
     bool visitTypeOfV(LTypeOfV *lir);
     bool visitOutOfLineTypeOfV(OutOfLineTypeOfV *ool);
     bool visitToIdV(LToIdV *lir);
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -5139,22 +5139,22 @@ class LRestPar : public LCallInstruction
     const LAllocation *numActuals() {
         return getOperand(1);
     }
     MRestPar *mir() const {
         return mir_->toRestPar();
     }
 };
 
-class LGuardThreadLocalObject : public LCallInstructionHelper<0, 2, 1>
-{
-  public:
-    LIR_HEADER(GuardThreadLocalObject);
-
-    LGuardThreadLocalObject(const LAllocation &slice, const LAllocation &object, const LDefinition &temp1) {
+class LGuardThreadExclusive : public LCallInstructionHelper<0, 2, 1>
+{
+  public:
+    LIR_HEADER(GuardThreadExclusive);
+
+    LGuardThreadExclusive(const LAllocation &slice, const LAllocation &object, const LDefinition &temp1) {
         setOperand(0, slice);
         setOperand(1, object);
         setTemp(0, temp1);
     }
 
     const LAllocation *forkJoinSlice() {
         return getOperand(0);
     }
--- a/js/src/jit/LOpcodes.h
+++ b/js/src/jit/LOpcodes.h
@@ -163,17 +163,17 @@
     _(LoadSlotV)                    \
     _(LoadSlotT)                    \
     _(StoreSlotV)                   \
     _(StoreSlotT)                   \
     _(GuardShape)                   \
     _(GuardObjectType)              \
     _(GuardObjectIdentity)          \
     _(GuardClass)                   \
-    _(GuardThreadLocalObject)       \
+    _(GuardThreadExclusive)         \
     _(TypeBarrierV)                 \
     _(TypeBarrierO)                 \
     _(MonitorTypes)                 \
     _(PostWriteBarrierO)            \
     _(PostWriteBarrierV)            \
     _(PostWriteBarrierAllSlots)     \
     _(InitializedLength)            \
     _(SetInitializedLength)         \
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2050,22 +2050,26 @@ LIRGenerator::visitFunctionEnvironment(M
 bool
 LIRGenerator::visitForkJoinSlice(MForkJoinSlice *ins)
 {
     LForkJoinSlice *lir = new(alloc()) LForkJoinSlice(tempFixed(CallTempReg0));
     return defineReturn(lir, ins);
 }
 
 bool
-LIRGenerator::visitGuardThreadLocalObject(MGuardThreadLocalObject *ins)
+LIRGenerator::visitGuardThreadExclusive(MGuardThreadExclusive *ins)
 {
-    LGuardThreadLocalObject *lir =
-        new(alloc()) LGuardThreadLocalObject(useFixed(ins->forkJoinSlice(), CallTempReg0),
-                                             useFixed(ins->object(), CallTempReg1),
-                                             tempFixed(CallTempReg2));
+    // FIXME (Bug 956281) -- For now, we always generate the most
+    // general form of write guard check. we could employ TI feedback
+    // to optimize this if we know that the object being tested is a
+    // typed object or know that it is definitely NOT a typed object.
+    LGuardThreadExclusive *lir =
+        new(alloc()) LGuardThreadExclusive(useFixed(ins->forkJoinSlice(), CallTempReg0),
+                                           useFixed(ins->object(), CallTempReg1),
+                                           tempFixed(CallTempReg2));
     lir->setMir(ins);
     return add(lir, ins);
 }
 
 bool
 LIRGenerator::visitInterruptCheck(MInterruptCheck *ins)
 {
     // Implicit interrupt checks require asm.js signal handlers to be
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -154,17 +154,17 @@ class LIRGenerator : public LIRGenerator
     bool visitSlots(MSlots *ins);
     bool visitElements(MElements *ins);
     bool visitConstantElements(MConstantElements *ins);
     bool visitConvertElementsToDoubles(MConvertElementsToDoubles *ins);
     bool visitMaybeToDoubleElement(MMaybeToDoubleElement *ins);
     bool visitLoadSlot(MLoadSlot *ins);
     bool visitFunctionEnvironment(MFunctionEnvironment *ins);
     bool visitForkJoinSlice(MForkJoinSlice *ins);
-    bool visitGuardThreadLocalObject(MGuardThreadLocalObject *ins);
+    bool visitGuardThreadExclusive(MGuardThreadExclusive *ins);
     bool visitInterruptCheck(MInterruptCheck *ins);
     bool visitCheckInterruptPar(MCheckInterruptPar *ins);
     bool visitStoreSlot(MStoreSlot *ins);
     bool visitTypeBarrier(MTypeBarrier *ins);
     bool visitMonitorTypes(MMonitorTypes *ins);
     bool visitPostWriteBarrier(MPostWriteBarrier *ins);
     bool visitArrayLength(MArrayLength *ins);
     bool visitSetArrayLength(MSetArrayLength *ins);
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -8574,34 +8574,34 @@ class MRestPar
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     bool possiblyCalls() const {
         return true;
     }
 };
 
-// Guard on an object being allocated in the current slice.
-class MGuardThreadLocalObject
+// Guard on an object being safe for writes by current parallel slice.
+// Must be either thread-local or else a handle into the destination array.
+class MGuardThreadExclusive
   : public MBinaryInstruction,
     public ObjectPolicy<1>
 {
-    MGuardThreadLocalObject(MDefinition *slice, MDefinition *obj)
+    MGuardThreadExclusive(MDefinition *slice, MDefinition *obj)
       : MBinaryInstruction(slice, obj)
     {
         setResultType(MIRType_None);
         setGuard();
-        setMovable();
-    }
-
-  public:
-    INSTRUCTION_HEADER(GuardThreadLocalObject);
-
-    static MGuardThreadLocalObject *New(TempAllocator &alloc, MDefinition *slice, MDefinition *obj) {
-        return new(alloc) MGuardThreadLocalObject(slice, obj);
+    }
+
+  public:
+    INSTRUCTION_HEADER(GuardThreadExclusive);
+
+    static MGuardThreadExclusive *New(TempAllocator &alloc, MDefinition *slice, MDefinition *obj) {
+        return new(alloc) MGuardThreadExclusive(slice, obj);
     }
     MDefinition *forkJoinSlice() const {
         return getOperand(0);
     }
     MDefinition *object() const {
         return getOperand(1);
     }
     BailoutKind bailoutKind() const {
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -204,17 +204,17 @@ namespace jit {
     _(NewCallObjectPar)                                                     \
     _(NewPar)                                                               \
     _(NewDenseArrayPar)                                                     \
     _(NewDerivedTypedObject)                                                \
     _(AbortPar)                                                             \
     _(LambdaPar)                                                            \
     _(RestPar)                                                              \
     _(ForkJoinSlice)                                                        \
-    _(GuardThreadLocalObject)                                               \
+    _(GuardThreadExclusive)                                                 \
     _(CheckInterruptPar)                                                    \
     _(RecompileCheck)
 
 // Forward declarations of MIR types.
 #define FORWARD_DECLARE(op) class M##op;
  MIR_OPCODE_LIST(FORWARD_DECLARE)
 #undef FORWARD_DECLARE
 
--- a/js/src/jit/ParallelFunctions.cpp
+++ b/js/src/jit/ParallelFunctions.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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/. */
 
 #include "jit/ParallelFunctions.h"
 
+#include "builtin/TypedObject.h"
 #include "vm/ArrayObject.h"
 
 #include "jsgcinlines.h"
 #include "jsobjinlines.h"
 
 using namespace js;
 using namespace jit;
 
@@ -32,25 +33,96 @@ jit::ForkJoinSlicePar()
 JSObject *
 jit::NewGCThingPar(ForkJoinSlice *slice, gc::AllocKind allocKind)
 {
     JS_ASSERT(ForkJoinSlice::current() == slice);
     uint32_t thingSize = (uint32_t)gc::Arena::thingSize(allocKind);
     return gc::NewGCThing<JSObject, NoGC>(slice, allocKind, thingSize, gc::DefaultHeap);
 }
 
-// Check that the object was created by the current thread
-// (and hence is writable).
 bool
-jit::IsThreadLocalObject(ForkJoinSlice *slice, JSObject *object)
+jit::ParallelWriteGuard(ForkJoinSlice *slice, JSObject *object)
 {
+    // Implements the most general form of the write guard, which is
+    // suitable for writes to any object O. There are two cases to
+    // consider and test for:
+    //
+    // 1. Writes to thread-local memory are safe. Thread-local memory
+    //    is defined as memory allocated by the current thread.
+    //    The definition of the PJS API guarantees that such memory
+    //    cannot have escaped to other parallel threads.
+    //
+    // 2. Writes into the output buffer are safe. Some PJS operations
+    //    supply an out pointer into the final target buffer. The design
+    //    of the API ensures that this out pointer is always pointing
+    //    at a fresh region of the buffer that is not accessible to
+    //    other threads. Thus, even though this output buffer has not
+    //    been created by the current thread, it is writable.
+    //
+    // There are some subtleties to consider:
+    //
+    // A. Typed objects and typed arrays are just views onto a base buffer.
+    //    For the purposes of guarding parallel writes, it is not important
+    //    whether the *view* is thread-local -- what matters is whether
+    //    the *underlying buffer* is thread-local.
+    //
+    // B. With regard to the output buffer, we have to be careful
+    //    because of the potential for sequential iterations to be
+    //    intermingled with parallel ones. During a sequential
+    //    iteration, the out pointer could escape into global
+    //    variables and so forth, and thus be used during later
+    //    parallel operations. However, those out pointers must be
+    //    pointing to distinct regions of the final output buffer than
+    //    the ones that are currently being written, so there is no
+    //    harm done in letting them be read (but not written).
+    //
+    //    In order to be able to distinguish escaped out pointers from
+    //    prior iterations and the proper out pointers from the
+    //    current iteration, we always track a *target memory region*
+    //    (which is a span of bytes within the output buffer) and not
+    //    just the output buffer itself.
+
     JS_ASSERT(ForkJoinSlice::current() == slice);
+
+    if (IsTypedDatum(*object)) {
+        TypedDatum &datum = AsTypedDatum(*object);
+
+        // Note: check target region based on `datum`, not the owner.
+        // This is because `datum` may point to some subregion of the
+        // owner and we only care if that *subregion* is within the
+        // target region, not the entire owner.
+        if (IsInTargetRegion(slice, &datum))
+            return true;
+
+        // Also check whether owner is thread-local.
+        TypedDatum *owner = datum.owner();
+        return owner && slice->isThreadLocal(owner);
+    }
+
+    // For other kinds of writable objects, must be thread-local.
     return slice->isThreadLocal(object);
 }
 
+// Check that |object| (which must be a typed datum) maps
+// to memory in the target region.
+//
+// For efficiency, we assume that all handles which the user has
+// access to are either entirely within the target region or entirely
+// without, but not straddling the target region nor encompassing
+// it. This invariant is maintained by the PJS APIs, where the target
+// region and handles are always elements of the same output array.
+bool
+jit::IsInTargetRegion(ForkJoinSlice *slice, TypedDatum *datum)
+{
+    JS_ASSERT(IsTypedDatum(*datum)); // in case JIT supplies something bogus
+    uint8_t *typedMem = datum->typedMem();
+    return (typedMem >= slice->targetRegionStart &&
+            typedMem <  slice->targetRegionEnd);
+}
+
 #ifdef DEBUG
 static void
 printTrace(const char *prefix, struct IonLIRTraceData *cached)
 {
     fprintf(stderr, "%s / Block %3u / LIR %3u / Mode %u / LIR %s\n",
             prefix,
             cached->blockIndex, cached->lirIndex, cached->execModeInt, cached->lirOpName);
 }
--- a/js/src/jit/ParallelFunctions.h
+++ b/js/src/jit/ParallelFunctions.h
@@ -6,21 +6,25 @@
 
 #ifndef jit_ParallelFunctions_h
 #define jit_ParallelFunctions_h
 
 #include "gc/Heap.h"
 #include "vm/ForkJoin.h"
 
 namespace js {
+
+class TypedDatum; // subclass of JSObject* defined in builtin/TypedObject.h
+
 namespace jit {
 
 ForkJoinSlice *ForkJoinSlicePar();
 JSObject *NewGCThingPar(ForkJoinSlice *slice, gc::AllocKind allocKind);
-bool IsThreadLocalObject(ForkJoinSlice *slice, JSObject *object);
+bool ParallelWriteGuard(ForkJoinSlice *slice, JSObject *object);
+bool IsInTargetRegion(ForkJoinSlice *slice, TypedDatum *object);
 bool CheckOverRecursedPar(ForkJoinSlice *slice);
 bool CheckInterruptPar(ForkJoinSlice *slice);
 
 // Extends the given array with `length` new holes.  Returns nullptr on
 // failure or else `array`, which is convenient during code
 // generation.
 JSObject *ExtendArrayPar(ForkJoinSlice *slice, JSObject *array, uint32_t length);
 
--- a/js/src/jit/ParallelSafetyAnalysis.cpp
+++ b/js/src/jit/ParallelSafetyAnalysis.cpp
@@ -138,17 +138,17 @@ class ParallelSafetyVisitor : public MIn
     CUSTOM_OP(Call)
     UNSAFE_OP(ApplyArgs)
     UNSAFE_OP(Bail)
     UNSAFE_OP(AssertFloat32)
     UNSAFE_OP(GetDynamicName)
     UNSAFE_OP(FilterArgumentsOrEval)
     UNSAFE_OP(CallDirectEval)
     SAFE_OP(BitNot)
-    UNSAFE_OP(TypeOf)
+    SAFE_OP(TypeOf)
     UNSAFE_OP(ToId)
     SAFE_OP(BitAnd)
     SAFE_OP(BitOr)
     SAFE_OP(BitXor)
     SAFE_OP(Lsh)
     SAFE_OP(Rsh)
     SAFE_OP(Ursh)
     SPECIALIZED_OP(MinMax, PERMIT_NUMERIC)
@@ -282,17 +282,17 @@ class ParallelSafetyVisitor : public MIn
     UNSAFE_OP(RegExpExec)
     UNSAFE_OP(RegExpReplace)
     UNSAFE_OP(CallInstanceOf)
     UNSAFE_OP(FunctionBoundary)
     UNSAFE_OP(GuardString)
     UNSAFE_OP(NewDeclEnvObject)
     UNSAFE_OP(In)
     UNSAFE_OP(InArray)
-    SAFE_OP(GuardThreadLocalObject)
+    SAFE_OP(GuardThreadExclusive)
     SAFE_OP(CheckInterruptPar)
     SAFE_OP(CheckOverRecursedPar)
     SAFE_OP(FunctionDispatch)
     SAFE_OP(TypeObjectDispatch)
     SAFE_OP(IsCallable)
     SAFE_OP(HaveSameClass)
     UNSAFE_OP(EffectiveAddress)
     UNSAFE_OP(AsmJSUnsignedToDouble)
@@ -651,16 +651,20 @@ ParallelSafetyVisitor::insertWriteGuard(
           case MDefinition::Op_Elements:
             object = valueBeingWritten->toElements()->object();
             break;
 
           case MDefinition::Op_TypedArrayElements:
             object = valueBeingWritten->toTypedArrayElements()->object();
             break;
 
+          case MDefinition::Op_TypedObjectElements:
+            object = valueBeingWritten->toTypedObjectElements()->object();
+            break;
+
           default:
             SpewMIR(writeInstruction, "cannot insert write guard for %s",
                     valueBeingWritten->opName());
             return markUnsafe();
         }
         break;
 
       default:
@@ -677,18 +681,18 @@ ParallelSafetyVisitor::insertWriteGuard(
         // MNewPar will always be creating something thread-local, omit the guard
         SpewMIR(writeInstruction, "write to NewPar prop does not require guard");
         return true;
       default:
         break;
     }
 
     MBasicBlock *block = writeInstruction->block();
-    MGuardThreadLocalObject *writeGuard =
-        MGuardThreadLocalObject::New(alloc(), forkJoinSlice(), object);
+    MGuardThreadExclusive *writeGuard =
+        MGuardThreadExclusive::New(alloc(), forkJoinSlice(), object);
     block->insertBefore(writeInstruction, writeGuard);
     writeGuard->adjustInputs(alloc(), writeGuard);
     return true;
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // Calls
 //
--- a/js/src/vm/ForkJoin.cpp
+++ b/js/src/vm/ForkJoin.cpp
@@ -13,16 +13,18 @@
 #include "vm/ForkJoin.h"
 
 #include "mozilla/ThreadLocal.h"
 
 #include "jscntxt.h"
 #include "jslock.h"
 #include "jsprf.h"
 
+#include "builtin/TypedObject.h"
+
 #ifdef JS_THREADSAFE
 # include "jit/BaselineJIT.h"
 # include "vm/Monitor.h"
 #endif
 
 #if defined(DEBUG) && defined(JS_THREADSAFE) && defined(JS_ION)
 # include "jit/Ion.h"
 # include "jit/JitCompartment.h"
@@ -1577,16 +1579,18 @@ ForkJoinShared::requestZoneGC(JS::Zone *
 ForkJoinSlice::ForkJoinSlice(PerThreadData *perThreadData,
                              uint16_t sliceId, uint32_t workerId,
                              Allocator *allocator, ForkJoinShared *shared,
                              ParallelBailoutRecord *bailoutRecord)
   : ThreadSafeContext(shared->runtime(), perThreadData, Context_ForkJoin),
     sliceId(sliceId),
     workerId(workerId),
     bailoutRecord(bailoutRecord),
+    targetRegionStart(nullptr),
+    targetRegionEnd(nullptr),
     shared(shared),
     acquiredContext_(false),
     nogc_(shared->runtime())
 {
     /*
      * Unsafely set the zone. This is used to track malloc counters and to
      * trigger GCs and is otherwise not thread-safe to access.
      */
@@ -2098,9 +2102,51 @@ js::ParallelTestsShouldPass(JSContext *c
 {
     return jit::IsIonEnabled(cx) &&
            jit::IsBaselineEnabled(cx) &&
            !jit::js_JitOptions.eagerCompilation &&
            jit::js_JitOptions.baselineUsesBeforeCompile != 0 &&
            cx->runtime()->gcZeal() == 0;
 }
 
+bool
+js::intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp)
+{
+    // This version of SetForkJoinTargetRegion is called during
+    // sequential execution. It is a no-op. The parallel version
+    // is intrinsic_SetForkJoinTargetRegionPar(), below.
+    return true;
+}
+
+static bool
+intrinsic_SetForkJoinTargetRegionPar(ForkJoinSlice *slice, unsigned argc, Value *vp)
+{
+    // Sets the *target region*, which is the portion of the output
+    // buffer that the current iteration is permitted to write to.
+    //
+    // Note: it is important that the target region should be an
+    // entire element (or several elements) of the output array and
+    // not some region that spans from the middle of one element into
+    // the middle of another. This is because the guarding code
+    // assumes that handles, which never straddle across elements,
+    // will either be contained entirely within the target region or
+    // be contained entirely without of the region, and not straddling
+    // the region nor encompassing it.
+
+    CallArgs args = CallArgsFromVp(argc, vp);
+    JS_ASSERT(argc == 3);
+    JS_ASSERT(args[0].isObject() && IsTypedDatum(args[0].toObject()));
+    JS_ASSERT(args[1].isInt32());
+    JS_ASSERT(args[2].isInt32());
+
+    uint8_t *mem = AsTypedDatum(args[0].toObject()).typedMem();
+    int32_t start = args[1].toInt32();
+    int32_t end = args[2].toInt32();
+
+    slice->targetRegionStart = mem + start;
+    slice->targetRegionEnd = mem + end;
+    return true;
+}
+
+const JSJitInfo js::intrinsic_SetForkJoinTargetRegionInfo =
+    JS_JITINFO_NATIVE_PARALLEL(intrinsic_SetForkJoinTargetRegionPar);
+
 #endif // JS_THREADSAFE && JS_ION
--- a/js/src/vm/ForkJoin.h
+++ b/js/src/vm/ForkJoin.h
@@ -314,16 +314,33 @@ class ForkJoinSlice : public ThreadSafeC
     // Records the last instr. to execute on this thread.
     IonLIRTraceData traceData;
 
     // The maximum worker and slice id.
     uint16_t maxSliceId;
     uint32_t maxWorkerId;
 #endif
 
+    // When we run a par operation like mapPar, we create an out pointer
+    // into a specific region of the destination buffer. Even though the
+    // destination buffer is not thread-local, it is permissible to write into
+    // it via the handles provided. These two fields identify the memory
+    // region where writes are allowed so that the write guards can test for
+    // it.
+    //
+    // Note: we only permit writes into the *specific region* that the user
+    // is supposed to write. Normally, they only have access to this region
+    // anyhow. But due to sequential fallback it is possible for handles into
+    // other regions to escape into global variables in the sequential
+    // execution and then get accessed by later parallel sections. Thus we
+    // must be careful and ensure that the write is going through a handle
+    // into the correct *region* of the buffer.
+    uint8_t *targetRegionStart;
+    uint8_t *targetRegionEnd;
+
     ForkJoinSlice(PerThreadData *perThreadData, uint16_t sliceId, uint32_t workerId,
                   Allocator *allocator, ForkJoinShared *shared,
                   ParallelBailoutRecord *bailoutRecord);
 
     // True if this is the main thread, false if it is one of the parallel workers.
     bool isMainThread() const;
 
     // When the code would normally trigger a GC, we don't trigger it
@@ -428,16 +445,19 @@ class LockedJSContext
     operator JSContext *() { return cx_; }
     JSContext *operator->() { return cx_; }
 };
 
 bool InExclusiveParallelSection();
 
 bool ParallelTestsShouldPass(JSContext *cx);
 
+bool intrinsic_SetForkJoinTargetRegion(JSContext *cx, unsigned argc, Value *vp);
+extern const JSJitInfo intrinsic_SetForkJoinTargetRegionInfo;
+
 ///////////////////////////////////////////////////////////////////////////
 // Debug Spew
 
 namespace jit {
     class MDefinition;
 }
 
 namespace parallel {
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -1602,17 +1602,17 @@ BarrieredCell<ObjectImpl>::writeBarrierP
 }
 
 } // namespace gc
 
 inline void
 ObjectImpl::privateWriteBarrierPre(void **oldval)
 {
 #ifdef JSGC_INCREMENTAL
-    JS::shadow::Zone *shadowZone = this->shadowZone();
+    JS::shadow::Zone *shadowZone = this->shadowZoneFromAnyThread();
     if (shadowZone->needsBarrier()) {
         if (*oldval && getClass()->trace)
             getClass()->trace(shadowZone->barrierTracer(), this->asObjectPtr());
     }
 #endif
 }
 
 inline Value
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -621,16 +621,19 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("NewStringIterator",       intrinsic_NewStringIterator,       0,0),
     JS_FN("IsStringIterator",        intrinsic_IsStringIterator,        1,0),
 
     JS_FN("ForkJoin",                intrinsic_ForkJoin,                2,0),
     JS_FN("ForkJoinNumWorkers",      intrinsic_ForkJoinNumWorkers,      0,0),
     JS_FN("NewDenseArray",           intrinsic_NewDenseArray,           1,0),
     JS_FN("ShouldForceSequential",   intrinsic_ShouldForceSequential,   0,0),
     JS_FN("ParallelTestsShouldPass", intrinsic_ParallelTestsShouldPass, 0,0),
+    JS_FNINFO("SetForkJoinTargetRegion",
+              intrinsic_SetForkJoinTargetRegion,
+              &intrinsic_SetForkJoinTargetRegionInfo, 2, 0),
 
     // See builtin/TypedObject.h for descriptors of the typedobj functions.
     JS_FN("NewTypedHandle",
           js::NewTypedHandle,
           1, 0),
     JS_FN("NewDerivedTypedDatum",
           js::NewDerivedTypedDatum,
           3, 0),