Bug 934450 - Allow objects to have copy on write elements, r=billm,jandem.
authorBrian Hackett <bhackett1024@gmail.com>
Tue, 19 Aug 2014 22:25:37 -0800
changeset 222135 9605a571ca8ad581b903537906f99e0c449199e6
parent 222134 436085b283b4437cd6182d08627ad033554c654c
child 222136 c3c9eea58fa28f5422fcbea7f2fe4e7f9494325d
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-beta@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm, jandem
bugs934450
milestone34.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 934450 - Allow objects to have copy on write elements, r=billm,jandem.
js/public/Value.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/ParseNode.h
js/src/gc/Barrier.h
js/src/gc/ForkJoinNursery.cpp
js/src/gc/Marking.cpp
js/src/gc/Nursery.cpp
js/src/jit-test/tests/debug/Script-getAllColumnOffsets-05.js
js/src/jit-test/tests/parallel/write-array.js
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/jit/BaselineIC.cpp
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/IonAnalysis.cpp
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/IonMacroAssembler.cpp
js/src/jit/LIR-Common.h
js/src/jit/LOpcodes.h
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MCallOptimize.cpp
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jit/ParallelSafetyAnalysis.cpp
js/src/jsarray.cpp
js/src/jsarray.h
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsobjinlines.h
js/src/jsopcode.cpp
js/src/vm/Interpreter.cpp
js/src/vm/ObjectImpl.cpp
js/src/vm/ObjectImpl.h
js/src/vm/Opcodes.h
js/src/vm/Xdr.h
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -1688,17 +1688,17 @@ class ValueOperations
     bool toBoolean() const { return value()->toBoolean(); }
     double toNumber() const { return value()->toNumber(); }
     int32_t toInt32() const { return value()->toInt32(); }
     double toDouble() const { return value()->toDouble(); }
     JSString *toString() const { return value()->toString(); }
     JS::Symbol *toSymbol() const { return value()->toSymbol(); }
     JSObject &toObject() const { return value()->toObject(); }
     JSObject *toObjectOrNull() const { return value()->toObjectOrNull(); }
-    void *toGCThing() const { return value()->toGCThing(); }
+    gc::Cell *toGCThing() const { return value()->toGCThing(); }
     uint64_t asRawBits() const { return value()->asRawBits(); }
 
     JSValueType extractNonDoubleType() const { return value()->extractNonDoubleType(); }
     uint32_t toPrivateUint32() const { return value()->toPrivateUint32(); }
 
     JSWhyMagic whyMagic() const { return value()->whyMagic(); }
     uint32_t magicUint32() const { return value()->magicUint32(); }
 };
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -3851,17 +3851,17 @@ EmitAssignment(ExclusiveContext *cx, Byt
         break;
       default:
         JS_ASSERT(0);
     }
     return true;
 }
 
 bool
-ParseNode::getConstantValue(ExclusiveContext *cx, MutableHandleValue vp)
+ParseNode::getConstantValue(ExclusiveContext *cx, AllowConstantObjects allowObjects, MutableHandleValue vp)
 {
     switch (getKind()) {
       case PNK_NUMBER:
         vp.setNumber(pn_dval);
         return true;
       case PNK_TEMPLATE_STRING:
       case PNK_STRING:
         vp.setString(pn_atom);
@@ -3878,56 +3878,66 @@ ParseNode::getConstantValue(ExclusiveCon
       case PNK_SPREAD:
         return false;
       case PNK_CALLSITEOBJ:
       case PNK_ARRAY: {
         RootedValue value(cx);
         unsigned count;
         ParseNode *pn;
 
+        if (allowObjects == DontAllowObjects)
+            return false;
+        if (allowObjects == DontAllowNestedObjects)
+            allowObjects = DontAllowObjects;
+
         if (getKind() == PNK_CALLSITEOBJ) {
             count = pn_count - 1;
             pn = pn_head->pn_next;
         } else {
             JS_ASSERT(isOp(JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST));
             count = pn_count;
             pn = pn_head;
         }
 
         RootedObject obj(cx, NewDenseAllocatedArray(cx, count, nullptr, MaybeSingletonObject));
         if (!obj)
             return false;
 
         unsigned idx = 0;
         RootedId id(cx);
         for (; pn; idx++, pn = pn->pn_next) {
-            if (!pn->getConstantValue(cx, &value))
+            if (!pn->getConstantValue(cx, allowObjects, &value))
                 return false;
             id = INT_TO_JSID(idx);
             if (!JSObject::defineGeneric(cx, obj, id, value, nullptr, nullptr, JSPROP_ENUMERATE))
                 return false;
         }
         JS_ASSERT(idx == count);
 
         types::FixArrayType(cx, obj);
         vp.setObject(*obj);
         return true;
       }
       case PNK_OBJECT: {
         JS_ASSERT(isOp(JSOP_NEWINIT));
         JS_ASSERT(!(pn_xflags & PNX_NONCONST));
 
+        if (allowObjects == DontAllowObjects)
+            return false;
+        if (allowObjects == DontAllowNestedObjects)
+            allowObjects = DontAllowObjects;
+
         gc::AllocKind kind = GuessObjectGCKind(pn_count);
         RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_, kind, MaybeSingletonObject));
         if (!obj)
             return false;
 
         RootedValue value(cx), idvalue(cx);
         for (ParseNode *pn = pn_head; pn; pn = pn->pn_next) {
-            if (!pn->pn_right->getConstantValue(cx, &value))
+            if (!pn->pn_right->getConstantValue(cx, allowObjects, &value))
                 return false;
 
             ParseNode *pnid = pn->pn_left;
             if (pnid->isKind(PNK_NUMBER)) {
                 idvalue = NumberValue(pnid->pn_dval);
             } else {
                 JS_ASSERT(pnid->isKind(PNK_NAME) || pnid->isKind(PNK_STRING));
                 JS_ASSERT(pnid->pn_atom != cx->names().proto);
@@ -3971,17 +3981,17 @@ ParseNode::getConstantValue(ExclusiveCon
     }
     return false;
 }
 
 static bool
 EmitSingletonInitialiser(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     RootedValue value(cx);
-    if (!pn->getConstantValue(cx, &value))
+    if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value))
         return false;
 
     RootedObject obj(cx, &value.toObject());
     if (!obj->is<ArrayObject>() && !JSObject::setSingletonType(cx, obj))
         return false;
 
     ObjectBox *objbox = bce->parser->newObjectBox(obj);
     if (!objbox)
@@ -3989,17 +3999,17 @@ EmitSingletonInitialiser(ExclusiveContex
 
     return EmitObjectOp(cx, objbox, JSOP_OBJECT, bce);
 }
 
 static bool
 EmitCallSiteObject(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     RootedValue value(cx);
-    if (!pn->getConstantValue(cx, &value))
+    if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value))
         return false;
 
     JS_ASSERT(value.isObject());
 
     ObjectBox *objbox1 = bce->parser->newObjectBox(&value.toObject());
     if (!objbox1)
         return false;
 
@@ -6721,20 +6731,53 @@ frontend::EmitTree(ExclusiveContext *cx,
         break;
       }
 
       case PNK_CALLSITEOBJ:
             ok = EmitCallSiteObject(cx, bce, pn);
             break;
 
       case PNK_ARRAY:
-        if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && bce->checkSingletonContext())
-            ok = EmitSingletonInitialiser(cx, bce, pn);
-        else
-            ok = EmitArray(cx, bce, pn->pn_head, pn->pn_count);
+        if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head) {
+            if (bce->checkSingletonContext()) {
+                // Bake in the object entirely if it will only be created once.
+                ok = EmitSingletonInitialiser(cx, bce, pn);
+                break;
+            }
+
+            // If the array consists entirely of primitive values, make a
+            // template object with copy on write elements that can be reused
+            // every time the initializer executes.
+            RootedValue value(cx);
+            if (bce->emitterMode != BytecodeEmitter::SelfHosting &&
+                bce->script->compileAndGo() &&
+                pn->pn_count != 0 &&
+                pn->getConstantValue(cx, ParseNode::DontAllowNestedObjects, &value))
+            {
+                // Note: the type of the template object might not yet reflect
+                // that the object has copy on write elements. When the
+                // interpreter or JIT compiler fetches the template, it should
+                // use types::GetOrFixupCopyOnWriteObject to make sure the type
+                // for the template is accurate. We don't do this here as we
+                // want to use types::InitObject, which requires a finished
+                // script.
+                JSObject *obj = &value.toObject();
+                if (!ObjectElements::MakeElementsCopyOnWrite(cx, obj))
+                    return false;
+
+                ObjectBox *objbox = bce->parser->newObjectBox(obj);
+                if (!objbox)
+                    return false;
+
+                ok = EmitObjectOp(cx, objbox, JSOP_NEWARRAY_COPYONWRITE, bce);
+                break;
+            }
+        }
+
+        ok = EmitArray(cx, bce, pn->pn_head, pn->pn_count);
         break;
 
        case PNK_ARRAYCOMP:
         ok = EmitArrayComp(cx, bce, pn);
         break;
 
       case PNK_OBJECT:
         ok = EmitObject(cx, bce, pn);
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -826,17 +826,23 @@ class ParseNode
     }
 
     void checkListConsistency()
 #ifndef DEBUG
     {}
 #endif
     ;
 
-    bool getConstantValue(ExclusiveContext *cx, MutableHandleValue vp);
+    enum AllowConstantObjects {
+        DontAllowObjects = 0,
+        DontAllowNestedObjects,
+        AllowObjects
+    };
+
+    bool getConstantValue(ExclusiveContext *cx, AllowConstantObjects allowObjects, MutableHandleValue vp);
     inline bool isConstant();
 
     template <class NodeType>
     inline bool is() const {
         return NodeType::test(*this);
     }
 
     /* Casting operations. */
@@ -1269,17 +1275,17 @@ class PropertyByValue : public ParseNode
 struct CallSiteNode : public ListNode {
     explicit CallSiteNode(uint32_t begin): ListNode(PNK_CALLSITEOBJ, TokenPos(begin, begin + 1)) {}
 
     static bool test(const ParseNode &node) {
         return node.isKind(PNK_CALLSITEOBJ);
     }
 
     bool getRawArrayValue(ExclusiveContext *cx, MutableHandleValue vp) {
-        return pn_head->getConstantValue(cx, vp);
+        return pn_head->getConstantValue(cx, AllowObjects, vp);
     }
 };
 
 #ifdef DEBUG
 void DumpParseTree(ParseNode *pn, int indent = 0);
 #endif
 
 /*
--- a/js/src/gc/Barrier.h
+++ b/js/src/gc/Barrier.h
@@ -1002,24 +1002,44 @@ HeapValueify(Value *v)
     JS_STATIC_ASSERT(sizeof(HeapSlot) == sizeof(Value));
     return (HeapValue *)v;
 }
 
 class HeapSlotArray
 {
     HeapSlot *array;
 
+    // Whether writes may be performed to the slots in this array. This helps
+    // to control how object elements which may be copy on write are used.
+#ifdef DEBUG
+    bool allowWrite_;
+#endif
+
   public:
-    explicit HeapSlotArray(HeapSlot *array) : array(array) {}
+    explicit HeapSlotArray(HeapSlot *array, bool allowWrite)
+      : array(array)
+#ifdef DEBUG
+      , allowWrite_(allowWrite)
+#endif
+    {}
 
     operator const Value *() const { return Valueify(array); }
-    operator HeapSlot *() const { return array; }
+    operator HeapSlot *() const { JS_ASSERT(allowWrite()); return array; }
+
+    HeapSlotArray operator +(int offset) const { return HeapSlotArray(array + offset, allowWrite()); }
+    HeapSlotArray operator +(uint32_t offset) const { return HeapSlotArray(array + offset, allowWrite()); }
 
-    HeapSlotArray operator +(int offset) const { return HeapSlotArray(array + offset); }
-    HeapSlotArray operator +(uint32_t offset) const { return HeapSlotArray(array + offset); }
+  private:
+    bool allowWrite() const {
+#ifdef DEBUG
+        return allowWrite_;
+#else
+        return true;
+#endif
+    }
 };
 
 /*
  * Operations on a Heap thing inside the GC need to strip the barriers from
  * pointer operations. This template helps do that in contexts where the type
  * is templatized.
  */
 template <typename T> struct Unbarriered {};
--- a/js/src/gc/ForkJoinNursery.cpp
+++ b/js/src/gc/ForkJoinNursery.cpp
@@ -844,17 +844,17 @@ ForkJoinNursery::copySlotsToTospace(JSOb
     js_memcpy(dst->slots, src->slots, count * sizeof(HeapSlot));
     setSlotsForwardingPointer(src->slots, dst->slots, count);
     return count * sizeof(HeapSlot);
 }
 
 size_t
 ForkJoinNursery::copyElementsToTospace(JSObject *dst, JSObject *src, AllocKind dstKind)
 {
-    if (src->hasEmptyElements())
+    if (src->hasEmptyElements() || src->denseElementsAreCopyOnWrite())
         return 0;
 
     ObjectElements *srcHeader = src->getElementsHeader();
     ObjectElements *dstHeader;
 
     // TODO Bug 874151: Prefer to put element data inline if we have space.
     // (Note, not a correctness issue.)
     if (!isInsideFromspace(srcHeader)) {
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1748,23 +1748,34 @@ GCMarker::processMarkStackTop(SliceBudge
             clasp->trace(this, obj);
         }
 
         if (!shape->isNative())
             return;
 
         unsigned nslots = obj->slotSpan();
 
-        if (!obj->hasEmptyElements()) {
-            vp = obj->getDenseElements();
+        do {
+            if (obj->hasEmptyElements())
+                break;
+
+            if (obj->denseElementsAreCopyOnWrite()) {
+                JSObject *owner = obj->getElementsHeader()->ownerObject();
+                if (owner != obj) {
+                    PushMarkStack(this, owner);
+                    break;
+                }
+            }
+
+            vp = obj->getDenseElementsAllowCopyOnWrite();
             end = vp + obj->getDenseInitializedLength();
             if (!nslots)
                 goto scan_value_array;
             pushValueArray(obj, vp, end);
-        }
+        } while (false);
 
         vp = obj->fixedSlots();
         if (obj->slots) {
             unsigned nfixed = obj->numFixedSlots();
             if (nslots > nfixed) {
                 pushValueArray(obj, vp, vp + nfixed);
                 vp = obj->slots;
                 end = vp + (nslots - nfixed);
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -518,17 +518,19 @@ js::Nursery::traceObject(MinorCollection
 {
     const Class *clasp = obj->getClass();
     if (clasp->trace)
         clasp->trace(trc, obj);
 
     if (!obj->isNative())
         return;
 
-    if (!obj->hasEmptyElements())
+    // Note: the contents of copy on write elements pointers are filled in
+    // during parsing and cannot contain nursery pointers.
+    if (!obj->hasEmptyElements() && !obj->denseElementsAreCopyOnWrite())
         markSlots(trc, obj->getDenseElements(), obj->getDenseInitializedLength());
 
     HeapSlot *fixedStart, *fixedEnd, *dynStart, *dynEnd;
     obj->getSlotRange(0, obj->slotSpan(), &fixedStart, &fixedEnd, &dynStart, &dynEnd);
     markSlots(trc, fixedStart, fixedEnd);
     markSlots(trc, dynStart, dynEnd);
 }
 
@@ -664,17 +666,17 @@ js::Nursery::moveSlotsToTenured(JSObject
     PodCopy(dst->slots, src->slots, count);
     setSlotsForwardingPointer(src->slots, dst->slots, count);
     return count * sizeof(HeapSlot);
 }
 
 size_t
 js::Nursery::moveElementsToTenured(JSObject *dst, JSObject *src, AllocKind dstKind)
 {
-    if (src->hasEmptyElements())
+    if (src->hasEmptyElements() || src->denseElementsAreCopyOnWrite())
         return 0;
 
     Zone *zone = src->zone();
     ObjectElements *srcHeader = src->getElementsHeader();
     ObjectElements *dstHeader;
 
     /* TODO Bug 874151: Prefer to put element data inline if we have space. */
     if (!isInside(srcHeader)) {
--- a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-05.js
+++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets-05.js
@@ -9,12 +9,12 @@ Debugger(global).onDebuggerStatement = f
                 assertEq(offset.lineNumber, 1);
                 global.log += offset.columnNumber + " ";
             }
         });
     });
 };
 
 global.log = '';
-global.eval("function f(n){var a=[1,2,3]} debugger;");
+global.eval("function f(n){var a=[1,2,n]} debugger;");
 global.f(3);
 // Should hit each item in the array.
 assertEq(global.log, "18 21 23 25 19 ");
--- a/js/src/jit-test/tests/parallel/write-array.js
+++ b/js/src/jit-test/tests/parallel/write-array.js
@@ -1,14 +1,14 @@
 load(libdir + "parallelarray-helpers.js");
 
 function buildSimple() {
   assertParallelModesCommute(["seq", "par"], function(m) {
     return Array.buildPar(256, function(i) {
-      let obj = [0, 1, 2];
+      let obj = [i, 1, 2];
       obj[0] += 1;
       obj[1] += 1;
       obj[2] += 1;
       return obj;
     }, m);
   });
 }
 
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -1644,16 +1644,42 @@ BaselineCompiler::emit_JSOP_NEWARRAY()
     ICNewArray_Fallback::Compiler stubCompiler(cx, templateObject);
     if (!emitOpIC(stubCompiler.getStub(&stubSpace_)))
         return false;
 
     frame.push(R0);
     return true;
 }
 
+typedef JSObject *(*NewArrayCopyOnWriteFn)(JSContext *, HandleObject, gc::InitialHeap);
+const VMFunction jit::NewArrayCopyOnWriteInfo =
+    FunctionInfo<NewArrayCopyOnWriteFn>(js::NewDenseCopyOnWriteArray);
+
+bool
+BaselineCompiler::emit_JSOP_NEWARRAY_COPYONWRITE()
+{
+    RootedScript scriptRoot(cx, script);
+    JSObject *obj = types::GetOrFixupCopyOnWriteObject(cx, scriptRoot, pc);
+    if (!obj)
+        return false;
+
+    prepareVMCall();
+
+    pushArg(Imm32(gc::DefaultHeap));
+    pushArg(ImmGCPtr(obj));
+
+    if (!callVM(NewArrayCopyOnWriteInfo))
+        return false;
+
+    // Box and push return value.
+    masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
+    frame.push(R0);
+    return true;
+}
+
 bool
 BaselineCompiler::emit_JSOP_INITELEM_ARRAY()
 {
     // Keep the object and rhs on the stack.
     frame.syncStack(0);
 
     // Load object in R0, index in R1.
     masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0);
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -87,16 +87,17 @@ namespace jit {
     _(JSOP_STRICTNE)           \
     _(JSOP_CONDSWITCH)         \
     _(JSOP_CASE)               \
     _(JSOP_DEFAULT)            \
     _(JSOP_LINENO)             \
     _(JSOP_BITNOT)             \
     _(JSOP_NEG)                \
     _(JSOP_NEWARRAY)           \
+    _(JSOP_NEWARRAY_COPYONWRITE) \
     _(JSOP_INITELEM_ARRAY)     \
     _(JSOP_NEWOBJECT)          \
     _(JSOP_NEWINIT)            \
     _(JSOP_INITELEM)           \
     _(JSOP_INITELEM_GETTER)    \
     _(JSOP_INITELEM_SETTER)    \
     _(JSOP_INITELEM_INC)       \
     _(JSOP_MUTATEPROTO)        \
@@ -268,12 +269,14 @@ class BaselineCompiler : public Baseline
 
     bool addPCMappingEntry(bool addIndexEntry);
 
     void getScopeCoordinateObject(Register reg);
     Address getScopeCoordinateAddressFromObject(Register objReg, Register reg);
     Address getScopeCoordinateAddress(Register reg);
 };
 
+extern const VMFunction NewArrayCopyOnWriteInfo;
+
 } // namespace jit
 } // namespace js
 
 #endif /* jit_BaselineCompiler_h */
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -5251,38 +5251,48 @@ ICSetElem_Dense::Compiler::generateStubC
     // Bounds check.
     Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength());
     masm.branch32(Assembler::BelowOrEqual, initLength, key, &failure);
 
     // Hole check.
     BaseIndex element(scratchReg, key, TimesEight);
     masm.branchTestMagic(Assembler::Equal, element, &failure);
 
+    // Perform a single test to see if we either need to convert double
+    // elements or clone the copy on write elements in the object.
+    Label noSpecialHandling;
+    Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags());
+    masm.branchTest32(Assembler::Zero, elementsFlags,
+                      Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS |
+                            ObjectElements::COPY_ON_WRITE),
+                      &noSpecialHandling);
+
+    // Fail if we need to clone copy on write elements.
+    masm.branchTest32(Assembler::NonZero, elementsFlags,
+                      Imm32(ObjectElements::COPY_ON_WRITE),
+                      &failure);
+
     // Failure is not possible now.  Free up registers.
     regs.add(R0);
     regs.add(R1);
     regs.takeUnchecked(obj);
     regs.takeUnchecked(key);
     Address valueAddr(BaselineStackReg, ICStackValueOffset);
 
-    // Convert int32 values to double if convertDoubleElements is set. In this
-    // case the heap typeset is guaranteed to contain both int32 and double, so
-    // it's okay to store a double.
-    Label dontConvertDoubles;
-    Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags());
-    masm.branchTest32(Assembler::Zero, elementsFlags,
-                      Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
-                      &dontConvertDoubles);
-    // Note that double arrays are only created by IonMonkey, so if we have no
-    // floating-point support Ion is disabled and there should be no double arrays.
+    // We need to convert int32 values being stored into doubles. In this case
+    // the heap typeset is guaranteed to contain both int32 and double, so it's
+    // okay to store a double. Note that double arrays are only created by
+    // IonMonkey, so if we have no floating-point support Ion is disabled and
+    // there should be no double arrays.
     if (cx->runtime()->jitSupportsFloatingPoint)
-        masm.convertInt32ValueToDouble(valueAddr, regs.getAny(), &dontConvertDoubles);
+        masm.convertInt32ValueToDouble(valueAddr, regs.getAny(), &noSpecialHandling);
     else
         masm.assumeUnreachable("There shouldn't be double arrays when there is no FP support.");
-    masm.bind(&dontConvertDoubles);
+
+    masm.bind(&noSpecialHandling);
 
     // Don't overwrite R0 becuase |obj| might overlap with it, and it's needed
     // for post-write barrier later.
     ValueOperand tmpVal = regs.takeAnyValue();
     masm.loadValue(valueAddr, tmpVal);
     EmitPreBarrier(masm, element, MIRType_Value);
     masm.storeValue(tmpVal, element);
     regs.add(key);
@@ -5424,16 +5434,22 @@ ICSetElemDenseAddCompiler::generateStubC
     // Bounds check (key == initLength)
     Address initLength(scratchReg, ObjectElements::offsetOfInitializedLength());
     masm.branch32(Assembler::NotEqual, initLength, key, &failure);
 
     // Capacity check.
     Address capacity(scratchReg, ObjectElements::offsetOfCapacity());
     masm.branch32(Assembler::BelowOrEqual, capacity, key, &failure);
 
+    // Check for copy on write elements.
+    Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags());
+    masm.branchTest32(Assembler::NonZero, elementsFlags,
+                      Imm32(ObjectElements::COPY_ON_WRITE),
+                      &failure);
+
     // Failure is not possible now.  Free up registers.
     regs.add(R0);
     regs.add(R1);
     regs.takeUnchecked(obj);
     regs.takeUnchecked(key);
 
     // Increment initLength before write.
     masm.add32(Imm32(1), initLength);
@@ -5446,17 +5462,16 @@ ICSetElemDenseAddCompiler::generateStubC
     masm.bind(&skipIncrementLength);
 
     Address valueAddr(BaselineStackReg, ICStackValueOffset);
 
     // Convert int32 values to double if convertDoubleElements is set. In this
     // case the heap typeset is guaranteed to contain both int32 and double, so
     // it's okay to store a double.
     Label dontConvertDoubles;
-    Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags());
     masm.branchTest32(Assembler::Zero, elementsFlags,
                       Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
                       &dontConvertDoubles);
     // Note that double arrays are only created by IonMonkey, so if we have no
     // floating-point support Ion is disabled and there should be no double arrays.
     if (cx->runtime()->jitSupportsFloatingPoint)
         masm.convertInt32ValueToDouble(valueAddr, regs.getAny(), &dontConvertDoubles);
     else
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -17,16 +17,17 @@
 #include "jsprf.h"
 
 #include "asmjs/AsmJSModule.h"
 #include "builtin/Eval.h"
 #include "builtin/TypedObject.h"
 #ifdef JSGC_GENERATIONAL
 # include "gc/Nursery.h"
 #endif
+#include "jit/BaselineCompiler.h"
 #include "jit/IonCaches.h"
 #include "jit/IonLinker.h"
 #include "jit/IonOptimizationLevels.h"
 #include "jit/IonSpewer.h"
 #include "jit/JitcodeMap.h"
 #include "jit/Lowering.h"
 #include "jit/MIRGenerator.h"
 #include "jit/MoveEmitter.h"
@@ -1863,16 +1864,40 @@ CodeGenerator::visitMaybeToDoubleElement
     masm.bind(&convert);
     masm.convertInt32ToDouble(value, temp);
     masm.boxDouble(temp, out);
 
     masm.bind(&done);
     return true;
 }
 
+typedef bool (*CopyElementsForWriteFn)(ThreadSafeContext *, JSObject *);
+static const VMFunction CopyElementsForWriteInfo =
+    FunctionInfo<CopyElementsForWriteFn>(JSObject::CopyElementsForWrite);
+
+bool
+CodeGenerator::visitMaybeCopyElementsForWrite(LMaybeCopyElementsForWrite *lir)
+{
+    Register object = ToRegister(lir->object());
+    Register temp = ToRegister(lir->temp());
+
+    OutOfLineCode *ool = oolCallVM(CopyElementsForWriteInfo, lir,
+                                   (ArgList(), object), StoreNothing());
+    if (!ool)
+        return false;
+
+    masm.loadPtr(Address(object, JSObject::offsetOfElements()), temp);
+    masm.branchTest32(Assembler::NonZero,
+                      Address(temp, ObjectElements::offsetOfFlags()),
+                      Imm32(ObjectElements::COPY_ON_WRITE),
+                      ool->entry());
+    masm.bind(ool->rejoin());
+    return true;
+}
+
 bool
 CodeGenerator::visitFunctionEnvironment(LFunctionEnvironment *lir)
 {
     Address environment(ToRegister(lir->function()), JSFunction::offsetOfEnvironment());
     masm.loadPtr(environment, ToRegister(lir->output()));
     return true;
 }
 
@@ -3561,16 +3586,37 @@ bool
 CodeGenerator::visitOutOfLineNewArray(OutOfLineNewArray *ool)
 {
     if (!visitNewArrayCallVM(ool->lir()))
         return false;
     masm.jump(ool->rejoin());
     return true;
 }
 
+bool
+CodeGenerator::visitNewArrayCopyOnWrite(LNewArrayCopyOnWrite *lir)
+{
+    Register objReg = ToRegister(lir->output());
+    Register tempReg = ToRegister(lir->temp());
+    JSObject *templateObject = lir->mir()->templateObject();
+    gc::InitialHeap initialHeap = lir->mir()->initialHeap();
+
+    // If we have a template object, we can inline call object creation.
+    OutOfLineCode *ool = oolCallVM(NewArrayCopyOnWriteInfo, lir,
+                                   (ArgList(), ImmGCPtr(templateObject), Imm32(initialHeap)),
+                                   StoreRegisterTo(objReg));
+    if (!ool)
+        return false;
+
+    masm.createGCObject(objReg, tempReg, templateObject, initialHeap, ool->entry());
+
+    masm.bind(ool->rejoin());
+    return true;
+}
+
 // Out-of-line object allocation for JSOP_NEWOBJECT.
 class OutOfLineNewObject : public OutOfLineCodeBase<CodeGenerator>
 {
     LNewObject *lir_;
 
   public:
     explicit OutOfLineNewObject(LNewObject *lir)
       : lir_(lir)
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -110,16 +110,17 @@ class CodeGenerator : public CodeGenerat
     bool visitSlots(LSlots *lir);
     bool visitLoadSlotT(LLoadSlotT *lir);
     bool visitLoadSlotV(LLoadSlotV *lir);
     bool visitStoreSlotT(LStoreSlotT *lir);
     bool visitStoreSlotV(LStoreSlotV *lir);
     bool visitElements(LElements *lir);
     bool visitConvertElementsToDoubles(LConvertElementsToDoubles *lir);
     bool visitMaybeToDoubleElement(LMaybeToDoubleElement *lir);
+    bool visitMaybeCopyElementsForWrite(LMaybeCopyElementsForWrite *lir);
     bool visitGuardObjectIdentity(LGuardObjectIdentity *guard);
     bool visitGuardShapePolymorphic(LGuardShapePolymorphic *lir);
     bool visitTypeBarrierV(LTypeBarrierV *lir);
     bool visitTypeBarrierO(LTypeBarrierO *lir);
     bool visitMonitorTypes(LMonitorTypes *lir);
     bool visitPostWriteBarrierO(LPostWriteBarrierO *lir);
     bool visitPostWriteBarrierV(LPostWriteBarrierV *lir);
     bool visitOutOfLineCallPostWriteBarrier(OutOfLineCallPostWriteBarrier *ool);
@@ -139,16 +140,17 @@ class CodeGenerator : public CodeGenerat
     bool visitFilterArgumentsOrEvalV(LFilterArgumentsOrEvalV *lir);
     bool visitCallDirectEvalS(LCallDirectEvalS *lir);
     bool visitCallDirectEvalV(LCallDirectEvalV *lir);
     bool visitDoubleToInt32(LDoubleToInt32 *lir);
     bool visitFloat32ToInt32(LFloat32ToInt32 *lir);
     bool visitNewArrayCallVM(LNewArray *lir);
     bool visitNewArray(LNewArray *lir);
     bool visitOutOfLineNewArray(OutOfLineNewArray *ool);
+    bool visitNewArrayCopyOnWrite(LNewArrayCopyOnWrite *lir);
     bool visitNewObjectVMCall(LNewObject *lir);
     bool visitNewObject(LNewObject *lir);
     bool visitOutOfLineNewObject(OutOfLineNewObject *ool);
     bool visitNewDeclEnvObject(LNewDeclEnvObject *lir);
     bool visitNewCallObject(LNewCallObject *lir);
     bool visitNewSingletonCallObject(LNewSingletonCallObject *lir);
     bool visitNewCallObjectPar(LNewCallObjectPar *lir);
     bool visitNewStringObject(LNewStringObject *lir);
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -2263,16 +2263,26 @@ TryEliminateTypeBarrier(MTypeBarrier *ba
         if (previous == block)
             break;
         block = previous;
     }
 
     return true;
 }
 
+static inline MDefinition *
+PassthroughOperand(MDefinition *def)
+{
+    if (def->isConvertElementsToDoubles())
+        return def->toConvertElementsToDoubles()->elements();
+    if (def->isMaybeCopyElementsForWrite())
+        return def->toMaybeCopyElementsForWrite()->object();
+    return nullptr;
+}
+
 // Eliminate checks which are redundant given each other or other instructions.
 //
 // A type barrier is considered redundant if all missing types have been tested
 // for by earlier control instructions.
 //
 // A bounds check is considered redundant if it's dominated by another bounds
 // check with the same length and the indexes differ by only a constant amount.
 // In this case we eliminate the redundant bounds check and update the other one
@@ -2319,21 +2329,22 @@ jit::EliminateRedundantChecks(MIRGraph &
             bool eliminated = false;
 
             if (iter->isBoundsCheck()) {
                 if (!TryEliminateBoundsCheck(checks, index, iter->toBoundsCheck(), &eliminated))
                     return false;
             } else if (iter->isTypeBarrier()) {
                 if (!TryEliminateTypeBarrier(iter->toTypeBarrier(), &eliminated))
                     return false;
-            } else if (iter->isConvertElementsToDoubles()) {
-                // Now that code motion passes have finished, replace any
-                // ConvertElementsToDoubles with the actual elements.
-                MConvertElementsToDoubles *ins = iter->toConvertElementsToDoubles();
-                ins->replaceAllUsesWith(ins->elements());
+            } else {
+                // Now that code motion passes have finished, replace
+                // instructions which pass through one of their operands
+                // (and perform additional checks) with that operand.
+                if (MDefinition *passthrough = PassthroughOperand(*iter))
+                    iter->replaceAllUsesWith(passthrough);
             }
 
             if (eliminated)
                 iter = block->discardDefAt(iter);
             else
                 iter++;
         }
         index++;
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -1542,16 +1542,19 @@ IonBuilder::inspectOpcode(JSOp op)
       case JSOP_NEWINIT:
         if (GET_UINT8(pc) == JSProto_Array)
             return jsop_newarray(0);
         return jsop_newobject();
 
       case JSOP_NEWARRAY:
         return jsop_newarray(GET_UINT24(pc));
 
+      case JSOP_NEWARRAY_COPYONWRITE:
+        return jsop_newarray_copyonwrite();
+
       case JSOP_NEWOBJECT:
         return jsop_newobject();
 
       case JSOP_INITELEM:
         return jsop_initelem();
 
       case JSOP_INITELEM_ARRAY:
         return jsop_initelem_array();
@@ -5532,16 +5535,38 @@ IonBuilder::jsop_newarray(uint32_t count
     if (conversion == types::TemporaryTypeSet::AlwaysConvertToDoubles)
         templateObject->setShouldConvertDoubleElements();
     else
         templateObject->clearShouldConvertDoubleElements();
     return true;
 }
 
 bool
+IonBuilder::jsop_newarray_copyonwrite()
+{
+    JSObject *templateObject = types::GetCopyOnWriteObject(script(), pc);
+
+    // The baseline compiler should have ensured the template object has a type
+    // with the copy on write flag set already. During the arguments usage
+    // analysis the baseline compiler hasn't run yet, however, though in this
+    // case the template object's type doesn't matter.
+    JS_ASSERT_IF(info().executionMode() != ArgumentsUsageAnalysis,
+                 templateObject->type()->hasAnyFlags(types::OBJECT_FLAG_COPY_ON_WRITE));
+
+    MNewArrayCopyOnWrite *ins =
+        MNewArrayCopyOnWrite::New(alloc(), constraints(), templateObject,
+                                  templateObject->type()->initialHeap(constraints()));
+
+    current->add(ins);
+    current->push(ins);
+
+    return true;
+}
+
+bool
 IonBuilder::jsop_newobject()
 {
     JSObject *templateObject = inspector->getTemplateObject(pc);
     if (!templateObject)
         return abort("No template object for NEWOBJECT");
 
     JS_ASSERT(templateObject->is<JSObject>());
     MConstant *templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
@@ -7926,16 +7951,19 @@ IonBuilder::setElemTryCache(bool *emitte
     }
 
     // We can avoid worrying about holes in the IC if we know a priori we are safe
     // from them. If TI can guard that there are no indexed properties on the prototype
     // chain, we know that we anen't missing any setters by overwriting the hole with
     // another value.
     bool guardHoles = ElementAccessHasExtraIndexedProperty(constraints(), object);
 
+    // Make sure the object being written to doesn't have copy on write elements.
+    object = addMaybeCopyElementsForWrite(object);
+
     if (NeedsPostBarrier(info(), value))
         current->add(MPostWriteBarrier::New(alloc(), object, value));
 
     // Emit SetElementCache.
     MInstruction *ins = MSetElementCache::New(alloc(), object, index, value, script()->strict(), guardHoles);
     current->add(ins);
     current->push(value);
 
@@ -7961,16 +7989,19 @@ IonBuilder::jsop_setelem_dense(types::Te
     if (NeedsPostBarrier(info(), value))
         current->add(MPostWriteBarrier::New(alloc(), obj, value));
 
     // Ensure id is an integer.
     MInstruction *idInt32 = MToInt32::New(alloc(), id);
     current->add(idInt32);
     id = idInt32;
 
+    // Copy the elements vector if necessary.
+    obj = addMaybeCopyElementsForWrite(obj);
+
     // Get the elements vector.
     MElements *elements = MElements::New(alloc(), obj);
     current->add(elements);
 
     // Ensure the value is a double, if double conversion might be needed.
     MDefinition *newValue = value;
     switch (conversion) {
       case types::TemporaryTypeSet::AlwaysConvertToDoubles:
@@ -10253,16 +10284,26 @@ IonBuilder::jsop_instanceof()
 MInstruction *
 IonBuilder::addConvertElementsToDoubles(MDefinition *elements)
 {
     MInstruction *convert = MConvertElementsToDoubles::New(alloc(), elements);
     current->add(convert);
     return convert;
 }
 
+MDefinition *
+IonBuilder::addMaybeCopyElementsForWrite(MDefinition *object)
+{
+    if (!ElementAccessMightBeCopyOnWrite(constraints(), object))
+        return object;
+    MInstruction *copy = MMaybeCopyElementsForWrite::New(alloc(), object);
+    current->add(copy);
+    return copy;
+}
+
 MInstruction *
 IonBuilder::addBoundsCheck(MDefinition *index, MDefinition *length)
 {
     MInstruction *check = MBoundsCheck::New(alloc(), index, length);
     current->add(check);
 
     // If a bounds check failed in the past, don't optimize bounds checks.
     if (failedBoundsCheck_)
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -370,16 +370,17 @@ class IonBuilder : public MIRGenerator
     MDefinition *createThisScriptedSingleton(JSFunction *target, MDefinition *callee);
     MDefinition *createThis(JSFunction *target, MDefinition *callee);
     MInstruction *createDeclEnvObject(MDefinition *callee, MDefinition *scopeObj);
     MInstruction *createCallObject(MDefinition *callee, MDefinition *scopeObj);
 
     MDefinition *walkScopeChain(unsigned hops);
 
     MInstruction *addConvertElementsToDoubles(MDefinition *elements);
+    MDefinition *addMaybeCopyElementsForWrite(MDefinition *object);
     MInstruction *addBoundsCheck(MDefinition *index, MDefinition *length);
     MInstruction *addShapeGuard(MDefinition *obj, Shape *const shape, BailoutKind bailoutKind);
 
     MDefinition *convertShiftToMaskForStaticTypedArray(MDefinition *id,
                                                        Scalar::Type viewType);
 
     bool invalidatedIdempotentCache();
 
@@ -613,16 +614,17 @@ class IonBuilder : public MIRGenerator
     bool jsop_runonce();
     bool jsop_rest();
     bool jsop_not();
     bool jsop_getprop(PropertyName *name);
     bool jsop_setprop(PropertyName *name);
     bool jsop_delprop(PropertyName *name);
     bool jsop_delelem();
     bool jsop_newarray(uint32_t count);
+    bool jsop_newarray_copyonwrite();
     bool jsop_newobject();
     bool jsop_initelem();
     bool jsop_initelem_array();
     bool jsop_initelem_getter_setter();
     bool jsop_mutateproto();
     bool jsop_initprop(PropertyName *name);
     bool jsop_initprop_getter_setter(PropertyName *name);
     bool jsop_regexp(RegExpObject *reobj);
--- a/js/src/jit/IonMacroAssembler.cpp
+++ b/js/src/jit/IonMacroAssembler.cpp
@@ -604,16 +604,22 @@ MacroAssembler::newGCThing(Register resu
 void
 MacroAssembler::createGCObject(Register obj, Register temp, JSObject *templateObj,
                                gc::InitialHeap initialHeap, Label *fail, bool initFixedSlots)
 {
     uint32_t nDynamicSlots = templateObj->numDynamicSlots();
     gc::AllocKind allocKind = templateObj->tenuredGetAllocKind();
     JS_ASSERT(allocKind >= gc::FINALIZE_OBJECT0 && allocKind <= gc::FINALIZE_OBJECT_LAST);
 
+    // Arrays with copy on write elements do not need fixed space for an
+    // elements header. The template object, which owns the original elements,
+    // might have another allocation kind.
+    if (templateObj->denseElementsAreCopyOnWrite())
+        allocKind = gc::FINALIZE_OBJECT0_BACKGROUND;
+
     allocateObject(obj, temp, allocKind, nDynamicSlots, initialHeap, fail);
     initGCThing(obj, temp, templateObj, initFixedSlots);
 }
 
 
 // Inlined equivalent of gc::AllocateNonObject, without failure case handling.
 // Non-object allocation does not need to worry about slots, so can take a
 // simpler path.
@@ -839,26 +845,29 @@ MacroAssembler::initGCSlots(Register obj
 }
 
 void
 MacroAssembler::initGCThing(Register obj, Register slots, JSObject *templateObj,
                             bool initFixedSlots)
 {
     // Fast initialization of an empty object returned by allocateObject().
 
-    JS_ASSERT(!templateObj->hasDynamicElements());
+    JS_ASSERT_IF(!templateObj->denseElementsAreCopyOnWrite(), !templateObj->hasDynamicElements());
 
     storePtr(ImmGCPtr(templateObj->lastProperty()), Address(obj, JSObject::offsetOfShape()));
     storePtr(ImmGCPtr(templateObj->type()), Address(obj, JSObject::offsetOfType()));
     if (templateObj->hasDynamicSlots())
         storePtr(slots, Address(obj, JSObject::offsetOfSlots()));
     else
         storePtr(ImmPtr(nullptr), Address(obj, JSObject::offsetOfSlots()));
 
-    if (templateObj->is<ArrayObject>()) {
+    if (templateObj->denseElementsAreCopyOnWrite()) {
+        storePtr(ImmPtr((const Value *) templateObj->getDenseElements()),
+                 Address(obj, JSObject::offsetOfElements()));
+    } else if (templateObj->is<ArrayObject>()) {
         Register temp = slots;
         JS_ASSERT(!templateObj->getDenseInitializedLength());
 
         int elementsOffset = JSObject::offsetOfFixedElements();
 
         computeEffectiveAddress(Address(obj, elementsOffset), temp);
         storePtr(temp, Address(obj, JSObject::offsetOfElements()));
 
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -432,16 +432,34 @@ class LNewArray : public LInstructionHel
         return getTemp(0);
     }
 
     MNewArray *mir() const {
         return mir_->toNewArray();
     }
 };
 
+class LNewArrayCopyOnWrite : public LInstructionHelper<1, 0, 1>
+{
+  public:
+    LIR_HEADER(NewArrayCopyOnWrite)
+
+    explicit LNewArrayCopyOnWrite(const LDefinition &temp) {
+        setTemp(0, temp);
+    }
+
+    const LDefinition *temp() {
+        return getTemp(0);
+    }
+
+    MNewArrayCopyOnWrite *mir() const {
+        return mir_->toNewArrayCopyOnWrite();
+    }
+};
+
 class LNewObject : public LInstructionHelper<1, 0, 1>
 {
   public:
     LIR_HEADER(NewObject)
 
     explicit LNewObject(const LDefinition &temp) {
         setTemp(0, temp);
     }
@@ -3754,16 +3772,36 @@ class LMaybeToDoubleElement : public LIn
     const LAllocation *value() {
         return getOperand(1);
     }
     const LDefinition *tempFloat() {
         return getTemp(0);
     }
 };
 
+// If necessary, copy the elements in an object so they may be written to.
+class LMaybeCopyElementsForWrite : public LInstructionHelper<0, 1, 1>
+{
+  public:
+    LIR_HEADER(MaybeCopyElementsForWrite)
+
+    explicit LMaybeCopyElementsForWrite(const LAllocation &obj, const LDefinition &temp) {
+        setOperand(0, obj);
+        setTemp(0, temp);
+    }
+
+    const LAllocation *object() {
+        return getOperand(0);
+    }
+
+    const LDefinition *temp() {
+        return getTemp(0);
+    }
+};
+
 // Load the initialized length from an elements header.
 class LInitializedLength : public LInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(InitializedLength)
 
     explicit LInitializedLength(const LAllocation &elements) {
         setOperand(0, elements);
--- a/js/src/jit/LOpcodes.h
+++ b/js/src/jit/LOpcodes.h
@@ -26,16 +26,17 @@
     _(Value)                        \
     _(CloneLiteral)                 \
     _(Parameter)                    \
     _(Callee)                       \
     _(TableSwitch)                  \
     _(TableSwitchV)                 \
     _(Goto)                         \
     _(NewArray)                     \
+    _(NewArrayCopyOnWrite)          \
     _(ArraySplice)                  \
     _(NewObject)                    \
     _(NewDeclEnvObject)             \
     _(NewCallObject)                \
     _(NewSingletonCallObject)       \
     _(NewStringObject)              \
     _(NewPar)                       \
     _(NewDenseArrayPar)             \
@@ -169,16 +170,17 @@
     _(Lambda)                       \
     _(LambdaArrow)                  \
     _(LambdaForSingleton)           \
     _(LambdaPar)                    \
     _(Slots)                        \
     _(Elements)                     \
     _(ConvertElementsToDoubles)     \
     _(MaybeToDoubleElement)         \
+    _(MaybeCopyElementsForWrite)    \
     _(LoadSlotV)                    \
     _(LoadSlotT)                    \
     _(StoreSlotV)                   \
     _(StoreSlotT)                   \
     _(GuardShape)                   \
     _(GuardShapePolymorphic)        \
     _(GuardObjectType)              \
     _(GuardObjectIdentity)          \
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -151,16 +151,23 @@ LIRGenerator::visitDefFun(MDefFun *ins)
 bool
 LIRGenerator::visitNewArray(MNewArray *ins)
 {
     LNewArray *lir = new(alloc()) LNewArray(temp());
     return define(lir, ins) && assignSafepoint(lir, ins);
 }
 
 bool
+LIRGenerator::visitNewArrayCopyOnWrite(MNewArrayCopyOnWrite *ins)
+{
+    LNewArrayCopyOnWrite *lir = new(alloc()) LNewArrayCopyOnWrite(temp());
+    return define(lir, ins) && assignSafepoint(lir, ins);
+}
+
+bool
 LIRGenerator::visitNewObject(MNewObject *ins)
 {
     LNewObject *lir = new(alloc()) LNewObject(temp());
     return define(lir, ins) && assignSafepoint(lir, ins);
 }
 
 bool
 LIRGenerator::visitNewDeclEnvObject(MNewDeclEnvObject *ins)
@@ -2161,16 +2168,23 @@ LIRGenerator::visitMaybeToDoubleElement(
 
     LMaybeToDoubleElement *lir = new(alloc()) LMaybeToDoubleElement(useRegisterAtStart(ins->elements()),
                                                                     useRegisterAtStart(ins->value()),
                                                                     tempDouble());
     return defineBox(lir, ins);
 }
 
 bool
+LIRGenerator::visitMaybeCopyElementsForWrite(MMaybeCopyElementsForWrite *ins)
+{
+    LInstruction *check = new(alloc()) LMaybeCopyElementsForWrite(useRegister(ins->object()), temp());
+    return add(check, ins) && assignSafepoint(check, ins);
+}
+
+bool
 LIRGenerator::visitLoadSlot(MLoadSlot *ins)
 {
     switch (ins->type()) {
       case MIRType_Value:
         return defineBox(new(alloc()) LLoadSlotV(useRegister(ins->slots())), ins);
 
       case MIRType_Undefined:
       case MIRType_Null:
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -65,16 +65,17 @@ class LIRGenerator : public LIRGenerator
     // Visitor hooks are explicit, to give CPU-specific versions a chance to
     // intercept without a bunch of explicit gunk in the .cpp.
     bool visitCloneLiteral(MCloneLiteral *ins);
     bool visitParameter(MParameter *param);
     bool visitCallee(MCallee *callee);
     bool visitGoto(MGoto *ins);
     bool visitTableSwitch(MTableSwitch *tableswitch);
     bool visitNewArray(MNewArray *ins);
+    bool visitNewArrayCopyOnWrite(MNewArrayCopyOnWrite *ins);
     bool visitNewObject(MNewObject *ins);
     bool visitNewDeclEnvObject(MNewDeclEnvObject *ins);
     bool visitNewCallObject(MNewCallObject *ins);
     bool visitNewRunOnceCallObject(MNewRunOnceCallObject *ins);
     bool visitNewStringObject(MNewStringObject *ins);
     bool visitNewDerivedTypedObject(MNewDerivedTypedObject *ins);
     bool visitNewPar(MNewPar *ins);
     bool visitNewCallObjectPar(MNewCallObjectPar *ins);
@@ -161,16 +162,17 @@ class LIRGenerator : public LIRGenerator
     bool visitLambda(MLambda *ins);
     bool visitLambdaArrow(MLambdaArrow *ins);
     bool visitLambdaPar(MLambdaPar *ins);
     bool visitSlots(MSlots *ins);
     bool visitElements(MElements *ins);
     bool visitConstantElements(MConstantElements *ins);
     bool visitConvertElementsToDoubles(MConvertElementsToDoubles *ins);
     bool visitMaybeToDoubleElement(MMaybeToDoubleElement *ins);
+    bool visitMaybeCopyElementsForWrite(MMaybeCopyElementsForWrite *ins);
     bool visitLoadSlot(MLoadSlot *ins);
     bool visitFunctionEnvironment(MFunctionEnvironment *ins);
     bool visitForkJoinContext(MForkJoinContext *ins);
     bool visitGuardThreadExclusive(MGuardThreadExclusive *ins);
     bool visitInterruptCheck(MInterruptCheck *ins);
     bool visitAsmJSInterruptCheck(MAsmJSInterruptCheck *ins);
     bool visitInterruptCheckPar(MInterruptCheckPar *ins);
     bool visitStoreSlot(MStoreSlot *ins);
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -403,38 +403,40 @@ IonBuilder::inlineArrayPopShift(CallInfo
     // Pop and shift are only handled for dense arrays that have never been
     // used in an iterator: popping elements does not account for suppressing
     // deleted properties in active iterators.
     types::TypeObjectFlags unhandledFlags =
         types::OBJECT_FLAG_SPARSE_INDEXES |
         types::OBJECT_FLAG_LENGTH_OVERFLOW |
         types::OBJECT_FLAG_ITERATED;
 
-    types::TemporaryTypeSet *thisTypes = callInfo.thisArg()->resultTypeSet();
+    MDefinition *obj = callInfo.thisArg();
+    types::TemporaryTypeSet *thisTypes = obj->resultTypeSet();
     if (!thisTypes || thisTypes->getKnownClass() != &ArrayObject::class_)
         return InliningStatus_NotInlined;
     if (thisTypes->hasObjectFlags(constraints(), unhandledFlags))
         return InliningStatus_NotInlined;
 
     if (types::ArrayPrototypeHasIndexedProperty(constraints(), script()))
         return InliningStatus_NotInlined;
 
     callInfo.setImplicitlyUsedUnchecked();
 
+    obj = addMaybeCopyElementsForWrite(obj);
+
     types::TemporaryTypeSet *returnTypes = getInlineReturnTypeSet();
     bool needsHoleCheck = thisTypes->hasObjectFlags(constraints(), types::OBJECT_FLAG_NON_PACKED);
     bool maybeUndefined = returnTypes->hasType(types::Type::UndefinedType());
 
     BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(),
-                                                       callInfo.thisArg(), nullptr, returnTypes);
+                                                       obj, nullptr, returnTypes);
     if (barrier != BarrierKind::NoBarrier)
         returnType = MIRType_Value;
 
-    MArrayPopShift *ins = MArrayPopShift::New(alloc(), callInfo.thisArg(), mode,
-                                              needsHoleCheck, maybeUndefined);
+    MArrayPopShift *ins = MArrayPopShift::New(alloc(), obj, mode, needsHoleCheck, maybeUndefined);
     current->add(ins);
     current->push(ins);
     ins->setResultType(returnType);
 
     if (!resumeAfter(ins))
         return InliningStatus_Error;
 
     if (!pushTypeBarrier(ins, returnTypes, barrier))
@@ -545,20 +547,22 @@ IonBuilder::inlineArrayPush(CallInfo &ca
     if (conversion == types::TemporaryTypeSet::AlwaysConvertToDoubles ||
         conversion == types::TemporaryTypeSet::MaybeConvertToDoubles)
     {
         MInstruction *valueDouble = MToDouble::New(alloc(), value);
         current->add(valueDouble);
         value = valueDouble;
     }
 
+    obj = addMaybeCopyElementsForWrite(obj);
+
     if (NeedsPostBarrier(info(), value))
-        current->add(MPostWriteBarrier::New(alloc(), callInfo.thisArg(), value));
+        current->add(MPostWriteBarrier::New(alloc(), obj, value));
 
-    MArrayPush *ins = MArrayPush::New(alloc(), callInfo.thisArg(), value);
+    MArrayPush *ins = MArrayPush::New(alloc(), obj, value);
     current->add(ins);
     current->push(ins);
 
     if (!resumeAfter(ins))
         return InliningStatus_Error;
     return InliningStatus_Inlined;
 }
 
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -3443,16 +3443,23 @@ jit::ElementAccessIsTypedArray(MDefiniti
 bool
 jit::ElementAccessIsPacked(types::CompilerConstraintList *constraints, MDefinition *obj)
 {
     types::TemporaryTypeSet *types = obj->resultTypeSet();
     return types && !types->hasObjectFlags(constraints, types::OBJECT_FLAG_NON_PACKED);
 }
 
 bool
+jit::ElementAccessMightBeCopyOnWrite(types::CompilerConstraintList *constraints, MDefinition *obj)
+{
+    types::TemporaryTypeSet *types = obj->resultTypeSet();
+    return !types || types->hasObjectFlags(constraints, types::OBJECT_FLAG_COPY_ON_WRITE);
+}
+
+bool
 jit::ElementAccessHasExtraIndexedProperty(types::CompilerConstraintList *constraints,
                                           MDefinition *obj)
 {
     types::TemporaryTypeSet *types = obj->resultTypeSet();
 
     if (!types || types->hasObjectFlags(constraints, types::OBJECT_FLAG_LENGTH_OVERFLOW))
         return true;
 
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -1921,16 +1921,55 @@ class MNewArray : public MUnaryInstructi
     bool writeRecoverData(CompactBufferWriter &writer) const;
     bool canRecoverOnBailout() const {
         // The template object can safely be used in the recover instruction
         // because it can never be mutated by any other function execution.
         return true;
     }
 };
 
+class MNewArrayCopyOnWrite : public MNullaryInstruction
+{
+    CompilerRootObject templateObject_;
+    gc::InitialHeap initialHeap_;
+
+    MNewArrayCopyOnWrite(types::CompilerConstraintList *constraints, JSObject *templateObject,
+              gc::InitialHeap initialHeap)
+      : templateObject_(templateObject),
+        initialHeap_(initialHeap)
+    {
+        JS_ASSERT(!templateObject->hasSingletonType());
+        setResultType(MIRType_Object);
+        setResultTypeSet(MakeSingletonTypeSet(constraints, templateObject));
+    }
+
+  public:
+    INSTRUCTION_HEADER(NewArrayCopyOnWrite)
+
+    static MNewArrayCopyOnWrite *New(TempAllocator &alloc,
+                                     types::CompilerConstraintList *constraints,
+                                     JSObject *templateObject,
+                                     gc::InitialHeap initialHeap)
+    {
+        return new(alloc) MNewArrayCopyOnWrite(constraints, templateObject, initialHeap);
+    }
+
+    JSObject *templateObject() const {
+        return templateObject_;
+    }
+
+    gc::InitialHeap initialHeap() const {
+        return initialHeap_;
+    }
+
+    virtual AliasSet getAliasSet() const {
+        return AliasSet::None();
+    }
+};
+
 class MNewObject : public MUnaryInstruction
 {
     gc::InitialHeap initialHeap_;
     bool templateObjectIsClassPrototype_;
 
     MNewObject(types::CompilerConstraintList *constraints, MConstant *templateConst,
                gc::InitialHeap initialHeap, bool templateObjectIsClassPrototype)
       : MUnaryInstruction(templateConst),
@@ -6446,16 +6485,59 @@ class MMaybeToDoubleElement
     bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 };
 
+// Passes through an object, after ensuring its elements are not copy on write.
+class MMaybeCopyElementsForWrite
+  : public MUnaryInstruction,
+    public SingleObjectPolicy
+{
+    explicit MMaybeCopyElementsForWrite(MDefinition *object)
+      : MUnaryInstruction(object)
+    {
+        setGuard();
+        setMovable();
+        setResultType(MIRType_Object);
+        setResultTypeSet(object->resultTypeSet());
+    }
+
+  public:
+    INSTRUCTION_HEADER(MaybeCopyElementsForWrite)
+
+    static MMaybeCopyElementsForWrite *New(TempAllocator &alloc, MDefinition *object) {
+        return new(alloc) MMaybeCopyElementsForWrite(object);
+    }
+
+    MDefinition *object() const {
+        return getOperand(0);
+    }
+    bool congruentTo(const MDefinition *ins) const {
+        return congruentIfOperandsEqual(ins);
+    }
+    AliasSet getAliasSet() const {
+        // This instruction can read and write to the elements' contents,
+        // in the same manner as MConvertElementsToDoubles. As with that
+        // instruction, this is safe to consolidate and freely reorder this
+        // instruction, though this must precede any loads of the object's
+        // elements pointer or writes to the object's elements. The latter
+        // property is ensured by chaining this with the object definition
+        // itself, in the same manner as MBoundsCheck.
+        return AliasSet::None();
+    }
+
+    TypePolicy *typePolicy() {
+        return this;
+    }
+};
+
 // Load the initialized length from an elements header.
 class MInitializedLength
   : public MUnaryInstruction
 {
     explicit MInitializedLength(MDefinition *elements)
       : MUnaryInstruction(elements)
     {
         setResultType(MIRType_Int32);
@@ -11338,16 +11420,17 @@ MControlInstruction *MDefinition::toCont
 }
 
 // Helper functions used to decide how to build MIR.
 
 bool ElementAccessIsDenseNative(MDefinition *obj, MDefinition *id);
 bool ElementAccessIsTypedArray(MDefinition *obj, MDefinition *id,
                                Scalar::Type *arrayType);
 bool ElementAccessIsPacked(types::CompilerConstraintList *constraints, MDefinition *obj);
+bool ElementAccessMightBeCopyOnWrite(types::CompilerConstraintList *constraints, MDefinition *obj);
 bool ElementAccessHasExtraIndexedProperty(types::CompilerConstraintList *constraints,
                                           MDefinition *obj);
 MIRType DenseNativeElementType(types::CompilerConstraintList *constraints, MDefinition *obj);
 BarrierKind PropertyReadNeedsTypeBarrier(JSContext *propertycx,
                                          types::CompilerConstraintList *constraints,
                                          types::TypeObjectKey *object, PropertyName *name,
                                          types::TemporaryTypeSet *observed, bool updateObserved);
 BarrierKind PropertyReadNeedsTypeBarrier(JSContext *propertycx,
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -88,16 +88,17 @@ namespace jit {
     _(GuardString)                                                          \
     _(AssertRange)                                                          \
     _(ToDouble)                                                             \
     _(ToFloat32)                                                            \
     _(ToInt32)                                                              \
     _(TruncateToInt32)                                                      \
     _(ToString)                                                             \
     _(NewArray)                                                             \
+    _(NewArrayCopyOnWrite)                                                  \
     _(NewObject)                                                            \
     _(NewDeclEnvObject)                                                     \
     _(NewCallObject)                                                        \
     _(NewRunOnceCallObject)                                                 \
     _(NewStringObject)                                                      \
     _(ObjectState)                                                          \
     _(ArrayState)                                                           \
     _(InitElem)                                                             \
@@ -116,16 +117,17 @@ namespace jit {
     _(StringReplace)                                                        \
     _(Lambda)                                                               \
     _(LambdaArrow)                                                          \
     _(Slots)                                                                \
     _(Elements)                                                             \
     _(ConstantElements)                                                     \
     _(ConvertElementsToDoubles)                                             \
     _(MaybeToDoubleElement)                                                 \
+    _(MaybeCopyElementsForWrite)                                            \
     _(LoadSlot)                                                             \
     _(StoreSlot)                                                            \
     _(FunctionEnvironment)                                                  \
     _(FilterTypeSet)                                                        \
     _(TypeBarrier)                                                          \
     _(MonitorTypes)                                                         \
     _(PostWriteBarrier)                                                     \
     _(GetPropertyCache)                                                     \
--- a/js/src/jit/ParallelSafetyAnalysis.cpp
+++ b/js/src/jit/ParallelSafetyAnalysis.cpp
@@ -182,16 +182,17 @@ class ParallelSafetyVisitor : public MDe
     SAFE_OP(GuardObject)
     SAFE_OP(ToDouble)
     SAFE_OP(ToFloat32)
     SAFE_OP(ToInt32)
     SAFE_OP(TruncateToInt32)
     SAFE_OP(MaybeToDoubleElement)
     CUSTOM_OP(ToString)
     CUSTOM_OP(NewArray)
+    UNSAFE_OP(NewArrayCopyOnWrite)
     CUSTOM_OP(NewObject)
     CUSTOM_OP(NewCallObject)
     CUSTOM_OP(NewRunOnceCallObject)
     CUSTOM_OP(NewDerivedTypedObject)
     SAFE_OP(ObjectState)
     SAFE_OP(ArrayState)
     UNSAFE_OP(InitElem)
     UNSAFE_OP(InitElemGetterSetter)
@@ -331,16 +332,17 @@ class ParallelSafetyVisitor : public MDe
     UNSAFE_OP(AsmJSVoidReturn)
     UNSAFE_OP(AsmJSPassStackArg)
     UNSAFE_OP(AsmJSParameter)
     UNSAFE_OP(AsmJSCall)
     DROP_OP(RecompileCheck)
 
     // It looks like this could easily be made safe:
     UNSAFE_OP(ConvertElementsToDoubles)
+    UNSAFE_OP(MaybeCopyElementsForWrite)
 };
 
 static void
 TransplantResumePoint(MInstruction *oldInstruction, MInstruction *replacementInstruction)
 {
     MOZ_ASSERT(!oldInstruction->isDiscarded());
     if (MResumePoint *rp = oldInstruction->resumePoint()) {
         replacementInstruction->stealResumePoint(oldInstruction);
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -331,16 +331,18 @@ DeleteArrayElement(JSContext *cx, Handle
 {
     JS_ASSERT(index >= 0);
     JS_ASSERT(floor(index) == index);
 
     if (obj->is<ArrayObject>() && !obj->isIndexed()) {
         if (index <= UINT32_MAX) {
             uint32_t idx = uint32_t(index);
             if (idx < obj->getDenseInitializedLength()) {
+                if (!obj->maybeCopyElementsForWrite(cx))
+                    return false;
                 obj->markDenseElementsNotPacked(cx);
                 obj->setDenseElement(idx, MagicValue(JS_ELEMENTS_HOLE));
                 if (!js_SuppressDeletedElement(cx, obj, idx))
                     return false;
             }
         }
 
         *succeeded = true;
@@ -467,16 +469,19 @@ template <ExecutionMode mode>
 bool
 js::ArraySetLength(typename ExecutionModeTraits<mode>::ContextType cxArg,
                    Handle<ArrayObject*> arr, HandleId id,
                    unsigned attrs, HandleValue value, bool setterIsStrict)
 {
     MOZ_ASSERT(cxArg->isThreadLocal(arr));
     MOZ_ASSERT(id == NameToId(cxArg->names().length));
 
+    if (!arr->maybeCopyElementsForWrite(cxArg))
+        return false;
+
     /* Steps 1-2 are irrelevant in our implementation. */
 
     /* Steps 3-5. */
     uint32_t newLen;
     if (!CanonicalizeArrayLengthValue<mode>(cxArg, value, &newLen))
         return false;
 
     // Abort if we're being asked to change enumerability or configurability.
@@ -542,16 +547,19 @@ js::ArraySetLength(typename ExecutionMod
         // We can only do this if there are only densely-indexed elements.
         // Once there's a sparse indexed element, there's no good way to know,
         // save by enumerating all the properties to find it.  But we *have* to
         // know in case that sparse indexed element is non-configurable, as
         // that element must prevent any deletions below it.  Bug 586842 should
         // fix this inefficiency by moving indexed storage to be entirely
         // separate from non-indexed storage.
         if (!arr->isIndexed()) {
+            if (!arr->maybeCopyElementsForWrite(cxArg))
+                return false;
+
             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(cxArg, newLen);
 
@@ -2134,18 +2142,21 @@ js::array_pop(JSContext *cx, unsigned ar
 
     // If this was an array, then there are no elements above the one we just
     // deleted (if we deleted an element).  Thus we can shrink the dense
     // initialized length accordingly.  (This is fine even if the array length
     // is non-writable: length-changing occurs after element-deletion effects.)
     // Don't do anything if this isn't an array, as any deletion above has no
     // effect on any elements after the "last" one indicated by the "length"
     // property.
-    if (obj->is<ArrayObject>() && obj->getDenseInitializedLength() > index)
+    if (obj->is<ArrayObject>() && obj->getDenseInitializedLength() > index) {
+        if (!obj->maybeCopyElementsForWrite(cx))
+            return false;
         obj->setDenseInitializedLength(index);
+    }
 
     /* Steps 4a, 5d. */
     return SetLengthProperty(cx, obj, index);
 }
 
 void
 js::ArrayShiftMoveElements(JSObject *obj)
 {
@@ -2195,16 +2206,19 @@ js::array_shift(JSContext *cx, unsigned 
         obj->getDenseInitializedLength() > 0 &&
         newlen < obj->getDenseCapacity() &&
         !ObjectMayHaveExtraIndexedProperties(obj))
     {
         args.rval().set(obj->getDenseElement(0));
         if (args.rval().isMagic(JS_ELEMENTS_HOLE))
             args.rval().setUndefined();
 
+        if (!obj->maybeCopyElementsForWrite(cx))
+            return false;
+
         obj->moveDenseElements(0, 1, obj->getDenseInitializedLength() - 1);
         obj->setDenseInitializedLength(obj->getDenseInitializedLength() - 1);
 
         if (!SetLengthProperty(cx, obj, newlen))
             return false;
 
         return js_SuppressDeletedProperty(cx, obj, INT_TO_JSID(newlen));
     }
@@ -2453,16 +2467,19 @@ js::array_splice_impl(JSContext *cx, uns
 
     if (itemCount < actualDeleteCount) {
         /* Step 12: the array is being shrunk. */
         uint32_t sourceIndex = actualStart + actualDeleteCount;
         uint32_t targetIndex = actualStart + itemCount;
         uint32_t finalLength = len - actualDeleteCount + itemCount;
 
         if (CanOptimizeForDenseStorage(obj, 0, len, cx)) {
+            if (!obj->maybeCopyElementsForWrite(cx))
+                return false;
+
             /* Steps 12(a)-(b). */
             obj->moveDenseElements(targetIndex, sourceIndex, len - sourceIndex);
 
             /*
              * Update the initialized length. Do so before shrinking so that we
              * can apply the write barrier to the old slots.
              */
             obj->setDenseInitializedLength(finalLength);
@@ -2534,16 +2551,18 @@ js::array_splice_impl(JSContext *cx, uns
                 JSObject::EnsureDenseResult res =
                     arr->ensureDenseElements(cx, arr->length(), itemCount - actualDeleteCount);
                 if (res == JSObject::ED_FAILED)
                     return false;
             }
         }
 
         if (CanOptimizeForDenseStorage(obj, len, itemCount - actualDeleteCount, cx)) {
+            if (!obj->maybeCopyElementsForWrite(cx))
+                return false;
             obj->moveDenseElements(actualStart + itemCount,
                                    actualStart + actualDeleteCount,
                                    len - (actualStart + actualDeleteCount));
             obj->setDenseInitializedLength(len + itemCount - actualDeleteCount);
         } else {
             RootedValue fromValue(cx);
             for (double k = len - actualDeleteCount; k > actualStart; k--) {
                 if (!CheckForInterrupt(cx))
@@ -3338,36 +3357,57 @@ js::NewDenseCopiedArray(JSContext *cx, u
 ArrayObject *
 js::NewDenseAllocatedArrayWithTemplate(JSContext *cx, uint32_t length, JSObject *templateObject)
 {
     gc::AllocKind allocKind = GuessArrayGCKind(length);
     JS_ASSERT(CanBeFinalizedInBackground(allocKind, &ArrayObject::class_));
     allocKind = GetBackgroundAllocKind(allocKind);
 
     RootedTypeObject type(cx, templateObject->type());
-    if (!type)
-        return nullptr;
-
     RootedShape shape(cx, templateObject->lastProperty());
-    if (!shape)
-        return nullptr;
 
     gc::InitialHeap heap = GetInitialHeap(GenericObject, &ArrayObject::class_);
     Rooted<ArrayObject *> arr(cx, JSObject::createArray(cx, allocKind, heap, shape, type, length));
     if (!arr)
         return nullptr;
 
     if (!EnsureNewArrayElements(cx, arr, length))
         return nullptr;
 
     probes::CreateObject(cx, arr);
 
     return arr;
 }
 
+JSObject *
+js::NewDenseCopyOnWriteArray(JSContext *cx, HandleObject templateObject, gc::InitialHeap heap)
+{
+    RootedTypeObject type(cx, templateObject->type());
+    RootedShape shape(cx, templateObject->lastProperty());
+
+    JS_ASSERT(!gc::IsInsideNursery(templateObject));
+    HeapSlot *elements = templateObject->getDenseElementsAllowCopyOnWrite();
+
+    JSObject *metadata = nullptr;
+    if (!NewObjectMetadata(cx, &metadata))
+        return nullptr;
+    if (metadata) {
+        shape = Shape::setObjectMetadata(cx, metadata, templateObject->getTaggedProto(), shape);
+        if (!shape)
+            return nullptr;
+    }
+
+    Rooted<ArrayObject *> arr(cx, JSObject::createArray(cx, heap, shape, type, elements));
+    if (!arr)
+        return nullptr;
+
+    probes::CreateObject(cx, arr);
+    return arr;
+}
+
 #ifdef DEBUG
 bool
 js_ArrayInfo(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     JSObject *obj;
 
     for (unsigned i = 0; i < args.length(); i++) {
--- a/js/src/jsarray.h
+++ b/js/src/jsarray.h
@@ -76,16 +76,20 @@ NewDenseCopiedArray(JSContext *cx, uint3
 extern ArrayObject *
 NewDenseCopiedArray(JSContext *cx, uint32_t length, const Value *values, JSObject *proto = nullptr,
                     NewObjectKind newKind = GenericObject);
 
 /* Create a dense array based on templateObject with the given length. */
 extern ArrayObject *
 NewDenseAllocatedArrayWithTemplate(JSContext *cx, uint32_t length, JSObject *templateObject);
 
+/* Create a dense array with the same copy-on-write elements as another object. */
+extern JSObject *
+NewDenseCopyOnWriteArray(JSContext *cx, HandleObject templateObject, gc::InitialHeap heap);
+
 /*
  * Determines whether a write to the given element on |obj| should fail because
  * |obj| is an Array with a non-writable length, and writing that element would
  * increase the length of the array.
  */
 extern bool
 WouldDefinePastNonwritableLength(ThreadSafeContext *cx,
                                  HandleObject obj, uint32_t index, bool strict,
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -3460,16 +3460,60 @@ types::FillBytecodeTypeMap(JSScript *scr
             bytecodeMap[added++] = script->pcToOffset(pc);
             if (added == script->nTypeSets())
                 break;
         }
     }
     JS_ASSERT(added == script->nTypeSets());
 }
 
+JSObject *
+types::GetOrFixupCopyOnWriteObject(JSContext *cx, HandleScript script, jsbytecode *pc)
+{
+    // Make sure that the template object for script/pc has a type indicating
+    // that the object and its copies have copy on write elements.
+    RootedObject obj(cx, script->getObject(GET_UINT32_INDEX(pc)));
+    JS_ASSERT(obj->is<ArrayObject>());
+    JS_ASSERT(obj->denseElementsAreCopyOnWrite());
+
+    if (obj->type()->hasAnyFlags(OBJECT_FLAG_COPY_ON_WRITE))
+        return obj;
+
+    RootedTypeObject type(cx, TypeScript::InitObject(cx, script, pc, JSProto_Array));
+    if (!type)
+        return nullptr;
+
+    type->addFlags(OBJECT_FLAG_COPY_ON_WRITE);
+
+    // Update type information in the initializer object type.
+    JS_ASSERT(obj->slotSpan() == 0);
+    for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) {
+        const Value &v = obj->getDenseElement(i);
+        AddTypePropertyId(cx, type, JSID_VOID, v);
+    }
+
+    obj->setType(type);
+    return obj;
+}
+
+JSObject *
+types::GetCopyOnWriteObject(JSScript *script, jsbytecode *pc)
+{
+    // GetOrFixupCopyOnWriteObject should already have been called for
+    // script/pc, ensuring that the template object has a type with the
+    // COPY_ON_WRITE flag. We don't assert this here, due to a corner case
+    // where this property doesn't hold. See jsop_newarray_copyonwrite in
+    // IonBuilder.
+    JSObject *obj = script->getObject(GET_UINT32_INDEX(pc));
+    JS_ASSERT(obj->is<ArrayObject>());
+    JS_ASSERT(obj->denseElementsAreCopyOnWrite());
+
+    return obj;
+}
+
 void
 types::TypeMonitorResult(JSContext *cx, JSScript *script, jsbytecode *pc, const js::Value &rval)
 {
     /* Allow the non-TYPESET scenario to simplify stubs used in compound opcodes. */
     if (!(js_CodeSpec[*pc].format & JOF_TYPESET))
         return;
 
     if (!script->hasBaselineScript())
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -479,24 +479,27 @@ enum MOZ_ENUM_TYPE(uint32_t) {
     OBJECT_FLAG_RUNONCE_INVALIDATED   = 0x00200000,
 
     /*
      * Whether objects with this type should be allocated directly in the
      * tenured heap.
      */
     OBJECT_FLAG_PRE_TENURE            = 0x00400000,
 
+    /* Whether objects with this type might have copy on write elements. */
+    OBJECT_FLAG_COPY_ON_WRITE         = 0x00800000,
+
     /*
      * Whether all properties of this object are considered unknown.
      * If set, all other flags in DYNAMIC_MASK will also be set.
      */
-    OBJECT_FLAG_UNKNOWN_PROPERTIES    = 0x00800000,
+    OBJECT_FLAG_UNKNOWN_PROPERTIES    = 0x01000000,
 
     /* Flags which indicate dynamic properties of represented objects. */
-    OBJECT_FLAG_DYNAMIC_MASK          = 0x00ff0000,
+    OBJECT_FLAG_DYNAMIC_MASK          = 0x01ff0000,
 
     /* Mask for objects created with unknown properties. */
     OBJECT_FLAG_UNKNOWN_MASK =
         OBJECT_FLAG_DYNAMIC_MASK
       | OBJECT_FLAG_SETS_MARKED_UNKNOWN
 };
 typedef uint32_t TypeObjectFlags;
 
@@ -1275,16 +1278,22 @@ class TypeScript
 #ifdef DEBUG
     void printTypes(JSContext *cx, HandleScript script) const;
 #endif
 };
 
 void
 FillBytecodeTypeMap(JSScript *script, uint32_t *bytecodeMap);
 
+JSObject *
+GetOrFixupCopyOnWriteObject(JSContext *cx, HandleScript script, jsbytecode *pc);
+
+JSObject *
+GetCopyOnWriteObject(JSScript *script, jsbytecode *pc);
+
 class RecompileInfo;
 
 // Allocate a CompilerOutput for a finished compilation and generate the type
 // constraints for the compilation. Returns whether the type constraints
 // still hold.
 bool
 FinishCompilation(JSContext *cx, HandleScript script, ExecutionMode executionMode,
                   CompilerConstraintList *constraints, RecompileInfo *precompileInfo);
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1360,18 +1360,21 @@ JSObject::sealOrFreeze(JSContext *cx, Ha
     // right now, so we must do this manually.  Neither the custom property
     // tree mutations nor the setGenericAttributes call in the above code will
     // do this for us.
     //
     // ArraySetLength also implements the capacity <= length invariant for
     // arrays with non-writable length.  We don't need to do anything special
     // for that, because capacity was zeroed out by preventExtensions.  (See
     // the assertion before the if-else above.)
-    if (it == FREEZE && obj->is<ArrayObject>())
+    if (it == FREEZE && obj->is<ArrayObject>()) {
+        if (!obj->maybeCopyElementsForWrite(cx))
+            return false;
         obj->getElementsHeader()->setNonwritableArrayLength();
+    }
 
     return true;
 }
 
 /* static */ bool
 JSObject::isSealedOrFrozen(JSContext *cx, HandleObject obj, ImmutabilityType it, bool *resultp)
 {
     bool extensible;
@@ -2310,25 +2313,58 @@ template bool
 js::XDRObjectLiteral(XDRState<XDR_ENCODE> *xdr, MutableHandleObject obj);
 
 template bool
 js::XDRObjectLiteral(XDRState<XDR_DECODE> *xdr, MutableHandleObject obj);
 
 JSObject *
 js::CloneObjectLiteral(JSContext *cx, HandleObject parent, HandleObject srcObj)
 {
-    Rooted<TypeObject*> typeObj(cx);
-    typeObj = cx->getNewType(&JSObject::class_, TaggedProto(cx->global()->getOrCreateObjectPrototype(cx)));
-
-    JS_ASSERT(srcObj->getClass() == &JSObject::class_);
-    AllocKind kind = GetBackgroundAllocKind(GuessObjectGCKind(srcObj->numFixedSlots()));
-    JS_ASSERT_IF(srcObj->isTenured(), kind == srcObj->tenuredGetAllocKind());
-
-    RootedShape shape(cx, srcObj->lastProperty());
-    return NewReshapedObject(cx, typeObj, parent, kind, shape);
+    if (srcObj->getClass() == &JSObject::class_) {
+        AllocKind kind = GetBackgroundAllocKind(GuessObjectGCKind(srcObj->numFixedSlots()));
+        JS_ASSERT_IF(srcObj->isTenured(), kind == srcObj->tenuredGetAllocKind());
+
+        JSObject *proto = cx->global()->getOrCreateObjectPrototype(cx);
+        if (!proto)
+            return nullptr;
+        Rooted<TypeObject*> typeObj(cx, cx->getNewType(&JSObject::class_, TaggedProto(proto)));
+        if (!typeObj)
+            return nullptr;
+
+        RootedShape shape(cx, srcObj->lastProperty());
+        return NewReshapedObject(cx, typeObj, parent, kind, shape);
+    }
+
+    JS_ASSERT(srcObj->is<ArrayObject>());
+    JS_ASSERT(srcObj->denseElementsAreCopyOnWrite());
+    JS_ASSERT(srcObj->getElementsHeader()->ownerObject() == srcObj);
+
+    size_t length = srcObj->as<ArrayObject>().length();
+    RootedObject res(cx, NewDenseAllocatedArray(cx, length, nullptr, MaybeSingletonObject));
+    if (!res)
+        return nullptr;
+
+    RootedId id(cx);
+    RootedValue value(cx);
+    for (size_t i = 0; i < length; i++) {
+        // The only markable values in copy on write arrays are atoms, which
+        // can be freely copied between compartments.
+        value = srcObj->getDenseElement(i);
+        JS_ASSERT_IF(value.isMarkable(),
+                     cx->runtime()->isAtomsZone(value.toGCThing()->tenuredZone()));
+
+        id = INT_TO_JSID(i);
+        if (!JSObject::defineGeneric(cx, res, id, value, nullptr, nullptr, JSPROP_ENUMERATE))
+            return nullptr;
+    }
+
+    if (!ObjectElements::MakeElementsCopyOnWrite(cx, res))
+        return nullptr;
+
+    return res;
 }
 
 struct JSObject::TradeGutsReserved {
     Vector<Value> avals;
     Vector<Value> bvals;
     int newafixed;
     int newbfixed;
     RootedShape newashape;
@@ -3058,16 +3094,19 @@ JSObject::shrinkSlots(ThreadSafeContext 
         return;  /* Leave slots at its old size. */
 
     obj->slots = newslots;
 }
 
 /* static */ bool
 JSObject::sparsifyDenseElement(ExclusiveContext *cx, HandleObject obj, uint32_t index)
 {
+    if (!obj->maybeCopyElementsForWrite(cx))
+        return false;
+
     RootedValue value(cx, obj->getDenseElement(index));
     JS_ASSERT(!value.isMagic(JS_ELEMENTS_HOLE));
 
     JSObject::removeDenseElementForSparseIndex(cx, obj, index);
 
     uint32_t slot = obj->slotSpan();
     if (!obj->addDataProperty(cx, INT_TO_JSID(index), slot, JSPROP_ENUMERATE)) {
         obj->setDenseElement(index, value);
@@ -3078,16 +3117,19 @@ JSObject::sparsifyDenseElement(Exclusive
     obj->initSlot(slot, value);
 
     return true;
 }
 
 /* static */ bool
 JSObject::sparsifyDenseElements(js::ExclusiveContext *cx, HandleObject obj)
 {
+    if (!obj->maybeCopyElementsForWrite(cx))
+        return false;
+
     uint32_t initialized = obj->getDenseInitializedLength();
 
     /* Create new properties with the value of non-hole dense elements. */
     for (uint32_t i = 0; i < initialized; i++) {
         if (obj->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE))
             continue;
 
         if (!sparsifyDenseElement(cx, obj, i))
@@ -3196,16 +3238,19 @@ JSObject::maybeDensifySparseElements(js:
     if (newInitializedLength >= NELEMENTS_LIMIT)
         return ED_SPARSE;
 
     /*
      * This object meets all necessary restrictions, convert all indexed
      * properties into dense elements.
      */
 
+    if (!obj->maybeCopyElementsForWrite(cx))
+        return ED_FAILED;
+
     if (newInitializedLength > obj->getDenseCapacity()) {
         if (!obj->growElements(cx, newInitializedLength))
             return ED_FAILED;
     }
 
     obj->ensureDenseInitializedLength(cx, newInitializedLength, 0);
 
     RootedValue value(cx);
@@ -3377,16 +3422,18 @@ JSObject::goodAllocated(uint32_t reqAllo
     return goodAllocated;
 }
 
 bool
 JSObject::growElements(ThreadSafeContext *cx, uint32_t reqCapacity)
 {
     JS_ASSERT(nonProxyIsExtensible());
     JS_ASSERT(canHaveNonEmptyElements());
+    if (denseElementsAreCopyOnWrite())
+        MOZ_CRASH();
 
     uint32_t oldCapacity = getDenseCapacity();
     JS_ASSERT(oldCapacity < reqCapacity);
 
     using mozilla::CheckedInt;
 
     CheckedInt<uint32_t> checkedOldAllocated =
         CheckedInt<uint32_t>(oldCapacity) + ObjectElements::VALUES_PER_HEADER;
@@ -3439,16 +3486,18 @@ JSObject::growElements(ThreadSafeContext
     return true;
 }
 
 void
 JSObject::shrinkElements(ThreadSafeContext *cx, uint32_t reqCapacity)
 {
     JS_ASSERT(cx->isThreadLocal(this));
     JS_ASSERT(canHaveNonEmptyElements());
+    if (denseElementsAreCopyOnWrite())
+        MOZ_CRASH();
 
     if (!hasDynamicElements())
         return;
 
     uint32_t oldCapacity = getDenseCapacity();
     JS_ASSERT(reqCapacity < oldCapacity);
 
     uint32_t oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER;
@@ -3466,16 +3515,48 @@ JSObject::shrinkElements(ThreadSafeConte
         cx->recoverFromOutOfMemory();
         return;  // Leave elements at its old size.
     }
 
     newheader->capacity = newCapacity;
     elements = newheader->elements();
 }
 
+/* static */ bool
+JSObject::CopyElementsForWrite(ThreadSafeContext *cx, JSObject *obj)
+{
+    JS_ASSERT(obj->denseElementsAreCopyOnWrite());
+
+    // The original owner of a COW elements array should never be modified.
+    JS_ASSERT(obj->getElementsHeader()->ownerObject() != obj);
+
+    uint32_t initlen = obj->getDenseInitializedLength();
+    uint32_t allocated = initlen + ObjectElements::VALUES_PER_HEADER;
+    uint32_t newAllocated = goodAllocated(allocated);
+
+    uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
+
+    if (newCapacity >= NELEMENTS_LIMIT)
+        return false;
+
+    ObjectElements *newheader = AllocateElements(cx, obj, newAllocated);
+    if (!newheader)
+        return false;
+    js_memcpy(newheader, obj->getElementsHeader(),
+              (ObjectElements::VALUES_PER_HEADER + initlen) * sizeof(Value));
+
+    newheader->capacity = newCapacity;
+    newheader->clearCopyOnWrite();
+    obj->elements = newheader->elements();
+
+    Debug_SetSlotRangeToCrashOnTouch(obj->elements + initlen, newCapacity - initlen);
+
+    return true;
+}
+
 bool
 js::SetClassAndProto(JSContext *cx, HandleObject obj,
                      const Class *clasp, Handle<js::TaggedProto> proto,
                      bool *succeeded)
 {
     /*
      * Regenerate shapes for all of the scopes along the old prototype chain,
      * in case any entries were filled by looking up through obj. Stop when a
@@ -3962,16 +4043,19 @@ CallAddPropertyHookDense(typename Execut
     if (clasp->addProperty != JS_PropertyStub) {
         if (mode == ParallelExecution)
             return false;
 
         ExclusiveContext *cx = cxArg->asExclusiveContext();
         if (!cx->shouldBeJSContext())
             return false;
 
+        if (!obj->maybeCopyElementsForWrite(cx))
+            return false;
+
         /* Make a local copy of value so addProperty can mutate its inout parameter. */
         RootedValue value(cx, nominal);
 
         Rooted<jsid> id(cx, INT_TO_JSID(index));
         if (!CallJSPropertyOp(cx->asJSContext(), clasp->addProperty, obj, id, &value)) {
             obj->setDenseElementHole(cx, index);
             return false;
         }
@@ -4099,16 +4183,19 @@ DefinePropertyOrElement(typename Executi
     /*
      * Clear any existing dense index after adding a sparse indexed property,
      * and investigate converting the object to dense indexes.
      */
     if (JSID_IS_INT(id)) {
         if (mode == ParallelExecution)
             return false;
 
+        if (!obj->maybeCopyElementsForWrite(cx))
+            return false;
+
         ExclusiveContext *ncx = cx->asExclusiveContext();
         uint32_t index = JSID_TO_INT(id);
         JSObject::removeDenseElementForSparseIndex(ncx, obj, index);
         JSObject::EnsureDenseResult result = JSObject::maybeDensifySparseElements(ncx, obj);
         if (result == JSObject::ED_FAILED)
             return false;
         if (result == JSObject::ED_OK) {
             JS_ASSERT(setter == JS_StrictPropertyStub);
@@ -5379,16 +5466,19 @@ baseops::SetPropertyHelper(typename Exec
             return false;
         if (definesPast) {
             /* Bail out of parallel execution if we are strict to throw. */
             if (mode == ParallelExecution)
                 return !strict;
             return true;
         }
 
+        if (!obj->maybeCopyElementsForWrite(cxArg))
+            return false;
+
         if (mode == ParallelExecution)
             return obj->setDenseElementIfHasType(index, vp);
 
         obj->setDenseElementWithType(cxArg->asJSContext(), index, vp);
         return true;
     }
 
     if (obj->is<ArrayObject>() && id == NameToId(cxArg->names().length)) {
@@ -5535,16 +5625,19 @@ baseops::DeleteGeneric(JSContext *cx, Ha
             return true;
         }
 
         if (!CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, id, succeeded))
             return false;
         if (!succeeded)
             return true;
 
+        if (!obj->maybeCopyElementsForWrite(cx))
+            return false;
+
         obj->setDenseElementHole(cx, JSID_TO_INT(id));
         return js_SuppressDeletedProperty(cx, obj, id);
     }
 
     if (!shape->configurable()) {
         *succeeded = false;
         return true;
     }
@@ -6248,17 +6341,18 @@ js_DumpBacktrace(JSContext *cx)
 void
 JSObject::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::ObjectsExtraSizes *sizes)
 {
     if (hasDynamicSlots())
         sizes->mallocHeapSlots += mallocSizeOf(slots);
 
     if (hasDynamicElements()) {
         js::ObjectElements *elements = getElementsHeader();
-        sizes->mallocHeapElementsNonAsmJS += mallocSizeOf(elements);
+        if (!elements->isCopyOnWrite() || elements->ownerObject() == this)
+            sizes->mallocHeapElementsNonAsmJS += mallocSizeOf(elements);
     }
 
     // Other things may be measured in the future if DMD indicates it is worthwhile.
     if (is<JSFunction>() ||
         is<JSObject>() ||
         is<ArrayObject>() ||
         is<CallObject>() ||
         is<RegExpObject>() ||
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -241,16 +241,30 @@ class JSObject : public js::ObjectImpl
     /* Make an array object with the specified initial state. */
     static inline js::ArrayObject *createArray(js::ExclusiveContext *cx,
                                                js::gc::AllocKind kind,
                                                js::gc::InitialHeap heap,
                                                js::HandleShape shape,
                                                js::HandleTypeObject type,
                                                uint32_t length);
 
+    /* Make an array object with the specified initial state and elements. */
+    static inline js::ArrayObject *createArray(js::ExclusiveContext *cx,
+                                               js::gc::InitialHeap heap,
+                                               js::HandleShape shape,
+                                               js::HandleTypeObject type,
+                                               js::HeapSlot *elements);
+
+  private:
+    // Helper for the above two methods.
+    static inline JSObject *
+    createArrayInternal(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
+                        js::HandleShape shape, js::HandleTypeObject type);
+  public:
+
     /*
      * Remove the last property of an object, provided that it is safe to do so
      * (the shape and previous shape do not carry conflicting information about
      * the object itself).
      */
     inline void removeLastProperty(js::ExclusiveContext *cx);
     inline bool canRemoveLastProperty();
 
@@ -385,16 +399,17 @@ class JSObject : public js::ObjectImpl
      */
     void prepareSlotRangeForOverwrite(size_t start, size_t end) {
         for (size_t i = start; i < end; i++)
             getSlotAddressUnchecked(i)->js::HeapSlot::~HeapSlot();
     }
 
     void prepareElementRangeForOverwrite(size_t start, size_t end) {
         JS_ASSERT(end <= getDenseInitializedLength());
+        JS_ASSERT(!denseElementsAreCopyOnWrite());
         for (size_t i = start; i < end; i++)
             elements[i].js::HeapSlot::~HeapSlot();
     }
 
     static bool rollbackProperties(js::ExclusiveContext *cx, js::HandleObject obj,
                                    uint32_t slotSpan);
 
     void nativeSetSlot(uint32_t slot, const js::Value &value) {
@@ -592,16 +607,17 @@ class JSObject : public js::ObjectImpl
         return isSealedOrFrozen(cx, obj, FREEZE, resultp);
     }
 
     /* toString support. */
     static const char *className(JSContext *cx, js::HandleObject obj);
 
     /* Accessors for elements. */
     bool ensureElements(js::ThreadSafeContext *cx, uint32_t capacity) {
+        JS_ASSERT(!denseElementsAreCopyOnWrite());
         if (capacity > getDenseCapacity())
             return growElements(cx, capacity);
         return true;
     }
 
     static uint32_t goodAllocated(uint32_t n, uint32_t length);
     bool growElements(js::ThreadSafeContext *cx, uint32_t newcap);
     void shrinkElements(js::ThreadSafeContext *cx, uint32_t cap);
@@ -612,39 +628,50 @@ class JSObject : public js::ObjectImpl
     }
 
     uint32_t getDenseCapacity() {
         JS_ASSERT(isNative());
         JS_ASSERT(getElementsHeader()->capacity >= getElementsHeader()->initializedLength);
         return getElementsHeader()->capacity;
     }
 
+    static bool CopyElementsForWrite(js::ThreadSafeContext *cx, JSObject *obj);
+
+    bool maybeCopyElementsForWrite(js::ThreadSafeContext *cx) {
+        if (denseElementsAreCopyOnWrite())
+            return CopyElementsForWrite(cx, this);
+        return true;
+    }
+
   private:
     inline void ensureDenseInitializedLengthNoPackedCheck(js::ThreadSafeContext *cx,
                                                           uint32_t index, uint32_t extra);
 
   public:
     void setDenseInitializedLength(uint32_t length) {
         JS_ASSERT(isNative());
         JS_ASSERT(length <= getDenseCapacity());
+        JS_ASSERT(!denseElementsAreCopyOnWrite());
         prepareElementRangeForOverwrite(length, getElementsHeader()->initializedLength);
         getElementsHeader()->initializedLength = length;
     }
 
     inline void ensureDenseInitializedLength(js::ExclusiveContext *cx,
                                              uint32_t index, uint32_t extra);
     inline void ensureDenseInitializedLengthPreservePackedFlag(js::ThreadSafeContext *cx,
                                                                uint32_t index, uint32_t extra);
     void setDenseElement(uint32_t index, const js::Value &val) {
         JS_ASSERT(isNative() && index < getDenseInitializedLength());
+        JS_ASSERT(!denseElementsAreCopyOnWrite());
         elements[index].set(this, js::HeapSlot::Element, index, val);
     }
 
     void initDenseElement(uint32_t index, const js::Value &val) {
         JS_ASSERT(isNative() && index < getDenseInitializedLength());
+        JS_ASSERT(!denseElementsAreCopyOnWrite());
         elements[index].init(this, js::HeapSlot::Element, index, val);
     }
 
     void setDenseElementMaybeConvertDouble(uint32_t index, const js::Value &val) {
         if (val.isInt32() && shouldConvertDoubleElements())
             setDenseElement(index, js::DoubleValue(val.toInt32()));
         else
             setDenseElement(index, val);
@@ -658,38 +685,41 @@ class JSObject : public js::ObjectImpl
     inline void setDenseElementHole(js::ExclusiveContext *cx, uint32_t index);
     static inline void removeDenseElementForSparseIndex(js::ExclusiveContext *cx,
                                                         js::HandleObject obj, uint32_t index);
 
     inline js::Value getDenseOrTypedArrayElement(uint32_t idx);
 
     void copyDenseElements(uint32_t dstStart, const js::Value *src, uint32_t count) {
         JS_ASSERT(dstStart + count <= getDenseCapacity());
+        JS_ASSERT(!denseElementsAreCopyOnWrite());
         JSRuntime *rt = runtimeFromMainThread();
         if (JS::IsIncrementalBarrierNeeded(rt)) {
             JS::Zone *zone = this->zone();
             for (uint32_t i = 0; i < count; ++i)
                 elements[dstStart + i].set(zone, this, js::HeapSlot::Element, dstStart + i, src[i]);
         } else {
             memcpy(&elements[dstStart], src, count * sizeof(js::HeapSlot));
             DenseRangeWriteBarrierPost(rt, this, dstStart, count);
         }
     }
 
     void initDenseElements(uint32_t dstStart, const js::Value *src, uint32_t count) {
         JS_ASSERT(dstStart + count <= getDenseCapacity());
+        JS_ASSERT(!denseElementsAreCopyOnWrite());
         memcpy(&elements[dstStart], src, count * sizeof(js::HeapSlot));
         DenseRangeWriteBarrierPost(runtimeFromMainThread(), this, dstStart, count);
     }
 
     void initDenseElementsUnbarriered(uint32_t dstStart, const js::Value *src, uint32_t count);
 
     void moveDenseElements(uint32_t dstStart, uint32_t srcStart, uint32_t count) {
         JS_ASSERT(dstStart + count <= getDenseCapacity());
         JS_ASSERT(srcStart + count <= getDenseInitializedLength());
+        JS_ASSERT(!denseElementsAreCopyOnWrite());
 
         /*
          * Using memmove here would skip write barriers. Also, we need to consider
          * an array containing [A, B, C], in the following situation:
          *
          * 1. Incremental GC marks slot 0 of array (i.e., A), then returns to JS code.
          * 2. JS code moves slots 1..2 into slots 0..1, so it contains [B, C, C].
          * 3. Incremental GC finishes by marking slots 1 and 2 (i.e., C).
@@ -718,29 +748,35 @@ class JSObject : public js::ObjectImpl
         }
     }
 
     void moveDenseElementsNoPreBarrier(uint32_t dstStart, uint32_t srcStart, uint32_t count) {
         JS_ASSERT(!shadowZone()->needsIncrementalBarrier());
 
         JS_ASSERT(dstStart + count <= getDenseCapacity());
         JS_ASSERT(srcStart + count <= getDenseCapacity());
+        JS_ASSERT(!denseElementsAreCopyOnWrite());
 
         memmove(elements + dstStart, elements + srcStart, count * sizeof(js::Value));
         DenseRangeWriteBarrierPost(runtimeFromMainThread(), this, dstStart, count);
     }
 
     bool shouldConvertDoubleElements() {
         JS_ASSERT(getClass()->isNative());
         return getElementsHeader()->shouldConvertDoubleElements();
     }
 
     inline void setShouldConvertDoubleElements();
     inline void clearShouldConvertDoubleElements();
 
+    bool denseElementsAreCopyOnWrite() {
+        JS_ASSERT(isNative());
+        return getElementsHeader()->isCopyOnWrite();
+    }
+
     /* Packed information for this object's elements. */
     inline bool writeToIndexWouldMarkNotPacked(uint32_t index);
     inline void markDenseElementsNotPacked(js::ExclusiveContext *cx);
 
     /*
      * ensureDenseElements ensures that the object can hold at least
      * index + extra elements. It returns ED_OK on success, ED_FAILED on
      * failure to grow the array, ED_SPARSE when the object is too sparse to
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -208,16 +208,17 @@ JSObject::markDenseElementsNotPacked(js:
     MarkTypeObjectFlags(cx, this, js::types::OBJECT_FLAG_NON_PACKED);
 }
 
 inline void
 JSObject::ensureDenseInitializedLengthNoPackedCheck(js::ThreadSafeContext *cx, uint32_t index,
                                                     uint32_t extra)
 {
     JS_ASSERT(cx->isThreadLocal(this));
+    JS_ASSERT(!denseElementsAreCopyOnWrite());
 
     /*
      * Ensure that the array's contents have been initialized up to index, and
      * mark the elements through 'index + extra' as initialized in preparation
      * for a write.
      */
     JS_ASSERT(index + extra <= getDenseCapacity());
     uint32_t &initlen = getElementsHeader()->initializedLength;
@@ -250,16 +251,17 @@ JSObject::ensureDenseInitializedLengthPr
     ensureDenseInitializedLengthNoPackedCheck(cx, index, extra);
 }
 
 JSObject::EnsureDenseResult
 JSObject::extendDenseElements(js::ThreadSafeContext *cx,
                               uint32_t requiredCapacity, uint32_t extra)
 {
     JS_ASSERT(cx->isThreadLocal(this));
+    JS_ASSERT(!denseElementsAreCopyOnWrite());
 
     /*
      * Don't grow elements for non-extensible objects or watched objects. Dense
      * elements can be added/written with no extensible or watchpoint checks as
      * long as there is capacity for them.
      */
     if (!nonProxyIsExtensible() || watched()) {
         JS_ASSERT(getDenseCapacity() == 0);
@@ -289,16 +291,19 @@ JSObject::extendDenseElements(js::Thread
     return ED_OK;
 }
 
 inline JSObject::EnsureDenseResult
 JSObject::ensureDenseElementsNoPackedCheck(js::ThreadSafeContext *cx, uint32_t index, uint32_t extra)
 {
     JS_ASSERT(isNative());
 
+    if (!maybeCopyElementsForWrite(cx))
+        return ED_FAILED;
+
     uint32_t currentCapacity = getDenseCapacity();
 
     uint32_t requiredCapacity;
     if (extra == 1) {
         /* Optimize for the common case. */
         if (index < currentCapacity) {
             ensureDenseInitializedLengthNoPackedCheck(cx, index, 1);
             return ED_OK;
@@ -354,16 +359,17 @@ JSObject::getDenseOrTypedArrayElement(ui
 
 inline void
 JSObject::initDenseElementsUnbarriered(uint32_t dstStart, const js::Value *src, uint32_t count) {
     /*
      * For use by parallel threads, which since they cannot see nursery
      * things do not require a barrier.
      */
     JS_ASSERT(dstStart + count <= getDenseCapacity());
+    JS_ASSERT(!denseElementsAreCopyOnWrite());
 #if defined(DEBUG) && defined(JSGC_GENERATIONAL)
     /*
      * This asserts a global invariant: parallel code does not
      * observe objects inside the generational GC's nursery.
      */
     JS_ASSERT(!js::gc::IsInsideGGCNursery(this));
     for (uint32_t index = 0; index < count; ++index) {
         const JS::Value& value = src[index];
@@ -545,62 +551,99 @@ JSObject::create(js::ExclusiveContext *c
     if (span)
         obj->initializeSlotRange(0, span);
 
     js::gc::TraceCreateObject(obj);
 
     return obj;
 }
 
-/* static */ inline js::ArrayObject *
-JSObject::createArray(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
-                      js::HandleShape shape, js::HandleTypeObject type,
-                      uint32_t length)
+/* static */ inline JSObject *
+JSObject::createArrayInternal(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
+                              js::HandleShape shape, js::HandleTypeObject type)
 {
+    // Create a new array and initialize everything except for its elements.
     JS_ASSERT(shape && type);
     JS_ASSERT(type->clasp() == shape->getObjectClass());
     JS_ASSERT(type->clasp() == &js::ArrayObject::class_);
     JS_ASSERT_IF(type->clasp()->finalize, heap == js::gc::TenuredHeap);
 
-    /*
-     * Arrays use their fixed slots to store elements, and must have enough
-     * space for the elements header and also be marked as having no space for
-     * named properties stored in those fixed slots.
-     */
+    // Arrays can use their fixed slots to store elements, so can't have shapes
+    // which allow named properties to be stored in the fixed slots.
     JS_ASSERT(shape->numFixedSlots() == 0);
+
     size_t nDynamicSlots = dynamicSlotsCount(0, shape->slotSpan(), type->clasp());
     JSObject *obj = js::NewGCObject<js::CanGC>(cx, kind, nDynamicSlots, heap);
     if (!obj)
         return nullptr;
 
+    obj->shape_.init(shape);
+    obj->type_.init(type);
+
+    return obj;
+}
+
+/* static */ inline js::ArrayObject *
+JSObject::createArray(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
+                      js::HandleShape shape, js::HandleTypeObject type,
+                      uint32_t length)
+{
+    JSObject *obj = createArrayInternal(cx, kind, heap, shape, type);
+    if (!obj)
+        return nullptr;
+
     uint32_t capacity = js::gc::GetGCKindSlots(kind) - js::ObjectElements::VALUES_PER_HEADER;
 
-    obj->shape_.init(shape);
-    obj->type_.init(type);
     obj->setFixedElements();
     new (obj->getElementsHeader()) js::ObjectElements(capacity, length);
 
     size_t span = shape->slotSpan();
     if (span)
         obj->initializeSlotRange(0, span);
 
     js::gc::TraceCreateObject(obj);
 
     return &obj->as<js::ArrayObject>();
 }
 
+/* static */ inline js::ArrayObject *
+JSObject::createArray(js::ExclusiveContext *cx, js::gc::InitialHeap heap,
+                      js::HandleShape shape, js::HandleTypeObject type,
+                      js::HeapSlot *elements)
+{
+    // Use the smallest allocation kind for the array, as it can't have any
+    // fixed slots (see assert in the above function) and will not be using its
+    // fixed elements.
+    js::gc::AllocKind kind = js::gc::FINALIZE_OBJECT0_BACKGROUND;
+
+    JSObject *obj = createArrayInternal(cx, kind, heap, shape, type);
+    if (!obj)
+        return nullptr;
+
+    obj->elements = elements;
+
+    size_t span = shape->slotSpan();
+    if (span)
+        obj->initializeSlotRange(0, span);
+
+    js::gc::TraceCreateObject(obj);
+
+    return &obj->as<js::ArrayObject>();
+}
+
 inline void
 JSObject::finish(js::FreeOp *fop)
 {
     if (hasDynamicSlots())
         fop->free_(slots);
 
     if (hasDynamicElements()) {
         js::ObjectElements *elements = getElementsHeader();
-        fop->free_(elements);
+        if (!elements->isCopyOnWrite() || elements->ownerObject() == this)
+            fop->free_(elements);
     }
 }
 
 /* static */ inline bool
 JSObject::hasProperty(JSContext *cx, js::HandleObject obj,
                       js::HandleId id, bool *foundp)
 {
     JS::RootedObject pobj(cx);
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -1550,17 +1550,18 @@ ExpressionDecompiler::decompilePC(jsbyte
         return decompilePCForStackOperand(pc, -int32_t(GET_ARGC(pc) + 2)) &&
                write("(...)");
       case JSOP_SPREADCALL:
         return decompilePCForStackOperand(pc, -int32_t(3)) &&
                write("(...)");
       case JSOP_NEWARRAY:
         return write("[]");
       case JSOP_REGEXP:
-      case JSOP_OBJECT: {
+      case JSOP_OBJECT:
+      case JSOP_NEWARRAY_COPYONWRITE: {
         JSObject *obj = (op == JSOP_REGEXP)
                         ? script->getRegExp(GET_UINT32_INDEX(pc))
                         : script->getObject(GET_UINT32_INDEX(pc));
         RootedValue objv(cx, ObjectValue(*obj));
         JSString *str = ValueToSource(cx, objv);
         if (!str)
             return false;
         return write(str);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1591,17 +1591,16 @@ CASE(JSOP_UNUSED46)
 CASE(JSOP_UNUSED47)
 CASE(JSOP_UNUSED48)
 CASE(JSOP_UNUSED49)
 CASE(JSOP_UNUSED50)
 CASE(JSOP_UNUSED51)
 CASE(JSOP_UNUSED52)
 CASE(JSOP_UNUSED57)
 CASE(JSOP_UNUSED83)
-CASE(JSOP_UNUSED102)
 CASE(JSOP_UNUSED103)
 CASE(JSOP_UNUSED104)
 CASE(JSOP_UNUSED105)
 CASE(JSOP_UNUSED107)
 CASE(JSOP_UNUSED124)
 CASE(JSOP_UNUSED125)
 CASE(JSOP_UNUSED126)
 CASE(JSOP_UNUSED138)
@@ -3058,16 +3057,32 @@ CASE(JSOP_NEWARRAY)
     obj = NewDenseAllocatedArray(cx, count, nullptr, newKind);
     if (!obj || !SetInitializerObjectType(cx, script, REGS.pc, obj, newKind))
         goto error;
 
     PUSH_OBJECT(*obj);
 }
 END_CASE(JSOP_NEWARRAY)
 
+CASE(JSOP_NEWARRAY_COPYONWRITE)
+{
+    RootedObject &baseobj = rootObject0;
+    baseobj = types::GetOrFixupCopyOnWriteObject(cx, script, REGS.pc);
+    if (!baseobj)
+        goto error;
+
+    RootedObject &obj = rootObject1;
+    obj = NewDenseCopyOnWriteArray(cx, baseobj, gc::DefaultHeap);
+    if (!obj)
+        goto error;
+
+    PUSH_OBJECT(*obj);
+}
+END_CASE(JSOP_NEWARRAY_COPYONWRITE)
+
 CASE(JSOP_NEWOBJECT)
 {
     RootedObject &baseobj = rootObject0;
     baseobj = script->getObject(REGS.pc);
 
     RootedObject &obj = rootObject1;
     NewObjectKind newKind = UseNewTypeForInitializer(script, REGS.pc, baseobj->getClass());
     obj = CopyInitializerObject(cx, baseobj, newKind);
--- a/js/src/vm/ObjectImpl.cpp
+++ b/js/src/vm/ObjectImpl.cpp
@@ -92,26 +92,48 @@ ObjectElements::ConvertElementsToDoubles
      * elements converted to doubles, and arrays never have empty elements.
      */
     HeapSlot *elementsHeapPtr = (HeapSlot *) elementsPtr;
     JS_ASSERT(elementsHeapPtr != emptyObjectElements);
 
     ObjectElements *header = ObjectElements::fromElements(elementsHeapPtr);
     JS_ASSERT(!header->shouldConvertDoubleElements());
 
+    // Note: the elements can be mutated in place even for copy on write
+    // arrays. See comment on ObjectElements.
     Value *vp = (Value *) elementsPtr;
     for (size_t i = 0; i < header->initializedLength; i++) {
         if (vp[i].isInt32())
             vp[i].setDouble(vp[i].toInt32());
     }
 
     header->setShouldConvertDoubleElements();
     return true;
 }
 
+/* static */ bool
+ObjectElements::MakeElementsCopyOnWrite(ExclusiveContext *cx, JSObject *obj)
+{
+    // Make sure there is enough room for the owner object pointer at the end
+    // of the elements.
+    JS_STATIC_ASSERT(sizeof(HeapSlot) >= sizeof(HeapPtrObject));
+    if (!obj->ensureElements(cx, obj->getDenseInitializedLength() + 1))
+        return false;
+
+    ObjectElements *header = obj->getElementsHeader();
+
+    // Note: this method doesn't update type information to indicate that the
+    // elements might be copy on write. Handling this is left to the caller.
+    JS_ASSERT(!header->isCopyOnWrite());
+    header->flags |= COPY_ON_WRITE;
+
+    header->ownerObject().init(obj);
+    return true;
+}
+
 #ifdef DEBUG
 void
 js::ObjectImpl::checkShapeConsistency()
 {
     static int throttle = -1;
     if (throttle < 0) {
         if (const char *var = getenv("JS_CHECK_SHAPE_THROTTLE"))
             throttle = atoi(var);
@@ -289,17 +311,31 @@ js::ObjectImpl::markChildren(JSTracer *t
 
     const Class *clasp = type_->clasp();
     JSObject *obj = asObjectPtr();
     if (clasp->trace)
         clasp->trace(trc, obj);
 
     if (shape_->isNative()) {
         MarkObjectSlots(trc, obj, 0, obj->slotSpan());
-        gc::MarkArraySlots(trc, obj->getDenseInitializedLength(), obj->getDenseElements(), "objectElements");
+
+        do {
+            if (obj->denseElementsAreCopyOnWrite()) {
+                HeapPtrObject &owner = getElementsHeader()->ownerObject();
+                if (owner != this) {
+                    MarkObject(trc, &owner, "objectElementsOwner");
+                    break;
+                }
+            }
+
+            gc::MarkArraySlots(trc,
+                               obj->getDenseInitializedLength(),
+                               obj->getDenseElementsAllowCopyOnWrite(),
+                               "objectElements");
+        } while (false);
     }
 }
 
 void
 PropDesc::trace(JSTracer *trc)
 {
     gc::MarkValueRoot(trc, &value_, "PropDesc value");
     gc::MarkValueRoot(trc, &get_, "PropDesc get");
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -164,21 +164,31 @@ ArraySetLength(typename ExecutionModeTra
  * Elements do not track property creation order, so enumerating the elements
  * of an object does not necessarily visit indexes in the order they were
  * created.
  */
 class ObjectElements
 {
   public:
     enum Flags {
+        // Integers written to these elements must be converted to doubles.
         CONVERT_DOUBLE_ELEMENTS     = 0x1,
 
         // Present only if these elements correspond to an array with
         // non-writable length; never present for non-arrays.
-        NONWRITABLE_ARRAY_LENGTH    = 0x2
+        NONWRITABLE_ARRAY_LENGTH    = 0x2,
+
+        // These elements are shared with another object and must be copied
+        // before they can be changed. A pointer to the original owner of the
+        // elements, which is immutable, is stored immediately after the
+        // elements data. There is one case where elements can be written to
+        // before being copied: when setting the CONVERT_DOUBLE_ELEMENTS flag
+        // the shared elements may change (from ints to doubles) without
+        // making a copy first.
+        COPY_ON_WRITE               = 0x4
     };
 
   private:
     friend class ::JSObject;
     friend class ObjectImpl;
     friend class ArrayObject;
     friend class Nursery;
     friend class gc::ForkJoinNursery;
@@ -205,54 +215,73 @@ class ObjectElements
 
     /* 'length' property of array objects, unused for other objects. */
     uint32_t length;
 
     bool shouldConvertDoubleElements() const {
         return flags & CONVERT_DOUBLE_ELEMENTS;
     }
     void setShouldConvertDoubleElements() {
+        // Note: allow isCopyOnWrite() here, see comment above.
         flags |= CONVERT_DOUBLE_ELEMENTS;
     }
     void clearShouldConvertDoubleElements() {
+        JS_ASSERT(!isCopyOnWrite());
         flags &= ~CONVERT_DOUBLE_ELEMENTS;
     }
     bool hasNonwritableArrayLength() const {
         return flags & NONWRITABLE_ARRAY_LENGTH;
     }
     void setNonwritableArrayLength() {
+        JS_ASSERT(!isCopyOnWrite());
         flags |= NONWRITABLE_ARRAY_LENGTH;
     }
+    bool isCopyOnWrite() const {
+        return flags & COPY_ON_WRITE;
+    }
+    void clearCopyOnWrite() {
+        JS_ASSERT(isCopyOnWrite());
+        flags &= ~COPY_ON_WRITE;
+    }
 
   public:
     MOZ_CONSTEXPR ObjectElements(uint32_t capacity, uint32_t length)
       : flags(0), initializedLength(0), capacity(capacity), length(length)
     {}
 
     HeapSlot *elements() {
         return reinterpret_cast<HeapSlot*>(uintptr_t(this) + sizeof(ObjectElements));
     }
+    const HeapSlot *elements() const {
+        return reinterpret_cast<const HeapSlot*>(uintptr_t(this) + sizeof(ObjectElements));
+    }
     static ObjectElements * fromElements(HeapSlot *elems) {
         return reinterpret_cast<ObjectElements*>(uintptr_t(elems) - sizeof(ObjectElements));
     }
 
+    HeapPtrObject &ownerObject() const {
+        JS_ASSERT(isCopyOnWrite());
+        return *(HeapPtrObject *)(&elements()[initializedLength]);
+    }
+
     static int offsetOfFlags() {
         return int(offsetof(ObjectElements, flags)) - int(sizeof(ObjectElements));
     }
     static int offsetOfInitializedLength() {
         return int(offsetof(ObjectElements, initializedLength)) - int(sizeof(ObjectElements));
     }
     static int offsetOfCapacity() {
         return int(offsetof(ObjectElements, capacity)) - int(sizeof(ObjectElements));
     }
     static int offsetOfLength() {
         return int(offsetof(ObjectElements, length)) - int(sizeof(ObjectElements));
     }
 
     static bool ConvertElementsToDoubles(JSContext *cx, uintptr_t elements);
+    static bool MakeElementsCopyOnWrite(ExclusiveContext *cx, JSObject *obj);
 
     // This is enough slots to store an object of this class. See the static
     // assertion below.
     static const size_t VALUES_PER_HEADER = 2;
 };
 
 static_assert(ObjectElements::VALUES_PER_HEADER * sizeof(HeapSlot) == sizeof(ObjectElements),
               "ObjectElements doesn't fit in the given number of slots");
@@ -401,17 +430,22 @@ class ObjectImpl : public gc::BarrieredC
 
     // Attempt to change the [[Extensible]] bit on |obj| to false.  Callers
     // must ensure that |obj| is currently extensible before calling this!
     static bool
     preventExtensions(JSContext *cx, Handle<ObjectImpl*> obj);
 
     HeapSlotArray getDenseElements() {
         JS_ASSERT(isNative());
-        return HeapSlotArray(elements);
+        return HeapSlotArray(elements, !getElementsHeader()->isCopyOnWrite());
+    }
+    HeapSlotArray getDenseElementsAllowCopyOnWrite() {
+        // Backdoor allowing direct access to copy on write elements.
+        JS_ASSERT(isNative());
+        return HeapSlotArray(elements, true);
     }
     const Value &getDenseElement(uint32_t idx) {
         JS_ASSERT(isNative());
         MOZ_ASSERT(idx < getDenseInitializedLength());
         return elements[idx];
     }
     bool containsDenseElement(uint32_t idx) {
         JS_ASSERT(isNative());
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -877,29 +877,38 @@ 1234567890123456789012345678901234567890
      * Pops the top three values on the stack as 'val', 'id' and 'obj', defines
      * 'id' setter of 'obj' as 'val', pushes 'obj' onto the stack.
      *   Category: Literals
      *   Type: Object
      *   Operands:
      *   Stack: obj, id, val => obj
      */ \
     macro(JSOP_INITELEM_SETTER, 100, "initelem_setter",   NULL, 1,  3,  1, JOF_BYTE|JOF_ELEM|JOF_SET|JOF_DETECTING) \
-    \
     /*
      * Pushes the call site object specified by objectIndex onto the stack. Defines the raw
      * property specified by objectIndex + 1 on the call site object and freezes both the call site
      * object as well as its raw property.
      *   Category: Literals
      *   Type: Object
      *   Operands: uint32_t objectIndex
      *   Stack: => obj
      */ \
     macro(JSOP_CALLSITEOBJ,     101, "callsiteobj",     NULL,         5,  0,  1,  JOF_OBJECT) \
     \
-    macro(JSOP_UNUSED102,  102, "unused102",   NULL,         1,  0,  0,  JOF_BYTE) \
+    /*
+     * Pushes a newly created array onto the stack, whose elements are the same
+     * as that of a template object's copy on write elements.
+     *
+     *   Category: Literals
+     *   Type: Array
+     *   Operands: uint32_t objectIndex
+     *   Stack: => obj
+     */ \
+    macro(JSOP_NEWARRAY_COPYONWRITE, 102, "newarray_copyonwrite", NULL, 5, 0, 1, JOF_OBJECT) \
+    \
     macro(JSOP_UNUSED103,  103, "unused103",   NULL,         1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED104,  104, "unused104",   NULL,         1,  0,  0,  JOF_BYTE) \
     macro(JSOP_UNUSED105,  105, "unused105",   NULL,         1,  0,  0,  JOF_BYTE) \
     \
     /*
      * This opcode precedes every labeled statement. It's a no-op.
      *
      * 'offset' is the offset to the next instruction after this statement,
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -23,17 +23,17 @@ namespace js {
  * versions.  If deserialization fails, the data should be invalidated if
  * possible.
  *
  * When you change this, run make_opcode_doc.py and copy the new output into
  * this wiki page:
  *
  *  https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
  */
-static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 180);
+static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 181);
 
 class XDRBuffer {
   public:
     explicit XDRBuffer(JSContext *cx)
       : context(cx), base(nullptr), cursor(nullptr), limit(nullptr) { }
 
     JSContext *cx() const {
         return context;