Bug 1146597 - Add unboxed arrays for JSOP_NEWARRAY arrays, and shell option for using them, r=jandem.
authorBrian Hackett <bhackett1024@gmail.com>
Sun, 03 May 2015 08:14:04 -0700
changeset 273532 020c6a559e3ae53d3ae5d807880787cb01cdef59
parent 273531 599a8abf54a3814db5c09a26efa1cdb17db94246
child 273533 d9eec87368cefacd542212488b55a33f8aa2e175
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1146597
milestone40.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 1146597 - Add unboxed arrays for JSOP_NEWARRAY arrays, and shell option for using them, r=jandem.
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/jit-test/tests/ion/recover-arrays.js
js/src/jit/BaselineCompiler.cpp
js/src/jit/BaselineCompiler.h
js/src/jit/BaselineIC.cpp
js/src/jit/BaselineIC.h
js/src/jit/BaselineInspector.cpp
js/src/jit/BaselineInspector.h
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/LIR-Common.h
js/src/jit/LOpcodes.h
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MCallOptimize.cpp
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jit/MacroAssembler.cpp
js/src/jit/MacroAssembler.h
js/src/jit/Recover.cpp
js/src/jit/ScalarReplacement.cpp
js/src/jit/VMFunctions.cpp
js/src/jit/VMFunctions.h
js/src/jit/x64/MacroAssembler-x64.cpp
js/src/jsapi.h
js/src/jsarray.cpp
js/src/jsgc.h
js/src/jsiter.cpp
js/src/jsobjinlines.h
js/src/shell/js.cpp
js/src/vm/ArrayObject-inl.h
js/src/vm/Interpreter-inl.h
js/src/vm/Interpreter.cpp
js/src/vm/Interpreter.h
js/src/vm/NativeObject.cpp
js/src/vm/ObjectGroup.cpp
js/src/vm/Opcodes.h
js/src/vm/Runtime-inl.h
js/src/vm/TypeInference.cpp
js/src/vm/UnboxedObject.cpp
js/src/vm/UnboxedObject.h
js/src/vm/Xdr.h
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -6425,17 +6425,17 @@ BytecodeEmitter::emitCallOrNew(ParseNode
     bool oldEmittingForInit = emittingForInit;
     emittingForInit = false;
     if (!spread) {
         for (ParseNode* pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) {
             if (!emitTree(pn3))
                 return false;
         }
     } else {
-        if (!emitArray(pn2->pn_next, argc))
+        if (!emitArray(pn2->pn_next, argc, JSOP_SPREADCALLARRAY))
             return false;
     }
     emittingForInit = oldEmittingForInit;
 
     if (!spread) {
         if (!emitCall(pn->getOp(), argc, pn))
             return false;
     } else {
@@ -6892,37 +6892,38 @@ BytecodeEmitter::emitArrayComp(ParseNode
 
 bool
 BytecodeEmitter::emitSpread()
 {
     return emitForOf(STMT_SPREAD, nullptr, -1);
 }
 
 bool
-BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count)
+BytecodeEmitter::emitArray(ParseNode* pn, uint32_t count, JSOp op)
 {
     /*
      * Emit code for [a, b, c] that is equivalent to constructing a new
      * array and in source order evaluating each element value and adding
      * it to the array, without invoking latent setters.  We use the
      * JSOP_NEWINIT and JSOP_INITELEM_ARRAY bytecodes to ignore setters and
      * to avoid dup'ing and popping the array as each element is added, as
      * JSOP_SETELEM/JSOP_SETPROP would do.
      */
+    MOZ_ASSERT(op == JSOP_NEWARRAY || op == JSOP_SPREADCALLARRAY);
 
     int32_t nspread = 0;
     for (ParseNode* elt = pn; elt; elt = elt->pn_next) {
         if (elt->isKind(PNK_SPREAD))
             nspread++;
     }
 
     ptrdiff_t off;
-    if (!emitN(JSOP_NEWARRAY, 3, &off))                             // ARRAY
-        return false;
-    checkTypeSet(JSOP_NEWARRAY);
+    if (!emitN(op, 3, &off))                                        // ARRAY
+        return false;
+    checkTypeSet(op);
     jsbytecode* pc = code(off);
 
     // For arrays with spread, this is a very pessimistic allocation, the
     // minimum possible final size.
     SET_UINT24(pc, count - nspread);
 
     ParseNode* pn2 = pn;
     jsatomid atomIndex;
@@ -7570,17 +7571,17 @@ BytecodeEmitter::emitTree(ParseNode* pn)
                         return false;
 
                     ok = emitObjectOp(objbox, JSOP_NEWARRAY_COPYONWRITE);
                     break;
                 }
             }
         }
 
-        ok = emitArray(pn->pn_head, pn->pn_count);
+        ok = emitArray(pn->pn_head, pn->pn_count, JSOP_NEWARRAY);
         break;
 
        case PNK_ARRAYCOMP:
         ok = emitArrayComp(pn);
         break;
 
       case PNK_OBJECT:
         ok = emitObject(pn);
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -422,17 +422,17 @@ struct BytecodeEmitter
     bool emitGoto(StmtInfoBCE* toStmt, ptrdiff_t* lastp, SrcNoteType noteType = SRC_NULL);
 
     bool emitIndex32(JSOp op, uint32_t index);
     bool emitIndexOp(JSOp op, uint32_t index);
 
     bool emitAtomOp(JSAtom* atom, JSOp op);
     bool emitAtomOp(ParseNode* pn, JSOp op);
 
-    bool emitArray(ParseNode* pn, uint32_t count);
+    bool emitArray(ParseNode* pn, uint32_t count, JSOp op);
     bool emitArrayComp(ParseNode* pn);
 
     bool emitInternedObjectOp(uint32_t index, JSOp op);
     bool emitObjectOp(ObjectBox* objbox, JSOp op);
     bool emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op);
     bool emitRegExp(uint32_t index);
 
     MOZ_NEVER_INLINE bool emitFunction(ParseNode* pn, bool needsProto = false);
--- a/js/src/jit-test/tests/ion/recover-arrays.js
+++ b/js/src/jit-test/tests/ion/recover-arrays.js
@@ -1,13 +1,13 @@
 // Ion eager fails the test below because we have not yet created any
 // template object in baseline before running the content of the top-level
 // function.
-if (getJitCompilerOptions()["ion.warmup.trigger"] <= 20)
-    setJitCompilerOption("ion.warmup.trigger", 20);
+if (getJitCompilerOptions()["ion.warmup.trigger"] <= 100)
+    setJitCompilerOption("ion.warmup.trigger", 100);
 
 // This function is used to force a bailout when it is inlined, and to recover
 // the frame which is inlining this function.
 var resumeHere = function (i) { if (i >= 99) bailout(); };
 
 // This function is used to cause an invalidation after having removed a branch
 // after DCE.  This is made to check if we correctly recover an array
 // allocation.
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -1735,40 +1735,38 @@ BaselineCompiler::emit_JSOP_LINENO()
 }
 
 bool
 BaselineCompiler::emit_JSOP_NEWARRAY()
 {
     frame.syncStack(0);
 
     uint32_t length = GET_UINT24(pc);
-    RootedObjectGroup group(cx);
-    if (!ObjectGroup::useSingletonForAllocationSite(script, pc, JSProto_Array)) {
-        group = ObjectGroup::allocationSiteGroup(cx, script, pc, JSProto_Array);
-        if (!group)
-            return false;
-    }
-
-    // Pass length in R0, group in R1.
+
+    // Pass length in R0.
     masm.move32(Imm32(length), R0.scratchReg());
-    masm.movePtr(ImmGCPtr(group), R1.scratchReg());
-
-    ArrayObject* templateObject = NewDenseUnallocatedArray(cx, length, NullPtr(), TenuredObject);
-    if (!templateObject)
+
+    ObjectGroup* group = ObjectGroup::allocationSiteGroup(cx, script, pc, JSProto_Array);
+    if (!group)
         return false;
-    templateObject->setGroup(group);
-
-    ICNewArray_Fallback::Compiler stubCompiler(cx, templateObject);
+
+    ICNewArray_Fallback::Compiler stubCompiler(cx, group);
     if (!emitOpIC(stubCompiler.getStub(&stubSpace_)))
         return false;
 
     frame.push(R0);
     return true;
 }
 
+bool
+BaselineCompiler::emit_JSOP_SPREADCALLARRAY()
+{
+    return emit_JSOP_NEWARRAY();
+}
+
 typedef JSObject* (*NewArrayCopyOnWriteFn)(JSContext*, HandleArrayObject, gc::InitialHeap);
 const VMFunction jit::NewArrayCopyOnWriteInfo =
     FunctionInfo<NewArrayCopyOnWriteFn>(js::NewDenseCopyOnWriteArray);
 
 bool
 BaselineCompiler::emit_JSOP_NEWARRAY_COPYONWRITE()
 {
     RootedScript scriptRoot(cx, script);
@@ -1824,34 +1822,25 @@ BaselineCompiler::emit_JSOP_NEWOBJECT()
 }
 
 bool
 BaselineCompiler::emit_JSOP_NEWINIT()
 {
     frame.syncStack(0);
     JSProtoKey key = JSProtoKey(GET_UINT8(pc));
 
-    RootedObjectGroup group(cx);
-    if (!ObjectGroup::useSingletonForAllocationSite(script, pc, key)) {
-        group = ObjectGroup::allocationSiteGroup(cx, script, pc, key);
+    if (key == JSProto_Array) {
+        // Pass length in R0.
+        masm.move32(Imm32(0), R0.scratchReg());
+
+        ObjectGroup* group = ObjectGroup::allocationSiteGroup(cx, script, pc, JSProto_Array);
         if (!group)
             return false;
-    }
-
-    if (key == JSProto_Array) {
-        // Pass length in R0, group in R1.
-        masm.move32(Imm32(0), R0.scratchReg());
-        masm.movePtr(ImmGCPtr(group), R1.scratchReg());
-
-        ArrayObject* templateObject = NewDenseUnallocatedArray(cx, 0, NullPtr(), TenuredObject);
-        if (!templateObject)
-            return false;
-        templateObject->setGroup(group);
-
-        ICNewArray_Fallback::Compiler stubCompiler(cx, templateObject);
+
+        ICNewArray_Fallback::Compiler stubCompiler(cx, group);
         if (!emitOpIC(stubCompiler.getStub(&stubSpace_)))
             return false;
     } else {
         MOZ_ASSERT(key == JSProto_Object);
 
         ICNewObject_Fallback::Compiler stubCompiler(cx);
         if (!emitOpIC(stubCompiler.getStub(&stubSpace_)))
             return false;
--- a/js/src/jit/BaselineCompiler.h
+++ b/js/src/jit/BaselineCompiler.h
@@ -88,16 +88,17 @@ namespace jit {
     _(JSOP_STRICTNE)           \
     _(JSOP_CONDSWITCH)         \
     _(JSOP_CASE)               \
     _(JSOP_DEFAULT)            \
     _(JSOP_LINENO)             \
     _(JSOP_BITNOT)             \
     _(JSOP_NEG)                \
     _(JSOP_NEWARRAY)           \
+    _(JSOP_SPREADCALLARRAY)    \
     _(JSOP_NEWARRAY_COPYONWRITE) \
     _(JSOP_INITELEM_ARRAY)     \
     _(JSOP_NEWOBJECT)          \
     _(JSOP_NEWINIT)            \
     _(JSOP_INITELEM)           \
     _(JSOP_INITELEM_GETTER)    \
     _(JSOP_INITELEM_SETTER)    \
     _(JSOP_INITELEM_INC)       \
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -177,17 +177,17 @@ ReceiverGuard::trace(JSTracer* trc)
 ReceiverGuard::StackGuard::StackGuard(JSObject* obj)
   : group(nullptr), shape(nullptr)
 {
     if (obj) {
         if (obj->is<UnboxedPlainObject>()) {
             group = obj->group();
             if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
                 shape = expando->lastProperty();
-        } else if (obj->is<TypedObject>()) {
+        } else if (obj->is<UnboxedArrayObject>() || obj->is<TypedObject>()) {
             group = obj->group();
         } else {
             shape = obj->maybeShape();
         }
     }
 }
 
 ReceiverGuard::StackGuard::StackGuard(ObjectGroup* group, Shape* shape)
@@ -293,32 +293,38 @@ ICStub::trace(JSTracer* trc)
         TraceEdge(trc, &callStub->holderShape(), "baseline-getelem-nativeprotocall-holdershape");
         break;
       }
       case ICStub::GetElem_Dense: {
         ICGetElem_Dense* getElemStub = toGetElem_Dense();
         TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-dense-shape");
         break;
       }
+      case ICStub::GetElem_UnboxedArray: {
+        ICGetElem_UnboxedArray* getElemStub = toGetElem_UnboxedArray();
+        TraceEdge(trc, &getElemStub->group(), "baseline-getelem-unboxed-array-group");
+        break;
+      }
       case ICStub::GetElem_TypedArray: {
         ICGetElem_TypedArray* getElemStub = toGetElem_TypedArray();
         TraceEdge(trc, &getElemStub->shape(), "baseline-getelem-typedarray-shape");
         break;
       }
-      case ICStub::SetElem_Dense: {
-        ICSetElem_Dense* setElemStub = toSetElem_Dense();
-        TraceEdge(trc, &setElemStub->shape(), "baseline-getelem-dense-shape");
+      case ICStub::SetElem_DenseOrUnboxedArray: {
+        ICSetElem_DenseOrUnboxedArray* setElemStub = toSetElem_DenseOrUnboxedArray();
+        if (setElemStub->shape())
+            TraceEdge(trc, &setElemStub->shape(), "baseline-getelem-dense-shape");
         TraceEdge(trc, &setElemStub->group(), "baseline-setelem-dense-group");
         break;
       }
-      case ICStub::SetElem_DenseAdd: {
-        ICSetElem_DenseAdd* setElemStub = toSetElem_DenseAdd();
+      case ICStub::SetElem_DenseOrUnboxedArrayAdd: {
+        ICSetElem_DenseOrUnboxedArrayAdd* setElemStub = toSetElem_DenseOrUnboxedArrayAdd();
         TraceEdge(trc, &setElemStub->group(), "baseline-setelem-denseadd-group");
 
-        JS_STATIC_ASSERT(ICSetElem_DenseAdd::MAX_PROTO_CHAIN_DEPTH == 4);
+        JS_STATIC_ASSERT(ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH == 4);
 
         switch (setElemStub->protoChainDepth()) {
           case 0: setElemStub->toImpl<0>()->traceShapes(trc); break;
           case 1: setElemStub->toImpl<1>()->traceShapes(trc); break;
           case 2: setElemStub->toImpl<2>()->traceShapes(trc); break;
           case 3: setElemStub->toImpl<3>()->traceShapes(trc); break;
           case 4: setElemStub->toImpl<4>()->traceShapes(trc); break;
           default: MOZ_CRASH("Invalid proto stub.");
@@ -555,17 +561,19 @@ ICStub::trace(JSTracer* trc)
       case ICStub::InstanceOf_Function: {
         ICInstanceOf_Function* instanceofStub = toInstanceOf_Function();
         TraceEdge(trc, &instanceofStub->shape(), "baseline-instanceof-fun-shape");
         TraceEdge(trc, &instanceofStub->prototypeObject(), "baseline-instanceof-fun-prototype");
         break;
       }
       case ICStub::NewArray_Fallback: {
         ICNewArray_Fallback* stub = toNewArray_Fallback();
-        TraceEdge(trc, &stub->templateObject(), "baseline-newarray-template");
+        if (stub->templateObject())
+            TraceEdge(trc, &stub->templateObject(), "baseline-newarray-template");
+        TraceEdge(trc, &stub->templateGroup(), "baseline-newarray-template-group");
         break;
       }
       case ICStub::NewObject_Fallback: {
         ICNewObject_Fallback* stub = toNewObject_Fallback();
         if (stub->templateObject())
             TraceEdge(trc, &stub->templateObject(), "baseline-newobject-template");
         break;
       }
@@ -1491,19 +1499,18 @@ DoTypeUpdateFallback(JSContext* cx, Base
     FallbackICSpew(cx, stub->getChainFallback(), "TypeUpdate(%s)",
                    ICStub::KindString(stub->kind()));
 
     RootedScript script(cx, frame->script());
     RootedObject obj(cx, &objval.toObject());
     RootedId id(cx);
 
     switch(stub->kind()) {
-      case ICStub::SetElem_Dense:
-      case ICStub::SetElem_DenseAdd: {
-        MOZ_ASSERT(obj->isNative());
+      case ICStub::SetElem_DenseOrUnboxedArray:
+      case ICStub::SetElem_DenseOrUnboxedArrayAdd: {
         id = JSID_VOID;
         AddTypePropertyId(cx, obj, id, value);
         break;
       }
       case ICStub::SetProp_Native:
       case ICStub::SetProp_NativeAdd:
       case ICStub::SetProp_Unboxed: {
         MOZ_ASSERT(obj->isNative() || obj->is<UnboxedPlainObject>());
@@ -1703,41 +1710,58 @@ ICThis_Fallback::Compiler::generateStubC
     return tailCallVM(DoThisFallbackInfo, masm);
 }
 
 //
 // NewArray_Fallback
 //
 
 static bool
-DoNewArray(JSContext* cx, ICNewArray_Fallback* stub, uint32_t length,
-           HandleObjectGroup group, MutableHandleValue res)
+DoNewArray(JSContext* cx, BaselineFrame* frame, ICNewArray_Fallback* stub, uint32_t length,
+           MutableHandleValue res)
 {
     FallbackICSpew(cx, stub, "NewArray");
 
-    JSObject* obj = NewDenseArray(cx, length, group, NewArray_FullyAllocating);
-    if (!obj)
-        return false;
+    RootedObject obj(cx);
+    if (stub->templateObject()) {
+        RootedObject templateObject(cx, stub->templateObject());
+        obj = NewArrayOperationWithTemplate(cx, templateObject);
+        if (!obj)
+            return false;
+    } else {
+        RootedScript script(cx, frame->script());
+        jsbytecode* pc = stub->icEntry()->pc(script);
+        obj = NewArrayOperation(cx, script, pc, length);
+        if (!obj)
+            return false;
+
+        if (obj && !obj->isSingleton() && !obj->group()->maybePreliminaryObjects()) {
+            JSObject* templateObject = NewArrayOperation(cx, script, pc, length, TenuredObject);
+            if (!templateObject)
+                return false;
+            stub->setTemplateObject(templateObject);
+        }
+    }
 
     res.setObject(*obj);
     return true;
 }
 
-typedef bool(*DoNewArrayFn)(JSContext*, ICNewArray_Fallback*, uint32_t, HandleObjectGroup,
+typedef bool(*DoNewArrayFn)(JSContext*, BaselineFrame*, ICNewArray_Fallback*, uint32_t,
                             MutableHandleValue);
 static const VMFunction DoNewArrayInfo = FunctionInfo<DoNewArrayFn>(DoNewArray, TailCall);
 
 bool
 ICNewArray_Fallback::Compiler::generateStubCode(MacroAssembler& masm)
 {
     EmitRestoreTailCallReg(masm);
 
-    masm.push(R1.scratchReg()); // type
     masm.push(R0.scratchReg()); // length
     masm.push(BaselineStubReg); // stub.
+    masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
 
     return tailCallVM(DoNewArrayInfo, masm);
 }
 
 //
 // NewObject_Fallback
 //
 
@@ -3399,16 +3423,19 @@ CheckHasNoSuchProperty(JSContext* cx, Ha
             if (curObj->as<NativeObject>().contains(cx, NameToId(name)))
                 return false;
         } else if (curObj != obj) {
             // Non-native objects are only handled as the original receiver.
             return false;
         } else if (curObj->is<UnboxedPlainObject>()) {
             if (curObj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, NameToId(name)))
                 return false;
+        } else if (curObj->is<UnboxedArrayObject>()) {
+            if (name == cx->names().length)
+                return false;
         } else if (curObj->is<TypedObject>()) {
             if (curObj->as<TypedObject>().typeDescr().hasProperty(cx->names(), NameToId(name)))
                 return false;
         } else {
             return false;
         }
 
         JSObject* proto = curObj->getTaggedProto().toObjectOrNull();
@@ -3427,18 +3454,22 @@ CheckHasNoSuchProperty(JSContext* cx, Ha
 static bool
 IsCacheableProtoChain(JSObject* obj, JSObject* holder, bool isDOMProxy=false)
 {
     MOZ_ASSERT_IF(isDOMProxy, IsCacheableDOMProxy(obj));
 
     if (!isDOMProxy && !obj->isNative()) {
         if (obj == holder)
             return false;
-        if (!obj->is<UnboxedPlainObject>() && !obj->is<TypedObject>())
-            return false;
+        if (!obj->is<UnboxedPlainObject>() &&
+            !obj->is<UnboxedArrayObject>() &&
+            !obj->is<TypedObject>())
+        {
+            return false;
+        }
     }
 
     // Don't handle objects which require a prototype guard. This should
     // be uncommon so handling it is likely not worth the complexity.
     if (obj->hasUncacheableProto())
         return false;
 
     JSObject* cur = obj;
@@ -4004,16 +4035,26 @@ static bool
 IsNativeDenseElementAccess(HandleObject obj, HandleValue key)
 {
     if (obj->isNative() && key.isInt32() && key.toInt32() >= 0 && !IsAnyTypedArray(obj.get()))
         return true;
     return false;
 }
 
 static bool
+IsNativeOrUnboxedDenseElementAccess(HandleObject obj, HandleValue key)
+{
+    if (!obj->isNative() && !obj->is<UnboxedArrayObject>())
+        return false;
+    if (key.isInt32() && key.toInt32() >= 0 && !IsAnyTypedArray(obj.get()))
+        return true;
+    return false;
+}
+
+static bool
 TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_Fallback* stub,
                      HandleValue lhs, HandleValue rhs, HandleValue res)
 {
     bool isCallElem = (JSOp(*pc) == JSOP_CALLELEM);
 
     // Check for String[i] => Char accesses.
     if (lhs.isString() && rhs.isInt32() && res.isString() &&
         !stub->hasStub(ICStub::GetElem_String))
@@ -4090,16 +4131,29 @@ TryAttachGetElemStub(JSContext* cx, JSSc
         if (!TryAttachNativeGetValueElemStub(cx, rootedScript, pc, stub,
             obj.as<NativeObject>(), rhs))
         {
             return false;
         }
         script = rootedScript;
     }
 
+    // Check for UnboxedArray[int] accesses.
+    if (obj->is<UnboxedArrayObject>() && rhs.isInt32() && rhs.toInt32() >= 0) {
+        JitSpew(JitSpew_BaselineIC, "  Generating GetElem(UnboxedArray[Int32]) stub");
+        ICGetElem_UnboxedArray::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(),
+                                                  obj->group());
+        ICStub* unboxedStub = compiler.getStub(compiler.getStubSpace(script));
+        if (!unboxedStub)
+            return false;
+
+        stub->addNewStub(unboxedStub);
+        return true;
+    }
+
     // Check for TypedArray[int] => Number and TypedObject[int] => Number accesses.
     if ((IsAnyTypedArray(obj.get()) || IsPrimitiveArrayTypedObject(obj)) &&
         rhs.isNumber() &&
         res.isNumber() &&
         !TypedArrayGetElemStubExists(stub, obj))
     {
         // Don't attach CALLELEM stubs for accesses on typed array expected to yield numbers.
 #if JS_HAS_NO_SUCH_METHOD
@@ -4722,16 +4776,64 @@ ICGetElem_Dense::Compiler::generateStubC
 
     // Failure case - jump to next stub
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
 }
 
 //
+// GetElem_UnboxedArray
+//
+
+bool
+ICGetElem_UnboxedArray::Compiler::generateStubCode(MacroAssembler& masm)
+{
+    Label failure;
+    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
+    masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
+
+    AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
+    Register scratchReg = regs.takeAny();
+
+    // Unbox R0 and group guard.
+    Register obj = masm.extractObject(R0, ExtractTemp0);
+    masm.loadPtr(Address(BaselineStubReg, ICGetElem_UnboxedArray::offsetOfGroup()), scratchReg);
+    masm.branchTestObjGroup(Assembler::NotEqual, obj, scratchReg, &failure);
+
+    // Unbox key.
+    Register key = masm.extractInt32(R1, ExtractTemp1);
+
+    // Bounds check.
+    masm.load32(Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()),
+                scratchReg);
+    masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg);
+    masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure);
+
+    // Load obj->elements.
+    masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg);
+
+    // Load value.
+    size_t width = UnboxedTypeSize(elementType_);
+    BaseIndex addr(scratchReg, key, ScaleFromElemWidth(width));
+    masm.loadUnboxedProperty(addr, elementType_, R0);
+
+    // Only monitor the result if its type might change.
+    if (elementType_ == JSVAL_TYPE_OBJECT)
+        EmitEnterTypeMonitorIC(masm);
+    else
+        EmitReturnFromIC(masm);
+
+    // Failure case - jump to next stub
+    masm.bind(&failure);
+    EmitStubGuardFailure(masm);
+    return true;
+}
+
+//
 // GetElem_TypedArray
 //
 
 static void
 LoadTypedThingLength(MacroAssembler& masm, TypedThingLayout layout, Register obj, Register result)
 {
     switch (layout) {
       case Layout_TypedArray:
@@ -5022,52 +5124,58 @@ ICGetElem_Arguments::Compiler::generateS
     return true;
 }
 
 //
 // SetElem_Fallback
 //
 
 static bool
-SetElemDenseAddHasSameShapes(ICSetElem_DenseAdd* stub, NativeObject* obj)
-{
-    size_t numShapes = stub->protoChainDepth() + 1;
-    for (size_t i = 0; i < numShapes; i++) {
-        static const size_t MAX_DEPTH = ICSetElem_DenseAdd::MAX_PROTO_CHAIN_DEPTH;
-        if (obj->lastProperty() != stub->toImplUnchecked<MAX_DEPTH>()->shape(i))
-            return false;
-        JSObject* proto = obj->getProto();
+SetElemAddHasSameShapes(ICSetElem_DenseOrUnboxedArrayAdd* stub, JSObject* obj)
+{
+    static const size_t MAX_DEPTH = ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH;
+    ICSetElem_DenseOrUnboxedArrayAddImpl<MAX_DEPTH>* nstub = stub->toImplUnchecked<MAX_DEPTH>();
+
+    if (obj->maybeShape() != nstub->shape(0))
+        return false;
+
+    JSObject* proto = obj->getProto();
+    for (size_t i = 0; i < stub->protoChainDepth(); i++) {
+        if (!proto->isNative())
+            return false;
+        if (proto->as<NativeObject>().lastProperty() != nstub->shape(i + 1))
+            return false;
+        proto = obj->getProto();
         if (!proto) {
-            if (i != numShapes - 1)
+            if (i != stub->protoChainDepth() - 1)
                 return false;
             break;
         }
-        if (!proto->isNative())
-            return false;
-        obj = &proto->as<NativeObject>();
-    }
-
-    return true;
-}
-
-static bool
-DenseSetElemStubExists(JSContext* cx, ICStub::Kind kind, ICSetElem_Fallback* stub, HandleNativeObject obj)
-{
-    MOZ_ASSERT(kind == ICStub::SetElem_Dense || kind == ICStub::SetElem_DenseAdd);
+    }
+
+    return true;
+}
+
+static bool
+DenseOrUnboxedArraySetElemStubExists(JSContext* cx, ICStub::Kind kind,
+                                     ICSetElem_Fallback* stub, HandleObject obj)
+{
+    MOZ_ASSERT(kind == ICStub::SetElem_DenseOrUnboxedArray ||
+               kind == ICStub::SetElem_DenseOrUnboxedArrayAdd);
 
     for (ICStubConstIterator iter = stub->beginChainConst(); !iter.atEnd(); iter++) {
-        if (kind == ICStub::SetElem_Dense && iter->isSetElem_Dense()) {
-            ICSetElem_Dense* dense = iter->toSetElem_Dense();
-            if (obj->lastProperty() == dense->shape() && obj->getGroup(cx) == dense->group())
+        if (kind == ICStub::SetElem_DenseOrUnboxedArray && iter->isSetElem_DenseOrUnboxedArray()) {
+            ICSetElem_DenseOrUnboxedArray* nstub = iter->toSetElem_DenseOrUnboxedArray();
+            if (obj->maybeShape() == nstub->shape() && obj->getGroup(cx) == nstub->group())
                 return true;
         }
 
-        if (kind == ICStub::SetElem_DenseAdd && iter->isSetElem_DenseAdd()) {
-            ICSetElem_DenseAdd* dense = iter->toSetElem_DenseAdd();
-            if (obj->getGroup(cx) == dense->group() && SetElemDenseAddHasSameShapes(dense, obj))
+        if (kind == ICStub::SetElem_DenseOrUnboxedArrayAdd && iter->isSetElem_DenseOrUnboxedArrayAdd()) {
+            ICSetElem_DenseOrUnboxedArrayAdd* nstub = iter->toSetElem_DenseOrUnboxedArrayAdd();
+            if (obj->getGroup(cx) == nstub->group() && SetElemAddHasSameShapes(nstub, obj))
                 return true;
         }
     }
     return false;
 }
 
 static bool
 TypedArraySetElemStubExists(ICSetElem_Fallback* stub, HandleObject obj, bool expectOOB)
@@ -5096,47 +5204,63 @@ RemoveExistingTypedArraySetElemStub(JSCo
         // being replaced with one that expects out of bounds index.
         MOZ_ASSERT(!iter->toSetElem_TypedArray()->expectOutOfBounds());
         iter.unlink(cx);
         return true;
     }
     return false;
 }
 
-static bool
-CanOptimizeDenseSetElem(NativeObject* obj, uint32_t index,
-                        Shape* oldShape, uint32_t oldCapacity, uint32_t oldInitLength,
-                        bool* isAddingCaseOut, size_t* protoDepthOut)
-{
-    uint32_t initLength = obj->getDenseInitializedLength();
-    uint32_t capacity = obj->getDenseCapacity();
+static size_t
+SetElemObjectInitializedLength(JSObject *obj)
+{
+    if (obj->isNative())
+        return obj->as<NativeObject>().getDenseInitializedLength();
+    return obj->as<UnboxedArrayObject>().initializedLength();
+}
+
+static size_t
+SetElemObjectCapacity(JSObject *obj)
+{
+    if (obj->isNative())
+        return obj->as<NativeObject>().getDenseCapacity();
+    return obj->as<UnboxedArrayObject>().capacity();
+}
+
+static bool
+CanOptimizeDenseOrUnboxedArraySetElem(JSObject* obj, uint32_t index,
+                                      Shape* oldShape, uint32_t oldCapacity, uint32_t oldInitLength,
+                                      bool* isAddingCaseOut, size_t* protoDepthOut)
+{
+    uint32_t initLength = SetElemObjectInitializedLength(obj);
+    uint32_t capacity = SetElemObjectCapacity(obj);
 
     *isAddingCaseOut = false;
     *protoDepthOut = 0;
 
     // Some initial sanity checks.
     if (initLength < oldInitLength || capacity < oldCapacity)
         return false;
 
-    Shape* shape = obj->lastProperty();
+    Shape* shape = obj->maybeShape();
 
     // Cannot optimize if the shape changed.
     if (oldShape != shape)
         return false;
 
     // Cannot optimize if the capacity changed.
     if (oldCapacity != capacity)
         return false;
 
     // Cannot optimize if the index doesn't fit within the new initialized length.
     if (index >= initLength)
         return false;
 
     // Cannot optimize if the value at position after the set is a hole.
-    if (!obj->containsDenseElement(index))
+    if (obj->isNative() && !obj->as<NativeObject>().containsDenseElement(index))
         return false;
 
     // At this point, if we know that the initLength did not change, then
     // an optimized set is possible.
     if (oldInitLength == initLength)
         return true;
 
     // If it did change, ensure that it changed specifically by incrementing by 1
@@ -5145,36 +5269,30 @@ CanOptimizeDenseSetElem(NativeObject* ob
         return false;
     if (index != oldInitLength)
         return false;
 
     // The checks are not complete.  The object may have a setter definition,
     // either directly, or via a prototype, or via the target object for a prototype
     // which is a proxy, that handles a particular integer write.
     // Scan the prototype and shape chain to make sure that this is not the case.
-    JSObject* curObj = obj;
+    if (obj->isIndexed())
+        return false;
+    JSObject* curObj = obj->getProto();
     while (curObj) {
-        // Ensure object is native.
-        if (!curObj->isNative())
-            return false;
-
-        // Ensure all indexed properties are stored in dense elements.
-        if (curObj->isIndexed())
-            return false;
-
+        ++*protoDepthOut;
+        if (!curObj->isNative() || curObj->isIndexed())
+            return false;
         curObj = curObj->getProto();
-        if (curObj)
-            ++*protoDepthOut;
-    }
-
-    if (*protoDepthOut > ICSetElem_DenseAdd::MAX_PROTO_CHAIN_DEPTH)
+    }
+
+    if (*protoDepthOut > ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH)
         return false;
 
     *isAddingCaseOut = true;
-
     return true;
 }
 
 static bool
 DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_, Value* stack,
                   HandleValue objv, HandleValue index, HandleValue rhs)
 {
     // This fallback stub may trigger debug mode toggling.
@@ -5230,59 +5348,67 @@ DoSetElemFallback(JSContext* cx, Baselin
 
     if (stub->numOptimizedStubs() >= ICSetElem_Fallback::MAX_OPTIMIZED_STUBS) {
         // TODO: Discard all stubs in this IC and replace with inert megamorphic stub.
         // But for now we just bail.
         return true;
     }
 
     // Try to generate new stubs.
-    if (IsNativeDenseElementAccess(obj, index) && !rhs.isMagic(JS_ELEMENTS_HOLE))
-    {
-        HandleNativeObject nobj = obj.as<NativeObject>();
-
+    if (IsNativeOrUnboxedDenseElementAccess(obj, index) && !rhs.isMagic(JS_ELEMENTS_HOLE)) {
         bool addingCase;
         size_t protoDepth;
 
-        if (CanOptimizeDenseSetElem(nobj, index.toInt32(),
-                                    oldShape, oldCapacity, oldInitLength,
-                                    &addingCase, &protoDepth))
+        if (CanOptimizeDenseOrUnboxedArraySetElem(obj, index.toInt32(),
+                                                  oldShape, oldCapacity, oldInitLength,
+                                                  &addingCase, &protoDepth))
         {
-            RootedShape shape(cx, nobj->lastProperty());
+            RootedShape shape(cx, obj->maybeShape());
             RootedObjectGroup group(cx, obj->getGroup(cx));
             if (!group)
                 return false;
 
-            if (addingCase && !DenseSetElemStubExists(cx, ICStub::SetElem_DenseAdd, stub, nobj)) {
-                JitSpew(JitSpew_BaselineIC,
-                        "  Generating SetElem_DenseAdd stub "
-                        "(shape=%p, group=%p, protoDepth=%u)",
-                        nobj->lastProperty(), group.get(), protoDepth);
-                ICSetElemDenseAddCompiler compiler(cx, obj, protoDepth);
-                ICUpdatedStub* denseStub = compiler.getStub(compiler.getStubSpace(script));
-                if (!denseStub)
-                    return false;
-                if (!denseStub->addUpdateStubForValue(cx, script, obj, JSID_VOIDHANDLE, rhs))
-                    return false;
-
-                stub->addNewStub(denseStub);
-            } else if (!addingCase &&
-                       !DenseSetElemStubExists(cx, ICStub::SetElem_Dense, stub, nobj))
+            if (addingCase &&
+                !DenseOrUnboxedArraySetElemStubExists(cx, ICStub::SetElem_DenseOrUnboxedArrayAdd,
+                                                      stub, obj))
             {
                 JitSpew(JitSpew_BaselineIC,
-                        "  Generating SetElem_Dense stub (shape=%p, group=%p)",
-                        nobj->lastProperty(), group.get());
-                ICSetElem_Dense::Compiler compiler(cx, shape, group);
-                ICUpdatedStub* denseStub = compiler.getStub(compiler.getStubSpace(script));
-                if (!denseStub)
+                        "  Generating SetElem_DenseOrUnboxedArrayAdd stub "
+                        "(shape=%p, group=%p, protoDepth=%u)",
+                        shape.get(), group.get(), protoDepth);
+                ICSetElemDenseOrUnboxedArrayAddCompiler compiler(cx, obj, protoDepth);
+                ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(script));
+                if (!newStub)
+                    return false;
+                if (compiler.needsUpdateStubs() &&
+                    !newStub->addUpdateStubForValue(cx, script, obj, JSID_VOIDHANDLE, rhs))
+                {
                     return false;
-                if (!denseStub->addUpdateStubForValue(cx, script, obj, JSID_VOIDHANDLE, rhs))
+                }
+
+                stub->addNewStub(newStub);
+            } else if (!addingCase &&
+                       !DenseOrUnboxedArraySetElemStubExists(cx,
+                                                             ICStub::SetElem_DenseOrUnboxedArray,
+                                                             stub, obj))
+            {
+                JitSpew(JitSpew_BaselineIC,
+                        "  Generating SetElem_DenseOrUnboxedArray stub (shape=%p, group=%p)",
+                        shape.get(), group.get());
+                ICSetElem_DenseOrUnboxedArray::Compiler compiler(cx, shape, group);
+                ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(script));
+                if (!newStub)
                     return false;
-
-                stub->addNewStub(denseStub);
+                if (compiler.needsUpdateStubs() &&
+                    !newStub->addUpdateStubForValue(cx, script, obj, JSID_VOIDHANDLE, rhs))
+                {
+                    return false;
+                }
+
+                stub->addNewStub(newStub);
             }
         }
 
         return true;
     }
 
     if ((IsAnyTypedArray(obj.get()) || IsPrimitiveArrayTypedObject(obj)) &&
         index.isNumber() &&
@@ -5382,135 +5508,173 @@ BaselineScript::noteArrayWriteHole(uint3
     ICEntry& entry = icEntryFromPCOffset(pcOffset);
     ICFallbackStub* stub = entry.fallbackStub();
 
     if (stub->isSetElem_Fallback())
         stub->toSetElem_Fallback()->noteArrayWriteHole();
 }
 
 //
-// SetElem_Dense
-//
-
-bool
-ICSetElem_Dense::Compiler::generateStubCode(MacroAssembler& masm)
+// SetElem_DenseOrUnboxedArray
+//
+
+template <typename T>
+void
+EmitUnboxedPreBarrierForBaseline(MacroAssembler &masm, T address, JSValueType type)
+{
+    if (type == JSVAL_TYPE_OBJECT)
+        EmitPreBarrier(masm, address, MIRType_Object);
+    else if (type == JSVAL_TYPE_STRING)
+        EmitPreBarrier(masm, address, MIRType_String);
+    else
+        MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(type));
+}
+
+bool
+ICSetElem_DenseOrUnboxedArray::Compiler::generateStubCode(MacroAssembler& masm)
 {
     // R0 = object
     // R1 = key
     // Stack = { ... rhs-value, <return-addr>? }
-    Label failure;
-    Label failureUnstow;
+    Label failure, failurePopR0;
     masm.branchTestObject(Assembler::NotEqual, R0, &failure);
     masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
 
     AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
     Register scratchReg = regs.takeAny();
 
-    // Unbox R0 and guard on its shape.
+    // Unbox R0 and guard on its group and, if this is a native access, its shape.
     Register obj = masm.extractObject(R0, ExtractTemp0);
-    masm.loadPtr(Address(BaselineStubReg, ICSetElem_Dense::offsetOfShape()), scratchReg);
-    masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
-
-    // Stow both R0 and R1 (object and key)
-    // But R0 and R1 still hold their values.
-    EmitStowICValues(masm, 2);
-
-    // We may need to free up some registers.
-    regs = availableGeneralRegs(0);
-    regs.take(R0);
-
-    // Guard that the object group matches.
-    Register typeReg = regs.takeAny();
-    masm.loadPtr(Address(BaselineStubReg, ICSetElem_Dense::offsetOfGroup()), typeReg);
-    masm.branchPtr(Assembler::NotEqual, Address(obj, JSObject::offsetOfGroup()), typeReg,
-                   &failureUnstow);
-    regs.add(typeReg);
-
-    // Stack is now: { ..., rhs-value, object-value, key-value, maybe?-RET-ADDR }
-    // Load rhs-value in to R0
-    masm.loadValue(Address(BaselineStackReg, 2 * sizeof(Value) + ICStackValueOffset), R0);
-
-    // Call the type-update stub.
-    if (!callTypeUpdateIC(masm, sizeof(Value)))
-        return false;
-
-    // Unstow R0 and R1 (object and key)
-    EmitUnstowICValues(masm, 2);
-
-    // Reset register set.
-    regs = availableGeneralRegs(2);
-    scratchReg = regs.takeAny();
-
-    // Unbox object and key.
-    obj = masm.extractObject(R0, ExtractTemp0);
+    masm.loadPtr(Address(BaselineStubReg, ICSetElem_DenseOrUnboxedArray::offsetOfGroup()),
+                 scratchReg);
+    masm.branchTestObjGroup(Assembler::NotEqual, obj, scratchReg, &failure);
+    if (unboxedType_ == JSVAL_TYPE_MAGIC) {
+        masm.loadPtr(Address(BaselineStubReg, ICSetElem_DenseOrUnboxedArray::offsetOfShape()),
+                     scratchReg);
+        masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
+    }
+
+    if (needsUpdateStubs()) {
+        // Stow both R0 and R1 (object and key)
+        // But R0 and R1 still hold their values.
+        EmitStowICValues(masm, 2);
+
+        // Stack is now: { ..., rhs-value, object-value, key-value, maybe?-RET-ADDR }
+        // Load rhs-value into R0
+        masm.loadValue(Address(BaselineStackReg, 2 * sizeof(Value) + ICStackValueOffset), R0);
+
+        // Call the type-update stub.
+        if (!callTypeUpdateIC(masm, sizeof(Value)))
+            return false;
+
+        // Unstow R0 and R1 (object and key)
+        EmitUnstowICValues(masm, 2);
+
+        // Restore object.
+        obj = masm.extractObject(R0, ExtractTemp0);
+
+        // Trigger post barriers here on the value being written. Fields which
+        // objects can be written to also need update stubs.
+        masm.Push(R1);
+        masm.loadValue(Address(BaselineStackReg, sizeof(Value) + ICStackValueOffset), R1);
+
+        LiveGeneralRegisterSet saveRegs;
+        saveRegs.add(R0);
+        saveRegs.addUnchecked(obj);
+        saveRegs.add(BaselineStubReg);
+        emitPostWriteBarrierSlot(masm, obj, R1, scratchReg, saveRegs);
+
+        masm.Pop(R1);
+    }
+
+    // Unbox key.
     Register key = masm.extractInt32(R1, ExtractTemp1);
 
-    // Load obj->elements in scratchReg.
-    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratchReg);
-
-    // 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);
 
-    // 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(), &noSpecialHandling);
-    else
-        masm.assumeUnreachable("There shouldn't be double arrays when there is no FP support.");
-
-    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);
-    if (cx->runtime()->gc.nursery.exists()) {
-        Register r = regs.takeAny();
-        LiveGeneralRegisterSet saveRegs;
-        emitPostWriteBarrierSlot(masm, obj, tmpVal, r, saveRegs);
-        regs.add(r);
-    }
+    if (unboxedType_ == JSVAL_TYPE_MAGIC) {
+        // Set element on a native object.
+
+        // Load obj->elements in scratchReg.
+        masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratchReg);
+
+        // 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);
+
+        // 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(), &noSpecialHandling);
+        else
+            masm.assumeUnreachable("There shouldn't be double arrays when there is no FP support.");
+
+        masm.bind(&noSpecialHandling);
+
+        ValueOperand tmpVal = regs.takeAnyValue();
+        masm.loadValue(valueAddr, tmpVal);
+        EmitPreBarrier(masm, element, MIRType_Value);
+        masm.storeValue(tmpVal, element);
+    } else {
+        // Set element on an unboxed array.
+
+        // Bounds check.
+        Address initLength(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
+        masm.load32(initLength, scratchReg);
+        masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg);
+        masm.branch32(Assembler::BelowOrEqual, scratchReg, key, &failure);
+
+        // Load obj->elements.
+        masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg);
+
+        // Compute the address being written to.
+        BaseIndex address(obj, key, ScaleFromElemWidth(UnboxedTypeSize(unboxedType_)));
+
+        EmitUnboxedPreBarrierForBaseline(masm, address, unboxedType_);
+
+        masm.Push(R0);
+        masm.loadValue(valueAddr, R0);
+        masm.storeUnboxedProperty(address, unboxedType_,
+                                  ConstantOrRegister(TypedOrValueRegister(R0)), &failurePopR0);
+        masm.Pop(R0);
+    }
+
     EmitReturnFromIC(masm);
 
-
-    // Failure case - fail but first unstow R0 and R1
-    masm.bind(&failureUnstow);
-    EmitUnstowICValues(masm, 2);
+    if (failurePopR0.used()) {
+        masm.bind(&failurePopR0);
+        masm.Pop(R0);
+    }
 
     // Failure case - jump to next stub
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
 }
 
 static bool
@@ -5522,176 +5686,230 @@ GetProtoShapes(JSObject* obj, size_t pro
             return false;
         curProto = curProto->getProto();
     }
     MOZ_ASSERT(!curProto);
     return true;
 }
 
 //
-// SetElem_DenseAdd
+// SetElem_DenseOrUnboxedArrayAdd
 //
 
 ICUpdatedStub*
-ICSetElemDenseAddCompiler::getStub(ICStubSpace* space)
+ICSetElemDenseOrUnboxedArrayAddCompiler::getStub(ICStubSpace* space)
 {
     AutoShapeVector shapes(cx);
-    if (!shapes.append(obj_->as<NativeObject>().lastProperty()))
+    if (!shapes.append(obj_->maybeShape()))
         return nullptr;
 
     if (!GetProtoShapes(obj_, protoChainDepth_, &shapes))
         return nullptr;
 
-    JS_STATIC_ASSERT(ICSetElem_DenseAdd::MAX_PROTO_CHAIN_DEPTH == 4);
+    JS_STATIC_ASSERT(ICSetElem_DenseOrUnboxedArrayAdd::MAX_PROTO_CHAIN_DEPTH == 4);
 
     ICUpdatedStub* stub = nullptr;
     switch (protoChainDepth_) {
       case 0: stub = getStubSpecific<0>(space, &shapes); break;
       case 1: stub = getStubSpecific<1>(space, &shapes); break;
       case 2: stub = getStubSpecific<2>(space, &shapes); break;
       case 3: stub = getStubSpecific<3>(space, &shapes); break;
       case 4: stub = getStubSpecific<4>(space, &shapes); break;
       default: MOZ_CRASH("ProtoChainDepth too high.");
     }
     if (!stub || !stub->initUpdatingChain(cx, space))
         return nullptr;
     return stub;
 }
 
 bool
-ICSetElemDenseAddCompiler::generateStubCode(MacroAssembler& masm)
+ICSetElemDenseOrUnboxedArrayAddCompiler::generateStubCode(MacroAssembler& masm)
 {
     // R0 = object
     // R1 = key
     // Stack = { ... rhs-value, <return-addr>? }
-    Label failure;
-    Label failureUnstow;
+    Label failure, failurePopR0, failureUnstow;
     masm.branchTestObject(Assembler::NotEqual, R0, &failure);
     masm.branchTestInt32(Assembler::NotEqual, R1, &failure);
 
     AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
     Register scratchReg = regs.takeAny();
 
-    // Unbox R0 and guard on its shape.
+    // Unbox R0 and guard on its group and, if this is a native access, its shape.
     Register obj = masm.extractObject(R0, ExtractTemp0);
-    masm.loadPtr(Address(BaselineStubReg, ICSetElem_DenseAddImpl<0>::offsetOfShape(0)),
+    masm.loadPtr(Address(BaselineStubReg, ICSetElem_DenseOrUnboxedArrayAdd::offsetOfGroup()),
                  scratchReg);
-    masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
+    masm.branchTestObjGroup(Assembler::NotEqual, obj, scratchReg, &failure);
+    if (unboxedType_ == JSVAL_TYPE_MAGIC) {
+        masm.loadPtr(Address(BaselineStubReg, ICSetElem_DenseOrUnboxedArrayAddImpl<0>::offsetOfShape(0)),
+                     scratchReg);
+        masm.branchTestObjShape(Assembler::NotEqual, obj, scratchReg, &failure);
+    }
 
     // Stow both R0 and R1 (object and key)
     // But R0 and R1 still hold their values.
     EmitStowICValues(masm, 2);
 
     // We may need to free up some registers.
     regs = availableGeneralRegs(0);
     regs.take(R0);
-
-    // Guard that the object group matches.
-    Register typeReg = regs.takeAny();
-    masm.loadPtr(Address(BaselineStubReg, ICSetElem_DenseAdd::offsetOfGroup()), typeReg);
-    masm.branchPtr(Assembler::NotEqual, Address(obj, JSObject::offsetOfGroup()), typeReg,
-                   &failureUnstow);
-    regs.add(typeReg);
+    regs.take(scratchReg);
 
     // Shape guard objects on the proto chain.
-    scratchReg = regs.takeAny();
     Register protoReg = regs.takeAny();
     for (size_t i = 0; i < protoChainDepth_; i++) {
         masm.loadObjProto(i == 0 ? obj : protoReg, protoReg);
         masm.branchTestPtr(Assembler::Zero, protoReg, protoReg, &failureUnstow);
-        masm.loadPtr(Address(BaselineStubReg, ICSetElem_DenseAddImpl<0>::offsetOfShape(i + 1)),
+        masm.loadPtr(Address(BaselineStubReg, ICSetElem_DenseOrUnboxedArrayAddImpl<0>::offsetOfShape(i + 1)),
                      scratchReg);
         masm.branchTestObjShape(Assembler::NotEqual, protoReg, scratchReg, &failureUnstow);
     }
     regs.add(protoReg);
     regs.add(scratchReg);
 
-    // Stack is now: { ..., rhs-value, object-value, key-value, maybe?-RET-ADDR }
-    // Load rhs-value in to R0
-    masm.loadValue(Address(BaselineStackReg, 2 * sizeof(Value) + ICStackValueOffset), R0);
-
-    // Call the type-update stub.
-    if (!callTypeUpdateIC(masm, sizeof(Value)))
-        return false;
+    if (needsUpdateStubs()) {
+        // Stack is now: { ..., rhs-value, object-value, key-value, maybe?-RET-ADDR }
+        // Load rhs-value in to R0
+        masm.loadValue(Address(BaselineStackReg, 2 * sizeof(Value) + ICStackValueOffset), R0);
+
+        // Call the type-update stub.
+        if (!callTypeUpdateIC(masm, sizeof(Value)))
+            return false;
+    }
 
     // Unstow R0 and R1 (object and key)
     EmitUnstowICValues(masm, 2);
 
+    // Restore object.
+    obj = masm.extractObject(R0, ExtractTemp0);
+
+    if (needsUpdateStubs()) {
+        // Trigger post barriers here on the value being written. Fields which
+        // objects can be written to also need update stubs.
+        masm.Push(R1);
+        masm.loadValue(Address(BaselineStackReg, sizeof(Value) + ICStackValueOffset), R1);
+
+        LiveGeneralRegisterSet saveRegs;
+        saveRegs.add(R0);
+        saveRegs.addUnchecked(obj);
+        saveRegs.add(BaselineStubReg);
+        emitPostWriteBarrierSlot(masm, obj, R1, scratchReg, saveRegs);
+
+        masm.Pop(R1);
+    }
+
     // Reset register set.
     regs = availableGeneralRegs(2);
     scratchReg = regs.takeAny();
 
-    // Unbox obj and key.
-    obj = masm.extractObject(R0, ExtractTemp0);
+    // Unbox key.
     Register key = masm.extractInt32(R1, ExtractTemp1);
 
-    // Load obj->elements in scratchReg.
-    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratchReg);
-
-    // 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);
-
-    // If length is now <= key, increment length before write.
-    Label skipIncrementLength;
-    Address length(scratchReg, ObjectElements::offsetOfLength());
-    masm.branch32(Assembler::Above, length, key, &skipIncrementLength);
-    masm.add32(Imm32(1), length);
-    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;
-    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
-        masm.assumeUnreachable("There shouldn't be double arrays when there is no FP support.");
-    masm.bind(&dontConvertDoubles);
-
-    // Write the value.  No need for pre-barrier since we're not overwriting an old value.
-    ValueOperand tmpVal = regs.takeAnyValue();
-    BaseIndex element(scratchReg, key, TimesEight);
-    masm.loadValue(valueAddr, tmpVal);
-    masm.storeValue(tmpVal, element);
-    regs.add(key);
-    if (cx->runtime()->gc.nursery.exists()) {
-        Register r = regs.takeAny();
-        LiveGeneralRegisterSet saveRegs;
-        emitPostWriteBarrierSlot(masm, obj, tmpVal, r, saveRegs);
-        regs.add(r);
-    }
+    if (unboxedType_ == JSVAL_TYPE_MAGIC) {
+        // Adding element to a native object.
+
+        // Load obj->elements in scratchReg.
+        masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratchReg);
+
+        // 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);
+
+        // If length is now <= key, increment length before write.
+        Label skipIncrementLength;
+        Address length(scratchReg, ObjectElements::offsetOfLength());
+        masm.branch32(Assembler::Above, length, key, &skipIncrementLength);
+        masm.add32(Imm32(1), length);
+        masm.bind(&skipIncrementLength);
+
+        // 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;
+        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
+            masm.assumeUnreachable("There shouldn't be double arrays when there is no FP support.");
+        masm.bind(&dontConvertDoubles);
+
+        // Write the value.  No need for pre-barrier since we're not overwriting an old value.
+        ValueOperand tmpVal = regs.takeAnyValue();
+        BaseIndex element(scratchReg, key, TimesEight);
+        masm.loadValue(valueAddr, tmpVal);
+        masm.storeValue(tmpVal, element);
+    } else {
+        // Adding element to an unboxed array.
+
+        // Bounds check (key == initLength)
+        Address initLengthAddr(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
+        masm.load32(initLengthAddr, scratchReg);
+        masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), scratchReg);
+        masm.branch32(Assembler::NotEqual, scratchReg, key, &failure);
+
+        Address lengthAddr(obj, UnboxedArrayObject::offsetOfLength());
+
+        // Capacity check.
+        masm.checkUnboxedArrayCapacity(obj, Int32Key(key), scratchReg, &failure);
+
+        // Increment initLength before write.
+        masm.add32(Imm32(1), initLengthAddr);
+
+        // If length is now <= key, increment length before write.
+        Label skipIncrementLength;
+        masm.branch32(Assembler::Above, lengthAddr, key, &skipIncrementLength);
+        masm.add32(Imm32(1), lengthAddr);
+        masm.bind(&skipIncrementLength);
+
+        // Load obj->elements.
+        masm.loadPtr(Address(obj, UnboxedArrayObject::offsetOfElements()), scratchReg);
+
+        masm.Push(R0);
+        masm.loadValue(valueAddr, R0);
+
+        // Write the value. No need for pre-barrier since we're not overwriting an old value.
+        BaseIndex address(obj, key, ScaleFromElemWidth(UnboxedTypeSize(unboxedType_)));
+        masm.storeUnboxedProperty(address, unboxedType_,
+                                  ConstantOrRegister(TypedOrValueRegister(R0)), &failurePopR0);
+        masm.Pop(R0);
+    }
+
     EmitReturnFromIC(masm);
 
+    if (failurePopR0.used()) {
+        masm.bind(&failurePopR0);
+        masm.Pop(R0);
+        masm.jump(&failure);
+    }
+
     // Failure case - fail but first unstow R0 and R1
     masm.bind(&failureUnstow);
     EmitUnstowICValues(masm, 2);
 
     // Failure case - jump to next stub
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
@@ -6849,16 +7067,28 @@ TryAttachLengthStub(JSContext* cx, JSScr
         if (!newStub)
             return false;
 
         *attached = true;
         stub->addNewStub(newStub);
         return true;
     }
 
+    if (obj->is<UnboxedArrayObject>() && res.isInt32()) {
+        JitSpew(JitSpew_BaselineIC, "  Generating GetProp(UnboxedArray.length) stub");
+        ICGetProp_UnboxedArrayLength::Compiler compiler(cx);
+        ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
+        if (!newStub)
+            return false;
+
+        *attached = true;
+        stub->addNewStub(newStub);
+        return true;
+    }
+
     if (obj->is<ArgumentsObject>() && res.isInt32()) {
         JitSpew(JitSpew_BaselineIC, "  Generating GetProp(ArgsObj.length %s) stub",
                 obj->is<StrictArgumentsObject>() ? "Strict" : "Normal");
         ICGetProp_ArgumentsLength::Which which = ICGetProp_ArgumentsLength::Normal;
         if (obj->is<StrictArgumentsObject>())
             which = ICGetProp_ArgumentsLength::Strict;
         ICGetProp_ArgumentsLength::Compiler compiler(cx, which);
         ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
@@ -7614,16 +7844,40 @@ ICGetProp_ArrayLength::Compiler::generat
 
     // Failure case - jump to next stub
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
 }
 
 bool
+ICGetProp_UnboxedArrayLength::Compiler::generateStubCode(MacroAssembler& masm)
+{
+    Label failure;
+    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
+
+    Register scratch = R1.scratchReg();
+
+    // Unbox R0 and guard it's an unboxed array.
+    Register obj = masm.extractObject(R0, ExtractTemp0);
+    masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, &UnboxedArrayObject::class_, &failure);
+
+    // Load obj->length.
+    masm.load32(Address(obj, UnboxedArrayObject::offsetOfLength()), scratch);
+
+    masm.tagValue(JSVAL_TYPE_INT32, scratch, R0);
+    EmitReturnFromIC(masm);
+
+    // Failure case - jump to next stub
+    masm.bind(&failure);
+    EmitStubGuardFailure(masm);
+    return true;
+}
+
+bool
 ICGetProp_StringLength::Compiler::generateStubCode(MacroAssembler& masm)
 {
     Label failure;
     masm.branchTestString(Assembler::NotEqual, R0, &failure);
 
     // Unbox string and load its length.
     Register string = masm.extractString(R0, ExtractTemp0);
     masm.loadStringLength(string, string);
@@ -7715,25 +7969,25 @@ GuardReceiverObject(MacroAssembler& masm
     Address groupAddress(BaselineStubReg, receiverGuardOffset + ReceiverGuard::offsetOfGroup());
     Address shapeAddress(BaselineStubReg, receiverGuardOffset + ReceiverGuard::offsetOfShape());
     Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
 
     if (guard.group) {
         masm.loadPtr(groupAddress, scratch);
         masm.branchTestObjGroup(Assembler::NotEqual, object, scratch, failure);
 
-        if (guard.group->maybeUnboxedLayout() && !guard.shape) {
+        if (guard.group->clasp() == &UnboxedPlainObject::class_ && !guard.shape) {
             // Guard the unboxed object has no expando object.
             masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure);
         }
     }
 
     if (guard.shape) {
         masm.loadPtr(shapeAddress, scratch);
-        if (guard.group && guard.group->maybeUnboxedLayout()) {
+        if (guard.group && guard.group->clasp() == &UnboxedPlainObject::class_) {
             // Guard the unboxed object has a matching expando object.
             masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure);
             Label done;
             masm.push(object);
             masm.loadPtr(expandoAddress, object);
             masm.branchTestObjShape(Assembler::Equal, object, scratch, &done);
             masm.pop(object);
             masm.jump(failure);
@@ -9289,23 +9543,17 @@ ICSetProp_Unboxed::Compiler::generateStu
         saveRegs.add(BaselineStubReg);
         emitPostWriteBarrierSlot(masm, object, R1, scratch, saveRegs);
     }
 
     // Compute the address being written to.
     masm.load32(Address(BaselineStubReg, ICSetProp_Unboxed::offsetOfFieldOffset()), scratch);
     BaseIndex address(object, scratch, TimesOne);
 
-    if (fieldType_ == JSVAL_TYPE_OBJECT)
-        EmitPreBarrier(masm, address, MIRType_Object);
-    else if (fieldType_ == JSVAL_TYPE_STRING)
-        EmitPreBarrier(masm, address, MIRType_String);
-    else
-        MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(fieldType_));
-
+    EmitUnboxedPreBarrierForBaseline(masm, address, fieldType_);
     masm.storeUnboxedProperty(address, fieldType_,
                               ConstantOrRegister(TypedOrValueRegister(R1)), &failure);
 
     // The RHS has to be in R0.
     masm.moveValue(R1, R0);
 
     EmitReturnFromIC(masm);
 
@@ -9742,23 +9990,26 @@ GetTemplateObjectForNative(JSContext* cx
         ObjectGroup* group = ObjectGroup::allocationSiteGroup(cx, script, pc, JSProto_Array);
         if (!group)
             return false;
         res->setGroup(group);
         return true;
     }
 
     if (native == js::array_concat) {
-        if (args.thisv().isObject() && args.thisv().toObject().is<ArrayObject>() &&
-            !args.thisv().toObject().isSingleton())
+        if (args.thisv().isObject() &&
+            args.thisv().toObject().is<ArrayObject>() &&
+            !args.thisv().toObject().isSingleton() &&
+            !args.thisv().toObject().group()->hasUnanalyzedPreliminaryObjects())
         {
             RootedObject proto(cx, args.thisv().toObject().getProto());
             res.set(NewDenseEmptyArray(cx, proto, TenuredObject));
             if (!res)
                 return false;
+
             res->setGroup(args.thisv().toObject().group());
             return true;
         }
     }
 
     if (native == js::str_split && args.length() == 1 && args[0].isString()) {
         res.set(NewDenseUnallocatedArray(cx, 0, NullPtr(), TenuredObject));
         if (!res)
@@ -12327,55 +12578,68 @@ ICGetElem_Dense::ICGetElem_Dense(JitCode
 
 /* static */ ICGetElem_Dense*
 ICGetElem_Dense::Clone(ICStubSpace* space, ICStub* firstMonitorStub,
                        ICGetElem_Dense& other)
 {
     return New<ICGetElem_Dense>(space, other.jitCode(), firstMonitorStub, other.shape_);
 }
 
+ICGetElem_UnboxedArray::ICGetElem_UnboxedArray(JitCode* stubCode, ICStub* firstMonitorStub,
+                                               ObjectGroup *group)
+  : ICMonitoredStub(GetElem_UnboxedArray, stubCode, firstMonitorStub),
+    group_(group)
+{ }
+
+/* static */ ICGetElem_UnboxedArray*
+ICGetElem_UnboxedArray::Clone(ICStubSpace* space, ICStub* firstMonitorStub,
+                              ICGetElem_UnboxedArray& other)
+{
+    return New<ICGetElem_UnboxedArray>(space, other.jitCode(), firstMonitorStub, other.group_);
+}
+
 ICGetElem_TypedArray::ICGetElem_TypedArray(JitCode* stubCode, Shape* shape, Scalar::Type type)
   : ICStub(GetElem_TypedArray, stubCode),
     shape_(shape)
 {
     extra_ = uint16_t(type);
     MOZ_ASSERT(extra_ == type);
 }
 
 /* static */ ICGetElem_Arguments*
 ICGetElem_Arguments::Clone(ICStubSpace* space, ICStub* firstMonitorStub,
                            ICGetElem_Arguments& other)
 {
     return New<ICGetElem_Arguments>(space, other.jitCode(), firstMonitorStub, other.which());
 }
 
-ICSetElem_Dense::ICSetElem_Dense(JitCode* stubCode, Shape* shape, ObjectGroup* group)
-  : ICUpdatedStub(SetElem_Dense, stubCode),
+ICSetElem_DenseOrUnboxedArray::ICSetElem_DenseOrUnboxedArray(JitCode* stubCode, Shape* shape, ObjectGroup* group)
+  : ICUpdatedStub(SetElem_DenseOrUnboxedArray, stubCode),
     shape_(shape),
     group_(group)
 { }
 
-ICSetElem_DenseAdd::ICSetElem_DenseAdd(JitCode* stubCode, ObjectGroup* group,
-                                       size_t protoChainDepth)
-  : ICUpdatedStub(SetElem_DenseAdd, stubCode),
+ICSetElem_DenseOrUnboxedArrayAdd::ICSetElem_DenseOrUnboxedArrayAdd(JitCode* stubCode, ObjectGroup* group,
+                                                                   size_t protoChainDepth)
+  : ICUpdatedStub(SetElem_DenseOrUnboxedArrayAdd, stubCode),
     group_(group)
 {
     MOZ_ASSERT(protoChainDepth <= MAX_PROTO_CHAIN_DEPTH);
     extra_ = protoChainDepth;
 }
 
 template <size_t ProtoChainDepth>
 ICUpdatedStub*
-ICSetElemDenseAddCompiler::getStubSpecific(ICStubSpace* space, const AutoShapeVector* shapes)
+ICSetElemDenseOrUnboxedArrayAddCompiler::getStubSpecific(ICStubSpace* space, const AutoShapeVector* shapes)
 {
     RootedObjectGroup group(cx, obj_->getGroup(cx));
     if (!group)
         return nullptr;
     Rooted<JitCode*> stubCode(cx, getStubCode());
-    return ICStub::New<ICSetElem_DenseAddImpl<ProtoChainDepth>>(space, stubCode, group, shapes);
+    return ICStub::New<ICSetElem_DenseOrUnboxedArrayAddImpl<ProtoChainDepth>>(space, stubCode, group, shapes);
 }
 
 ICSetElem_TypedArray::ICSetElem_TypedArray(JitCode* stubCode, Shape* shape, Scalar::Type type,
                                            bool expectOutOfBounds)
   : ICStub(SetElem_TypedArray, stubCode),
     shape_(shape)
 {
     extra_ = uint8_t(type);
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -392,22 +392,23 @@ class ICEntry
                                 \
     _(GetElem_Fallback)         \
     _(GetElem_NativeSlot)       \
     _(GetElem_NativePrototypeSlot) \
     _(GetElem_NativePrototypeCallNative) \
     _(GetElem_NativePrototypeCallScripted) \
     _(GetElem_String)           \
     _(GetElem_Dense)            \
+    _(GetElem_UnboxedArray)     \
     _(GetElem_TypedArray)       \
     _(GetElem_Arguments)        \
                                 \
     _(SetElem_Fallback)         \
-    _(SetElem_Dense)            \
-    _(SetElem_DenseAdd)         \
+    _(SetElem_DenseOrUnboxedArray) \
+    _(SetElem_DenseOrUnboxedArrayAdd) \
     _(SetElem_TypedArray)       \
                                 \
     _(In_Fallback)              \
     _(In_Native)                \
     _(In_NativePrototype)       \
     _(In_NativeDoesNotExist)    \
     _(In_Dense)                 \
                                 \
@@ -423,16 +424,17 @@ class ICEntry
                                 \
     _(BindName_Fallback)        \
                                 \
     _(GetIntrinsic_Fallback)    \
     _(GetIntrinsic_Constant)    \
                                 \
     _(GetProp_Fallback)         \
     _(GetProp_ArrayLength)      \
+    _(GetProp_UnboxedArrayLength) \
     _(GetProp_Primitive)        \
     _(GetProp_StringLength)     \
     _(GetProp_Native)           \
     _(GetProp_NativeDoesNotExist) \
     _(GetProp_NativePrototype)  \
     _(GetProp_Unboxed)          \
     _(GetProp_TypedObject)      \
     _(GetProp_CallScripted)     \
@@ -1732,41 +1734,55 @@ class ICThis_Fallback : public ICFallbac
         }
     };
 };
 
 class ICNewArray_Fallback : public ICFallbackStub
 {
     friend class ICStubSpace;
 
-    HeapPtrArrayObject templateObject_;
-
-    ICNewArray_Fallback(JitCode* stubCode, ArrayObject* templateObject)
-      : ICFallbackStub(ICStub::NewArray_Fallback, stubCode), templateObject_(templateObject)
+    HeapPtrObject templateObject_;
+
+    // The group used for objects created here is always available, even if the
+    // template object itself is not.
+    HeapPtrObjectGroup templateGroup_;
+
+    ICNewArray_Fallback(JitCode* stubCode, ObjectGroup* templateGroup)
+      : ICFallbackStub(ICStub::NewArray_Fallback, stubCode),
+        templateObject_(nullptr), templateGroup_(templateGroup)
     {}
 
   public:
     class Compiler : public ICStubCompiler {
-        RootedArrayObject templateObject;
+        RootedObjectGroup templateGroup;
         bool generateStubCode(MacroAssembler& masm);
 
       public:
-        Compiler(JSContext* cx, ArrayObject* templateObject)
+        Compiler(JSContext* cx, ObjectGroup* templateGroup)
           : ICStubCompiler(cx, ICStub::NewArray_Fallback),
-            templateObject(cx, templateObject)
+            templateGroup(cx, templateGroup)
         {}
 
         ICStub* getStub(ICStubSpace* space) {
-            return ICStub::New<ICNewArray_Fallback>(space, getStubCode(), templateObject);
+            return ICStub::New<ICNewArray_Fallback>(space, getStubCode(), templateGroup);
         }
     };
 
-    HeapPtrArrayObject& templateObject() {
+    HeapPtrObject& templateObject() {
         return templateObject_;
     }
+
+    void setTemplateObject(JSObject* obj) {
+        MOZ_ASSERT(obj->group() == templateGroup());
+        templateObject_ = obj;
+    }
+
+    HeapPtrObjectGroup& templateGroup() {
+        return templateGroup_;
+    }
 };
 
 class ICNewObject_Fallback : public ICFallbackStub
 {
     friend class ICStubSpace;
 
     HeapPtrObject templateObject_;
 
@@ -3021,16 +3037,64 @@ class ICGetElem_Dense : public ICMonitor
         {}
 
         ICStub* getStub(ICStubSpace* space) {
             return ICStub::New<ICGetElem_Dense>(space, getStubCode(), firstMonitorStub_, shape_);
         }
     };
 };
 
+class ICGetElem_UnboxedArray : public ICMonitoredStub
+{
+    friend class ICStubSpace;
+
+    HeapPtrObjectGroup group_;
+
+    ICGetElem_UnboxedArray(JitCode* stubCode, ICStub* firstMonitorStub, ObjectGroup* group);
+
+  public:
+    static ICGetElem_UnboxedArray* Clone(ICStubSpace* space, ICStub* firstMonitorStub,
+                                         ICGetElem_UnboxedArray& other);
+
+    static size_t offsetOfGroup() {
+        return offsetof(ICGetElem_UnboxedArray, group_);
+    }
+
+    HeapPtrObjectGroup& group() {
+        return group_;
+    }
+
+    class Compiler : public ICStubCompiler {
+      ICStub* firstMonitorStub_;
+      RootedObjectGroup group_;
+      JSValueType elementType_;
+
+      protected:
+        bool generateStubCode(MacroAssembler& masm);
+
+        virtual int32_t getKey() const {
+            return static_cast<int32_t>(kind) |
+                   (static_cast<int32_t>(elementType_) << 16);
+        }
+
+      public:
+        Compiler(JSContext* cx, ICStub* firstMonitorStub, ObjectGroup* group)
+          : ICStubCompiler(cx, ICStub::GetElem_UnboxedArray),
+            firstMonitorStub_(firstMonitorStub),
+            group_(cx, group),
+            elementType_(group->unboxedLayout().elementType())
+        {}
+
+        ICStub* getStub(ICStubSpace* space) {
+            return ICStub::New<ICGetElem_UnboxedArray>(space, getStubCode(), firstMonitorStub_,
+                                                       group_);
+        }
+    };
+};
+
 // Enum for stubs handling a combination of typed arrays and typed objects.
 enum TypedThingLayout {
     Layout_TypedArray,
     Layout_OutlineTypedObject,
     Layout_InlineTypedObject
 };
 
 static inline TypedThingLayout
@@ -3178,158 +3242,179 @@ class ICSetElem_Fallback : public ICFall
         { }
 
         ICStub* getStub(ICStubSpace* space) {
             return ICStub::New<ICSetElem_Fallback>(space, getStubCode());
         }
     };
 };
 
-class ICSetElem_Dense : public ICUpdatedStub
+class ICSetElem_DenseOrUnboxedArray : public ICUpdatedStub
 {
     friend class ICStubSpace;
 
-    HeapPtrShape shape_;
+    HeapPtrShape shape_; // null for unboxed arrays
     HeapPtrObjectGroup group_;
 
-    ICSetElem_Dense(JitCode* stubCode, Shape* shape, ObjectGroup* group);
+    ICSetElem_DenseOrUnboxedArray(JitCode* stubCode, Shape* shape, ObjectGroup* group);
 
   public:
     static size_t offsetOfShape() {
-        return offsetof(ICSetElem_Dense, shape_);
+        return offsetof(ICSetElem_DenseOrUnboxedArray, shape_);
     }
     static size_t offsetOfGroup() {
-        return offsetof(ICSetElem_Dense, group_);
+        return offsetof(ICSetElem_DenseOrUnboxedArray, group_);
     }
 
     HeapPtrShape& shape() {
         return shape_;
     }
     HeapPtrObjectGroup& group() {
         return group_;
     }
 
     class Compiler : public ICStubCompiler {
         RootedShape shape_;
-
-        // Compiler is only live on stack during compilation, it should
-        // outlive any RootedObjectGroup it's passed.  So it can just
-        // use the handle.
-        HandleObjectGroup group_;
+        RootedObjectGroup group_;
+        JSValueType unboxedType_;
 
         bool generateStubCode(MacroAssembler& masm);
 
       public:
+        virtual int32_t getKey() const {
+            return static_cast<int32_t>(kind) |
+                   (static_cast<int32_t>(unboxedType_) << 16);
+        }
+
         Compiler(JSContext* cx, Shape* shape, HandleObjectGroup group)
-          : ICStubCompiler(cx, ICStub::SetElem_Dense),
+          : ICStubCompiler(cx, ICStub::SetElem_DenseOrUnboxedArray),
             shape_(cx, shape),
-            group_(group)
+            group_(cx, group),
+            unboxedType_(shape ? JSVAL_TYPE_MAGIC : group->unboxedLayout().elementType())
         {}
 
         ICUpdatedStub* getStub(ICStubSpace* space) {
-            ICSetElem_Dense* stub = ICStub::New<ICSetElem_Dense>(space, getStubCode(), shape_, group_);
+            ICSetElem_DenseOrUnboxedArray* stub =
+                ICStub::New<ICSetElem_DenseOrUnboxedArray>(space, getStubCode(), shape_, group_);
             if (!stub || !stub->initUpdatingChain(cx, space))
                 return nullptr;
             return stub;
         }
+
+        bool needsUpdateStubs() {
+            return unboxedType_ == JSVAL_TYPE_MAGIC || unboxedType_ == JSVAL_TYPE_OBJECT;
+        }
     };
 };
 
-template <size_t ProtoChainDepth> class ICSetElem_DenseAddImpl;
-
-class ICSetElem_DenseAdd : public ICUpdatedStub
+template <size_t ProtoChainDepth> class ICSetElem_DenseOrUnboxedArrayAddImpl;
+
+class ICSetElem_DenseOrUnboxedArrayAdd : public ICUpdatedStub
 {
     friend class ICStubSpace;
 
   public:
     static const size_t MAX_PROTO_CHAIN_DEPTH = 4;
 
   protected:
     HeapPtrObjectGroup group_;
 
-    ICSetElem_DenseAdd(JitCode* stubCode, ObjectGroup* group, size_t protoChainDepth);
+    ICSetElem_DenseOrUnboxedArrayAdd(JitCode* stubCode, ObjectGroup* group, size_t protoChainDepth);
 
   public:
     static size_t offsetOfGroup() {
-        return offsetof(ICSetElem_DenseAdd, group_);
+        return offsetof(ICSetElem_DenseOrUnboxedArrayAdd, group_);
     }
 
     HeapPtrObjectGroup& group() {
         return group_;
     }
     size_t protoChainDepth() const {
         MOZ_ASSERT(extra_ <= MAX_PROTO_CHAIN_DEPTH);
         return extra_;
     }
 
     template <size_t ProtoChainDepth>
-    ICSetElem_DenseAddImpl<ProtoChainDepth>* toImplUnchecked() {
-        return static_cast<ICSetElem_DenseAddImpl<ProtoChainDepth>*>(this);
+    ICSetElem_DenseOrUnboxedArrayAddImpl<ProtoChainDepth>* toImplUnchecked() {
+        return static_cast<ICSetElem_DenseOrUnboxedArrayAddImpl<ProtoChainDepth>*>(this);
     }
 
     template <size_t ProtoChainDepth>
-    ICSetElem_DenseAddImpl<ProtoChainDepth>* toImpl() {
+    ICSetElem_DenseOrUnboxedArrayAddImpl<ProtoChainDepth>* toImpl() {
         MOZ_ASSERT(ProtoChainDepth == protoChainDepth());
         return toImplUnchecked<ProtoChainDepth>();
     }
 };
 
 template <size_t ProtoChainDepth>
-class ICSetElem_DenseAddImpl : public ICSetElem_DenseAdd
+class ICSetElem_DenseOrUnboxedArrayAddImpl : public ICSetElem_DenseOrUnboxedArrayAdd
 {
     friend class ICStubSpace;
 
+    // Note: for unboxed arrays, the first shape is null.
     static const size_t NumShapes = ProtoChainDepth + 1;
     mozilla::Array<HeapPtrShape, NumShapes> shapes_;
 
-    ICSetElem_DenseAddImpl(JitCode* stubCode, ObjectGroup* group,
-                           const AutoShapeVector* shapes)
-      : ICSetElem_DenseAdd(stubCode, group, ProtoChainDepth)
+    ICSetElem_DenseOrUnboxedArrayAddImpl(JitCode* stubCode, ObjectGroup* group,
+                                         const AutoShapeVector* shapes)
+      : ICSetElem_DenseOrUnboxedArrayAdd(stubCode, group, ProtoChainDepth)
     {
         MOZ_ASSERT(shapes->length() == NumShapes);
         for (size_t i = 0; i < NumShapes; i++)
             shapes_[i].init((*shapes)[i]);
     }
 
   public:
     void traceShapes(JSTracer* trc) {
-        for (size_t i = 0; i < NumShapes; i++)
-            TraceEdge(trc, &shapes_[i], "baseline-setelem-denseadd-stub-shape");
+        for (size_t i = 0; i < NumShapes; i++) {
+            if (shapes_[i])
+                TraceEdge(trc, &shapes_[i], "baseline-setelem-denseadd-stub-shape");
+        }
     }
     Shape* shape(size_t i) const {
         MOZ_ASSERT(i < NumShapes);
         return shapes_[i];
     }
     static size_t offsetOfShape(size_t idx) {
-        return offsetof(ICSetElem_DenseAddImpl, shapes_) + idx * sizeof(HeapPtrShape);
+        return offsetof(ICSetElem_DenseOrUnboxedArrayAddImpl, shapes_) + idx * sizeof(HeapPtrShape);
     }
 };
 
-class ICSetElemDenseAddCompiler : public ICStubCompiler {
+class ICSetElemDenseOrUnboxedArrayAddCompiler : public ICStubCompiler {
     RootedObject obj_;
     size_t protoChainDepth_;
+    JSValueType unboxedType_;
 
     bool generateStubCode(MacroAssembler& masm);
 
   protected:
     virtual int32_t getKey() const {
-        return static_cast<int32_t>(kind) | (static_cast<int32_t>(protoChainDepth_) << 16);
-    }
-
-  public:
-    ICSetElemDenseAddCompiler(JSContext* cx, HandleObject obj, size_t protoChainDepth)
-        : ICStubCompiler(cx, ICStub::SetElem_DenseAdd),
+        return static_cast<int32_t>(kind) |
+               (static_cast<int32_t>(protoChainDepth_) << 16) |
+               (static_cast<int32_t>(unboxedType_) << 19);
+    }
+
+  public:
+    ICSetElemDenseOrUnboxedArrayAddCompiler(JSContext* cx, HandleObject obj, size_t protoChainDepth)
+        : ICStubCompiler(cx, ICStub::SetElem_DenseOrUnboxedArrayAdd),
           obj_(cx, obj),
-          protoChainDepth_(protoChainDepth)
+          protoChainDepth_(protoChainDepth),
+          unboxedType_(obj->is<UnboxedArrayObject>()
+                       ? obj->as<UnboxedArrayObject>().elementType()
+                       : JSVAL_TYPE_MAGIC)
     {}
 
     template <size_t ProtoChainDepth>
     ICUpdatedStub* getStubSpecific(ICStubSpace* space, const AutoShapeVector* shapes);
 
     ICUpdatedStub* getStub(ICStubSpace* space);
+
+    bool needsUpdateStubs() {
+        return unboxedType_ == JSVAL_TYPE_MAGIC || unboxedType_ == JSVAL_TYPE_OBJECT;
+    }
 };
 
 // Accesses scalar elements of a typed array or typed object.
 class ICSetElem_TypedArray : public ICStub
 {
     friend class ICStubSpace;
 
   protected: // Protected to silence Clang warning.
@@ -3971,16 +4056,40 @@ class ICGetProp_ArrayLength : public ICS
         {}
 
         ICStub* getStub(ICStubSpace* space) {
             return ICStub::New<ICGetProp_ArrayLength>(space, getStubCode());
         }
     };
 };
 
+// Stub for accessing an unboxed array's length.
+class ICGetProp_UnboxedArrayLength : public ICStub
+{
+    friend class ICStubSpace;
+
+    explicit ICGetProp_UnboxedArrayLength(JitCode* stubCode)
+      : ICStub(GetProp_UnboxedArrayLength, stubCode)
+    {}
+
+  public:
+    class Compiler : public ICStubCompiler {
+        bool generateStubCode(MacroAssembler& masm);
+
+      public:
+        explicit Compiler(JSContext* cx)
+          : ICStubCompiler(cx, ICStub::GetProp_UnboxedArrayLength)
+        {}
+
+        ICStub* getStub(ICStubSpace* space) {
+            return ICStub::New<ICGetProp_UnboxedArrayLength>(space, getStubCode());
+        }
+    };
+};
+
 // Stub for accessing a property on a primitive's prototype.
 class ICGetProp_Primitive : public ICMonitoredStub
 {
     friend class ICStubSpace;
 
   protected: // Protected to silence Clang warning.
     // Shape of String.prototype/Number.prototype to check for.
     HeapPtrShape protoShape_;
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -16,19 +16,19 @@ using namespace js::jit;
 using mozilla::DebugOnly;
 
 bool
 SetElemICInspector::sawOOBDenseWrite() const
 {
     if (!icEntry_)
         return false;
 
-    // Check for a SetElem_DenseAdd stub.
+    // Check for an element adding stub.
     for (ICStub* stub = icEntry_->firstStub(); stub; stub = stub->next()) {
-        if (stub->isSetElem_DenseAdd())
+        if (stub->isSetElem_DenseOrUnboxedArrayAdd())
             return true;
     }
 
     // Check for a write hole bit on the SetElem_Fallback stub.
     ICStub* stub = icEntry_->fallbackStub();
     if (stub->isSetElem_Fallback())
         return stub->toSetElem_Fallback()->hasArrayWriteHole();
 
@@ -54,17 +54,17 @@ SetElemICInspector::sawOOBTypedArrayWrit
 bool
 SetElemICInspector::sawDenseWrite() const
 {
     if (!icEntry_)
         return false;
 
     // Check for a SetElem_DenseAdd or SetElem_Dense stub.
     for (ICStub* stub = icEntry_->firstStub(); stub; stub = stub->next()) {
-        if (stub->isSetElem_DenseAdd() || stub->isSetElem_Dense())
+        if (stub->isSetElem_DenseOrUnboxedArrayAdd() || stub->isSetElem_DenseOrUnboxedArray())
             return true;
     }
     return false;
 }
 
 bool
 SetElemICInspector::sawTypedArrayWrite() const
 {
@@ -458,16 +458,35 @@ BaselineInspector::getTemplateObject(jsb
           default:
             break;
         }
     }
 
     return nullptr;
 }
 
+ObjectGroup*
+BaselineInspector::getTemplateObjectGroup(jsbytecode* pc)
+{
+    if (!hasBaselineScript())
+        return nullptr;
+
+    const ICEntry& entry = icEntryFromPC(pc);
+    for (ICStub* stub = entry.firstStub(); stub; stub = stub->next()) {
+        switch (stub->kind()) {
+          case ICStub::NewArray_Fallback:
+            return stub->toNewArray_Fallback()->templateGroup();
+          default:
+            break;
+        }
+    }
+
+    return nullptr;
+}
+
 JSFunction*
 BaselineInspector::getSingleCallee(jsbytecode* pc)
 {
     MOZ_ASSERT(*pc == JSOP_NEW);
 
     if (!hasBaselineScript())
         return nullptr;
 
--- a/js/src/jit/BaselineInspector.h
+++ b/js/src/jit/BaselineInspector.h
@@ -112,16 +112,20 @@ class BaselineInspector
     bool hasSeenNonStringIterMore(jsbytecode* pc);
 
     bool isOptimizableCallStringSplit(jsbytecode* pc, JSString** stringOut, JSString** stringArg,
                                       NativeObject** objOut);
     JSObject* getTemplateObject(jsbytecode* pc);
     JSObject* getTemplateObjectForNative(jsbytecode* pc, Native native);
     JSObject* getTemplateObjectForClassHook(jsbytecode* pc, const Class* clasp);
 
+    // Sometimes the group a template object will have is known, even if the
+    // object itself isn't.
+    ObjectGroup* getTemplateObjectGroup(jsbytecode* pc);
+
     JSFunction* getSingleCallee(jsbytecode* pc);
 
     DeclEnvObject* templateDeclEnvObject();
     CallObject* templateCallObject();
 
     bool commonGetPropFunction(jsbytecode* pc, JSObject** holder, Shape** holderShape,
                                JSFunction** commonGetter, Shape** globalShape, bool* isOwnProperty,
                                ReceiverVector& receivers, ObjectGroupVector& convertUnboxedGroups);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -2334,16 +2334,28 @@ CodeGenerator::visitGetPropertyPolymorph
     Register obj = ToRegister(ins->obj());
     TypedOrValueRegister output(ins->mir()->type(), ToAnyRegister(ins->output()));
     Register temp = (output.type() == MIRType_Double)
                     ? ToRegister(ins->temp())
                     : output.typedReg().gpr();
     emitGetPropertyPolymorphic(ins, obj, temp, output);
 }
 
+template <typename T>
+static void
+EmitUnboxedPreBarrier(MacroAssembler &masm, T address, JSValueType type)
+{
+    if (type == JSVAL_TYPE_OBJECT)
+        masm.patchableCallPreBarrier(address, MIRType_Object);
+    else if (type == JSVAL_TYPE_STRING)
+        masm.patchableCallPreBarrier(address, MIRType_String);
+    else
+        MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(type));
+}
+
 void
 CodeGenerator::emitSetPropertyPolymorphic(LInstruction* ins, Register obj, Register scratch,
                                           const ConstantOrRegister& value)
 {
     MSetPropertyPolymorphic* mir = ins->mirRaw()->toSetPropertyPolymorphic();
 
     Label done;
     for (size_t i = 0; i < mir->numReceivers(); i++) {
@@ -2372,23 +2384,17 @@ CodeGenerator::emitSetPropertyPolymorphi
                     emitPreBarrier(addr);
                 masm.storeConstantOrRegister(value, addr);
             }
         } else {
             const UnboxedLayout::Property* property =
                 receiver.group->unboxedLayout().lookup(mir->name());
             Address propertyAddr(obj, UnboxedPlainObject::offsetOfData() + property->offset);
 
-            if (property->type == JSVAL_TYPE_OBJECT)
-                masm.patchableCallPreBarrier(propertyAddr, MIRType_Object);
-            else if (property->type == JSVAL_TYPE_STRING)
-                masm.patchableCallPreBarrier(propertyAddr, MIRType_String);
-            else
-                MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(property->type));
-
+            EmitUnboxedPreBarrier(masm, propertyAddr, property->type);
             masm.storeUnboxedProperty(propertyAddr, property->type, value, nullptr);
         }
 
         if (i == mir->numReceivers() - 1) {
             bailoutFrom(&next, ins->snapshot());
         } else {
             masm.jump(&done);
             masm.bind(&next);
@@ -2420,17 +2426,19 @@ CodeGenerator::visitSetPropertyPolymorph
         value = TypedOrValueRegister(ins->mir()->value()->type(), ToAnyRegister(ins->value()));
 
     emitSetPropertyPolymorphic(ins, obj, temp, value);
 }
 
 void
 CodeGenerator::visitElements(LElements* lir)
 {
-    Address elements(ToRegister(lir->object()), NativeObject::offsetOfElements());
+    Address elements(ToRegister(lir->object()),
+                     lir->mir()->unboxed() ? UnboxedArrayObject::offsetOfElements()
+                                           : NativeObject::offsetOfElements());
     masm.loadPtr(elements, ToRegister(lir->output()));
 }
 
 typedef bool (*ConvertElementsToDoublesFn)(JSContext*, uintptr_t);
 static const VMFunction ConvertElementsToDoublesInfo =
     FunctionInfo<ConvertElementsToDoublesFn>(ObjectElements::ConvertElementsToDoubles);
 
 void
@@ -4051,38 +4059,50 @@ class OutOfLineNewArray : public OutOfLi
         codegen->visitOutOfLineNewArray(this);
     }
 
     LNewArray* lir() const {
         return lir_;
     }
 };
 
+typedef JSObject* (*NewArrayOperationFn)(JSContext*, HandleScript, jsbytecode*, uint32_t,
+                                         NewObjectKind);
+static const VMFunction NewArrayOperationInfo =
+    FunctionInfo<NewArrayOperationFn>(NewArrayOperation);
+
 typedef ArrayObject* (*NewDenseArrayFn)(ExclusiveContext*, uint32_t, HandleObjectGroup,
                                         AllocatingBehaviour, bool);
 static const VMFunction NewDenseArrayInfo = FunctionInfo<NewDenseArrayFn>(NewDenseArray);
 
 void
 CodeGenerator::visitNewArrayCallVM(LNewArray* lir)
 {
     Register objReg = ToRegister(lir->output());
 
     MOZ_ASSERT(!lir->isCall());
     saveLive(lir);
 
     JSObject* templateObject = lir->mir()->templateObject();
-    ObjectGroup* group =
-        templateObject->isSingleton() ? nullptr : templateObject->group();
-
-    pushArg(Imm32(lir->mir()->convertDoubleElements()));
-    pushArg(Imm32(lir->mir()->allocatingBehaviour()));
-    pushArg(ImmGCPtr(group));
-    pushArg(Imm32(lir->mir()->count()));
-
-    callVM(NewDenseArrayInfo, lir);
+
+    if (templateObject && !templateObject->is<UnboxedArrayObject>()) {
+        pushArg(Imm32(lir->mir()->convertDoubleElements()));
+        pushArg(Imm32(lir->mir()->allocatingBehaviour()));
+        pushArg(ImmGCPtr(templateObject->group()));
+        pushArg(Imm32(lir->mir()->count()));
+
+        callVM(NewDenseArrayInfo, lir);
+    } else {
+        pushArg(Imm32(GenericObject));
+        pushArg(Imm32(lir->mir()->count()));
+        pushArg(ImmPtr(lir->mir()->pc()));
+        pushArg(ImmGCPtr(lir->mir()->block()->info().script()));
+
+        callVM(NewArrayOperationInfo, lir);
+    }
 
     if (ReturnReg != objReg)
         masm.movePtr(ReturnReg, objReg);
 
     restoreLive(lir);
 }
 
 typedef JSObject* (*NewDerivedTypedObjectFn)(JSContext*,
@@ -4142,17 +4162,17 @@ CodeGenerator::visitHypot(LHypot* lir)
     MOZ_ASSERT(ToFloatRegister(lir->output()) == ReturnDoubleReg);
 }
 
 void
 CodeGenerator::visitNewArray(LNewArray* lir)
 {
     Register objReg = ToRegister(lir->output());
     Register tempReg = ToRegister(lir->temp());
-    ArrayObject* templateObject = lir->mir()->templateObject();
+    JSObject* templateObject = lir->mir()->templateObject();
     DebugOnly<uint32_t> count = lir->mir()->count();
 
     MOZ_ASSERT(count < NativeObject::NELEMENTS_LIMIT);
 
     if (lir->mir()->shouldUseVM()) {
         visitNewArrayCallVM(lir);
         return;
     }
@@ -6272,16 +6292,40 @@ CodeGenerator::visitSetInitializedLength
 
     masm.bumpKey(&index, 1);
     masm.storeKey(index, initLength);
     // Restore register value if it is used/captured after.
     masm.bumpKey(&index, -1);
 }
 
 void
+CodeGenerator::visitUnboxedArrayLength(LUnboxedArrayLength* lir)
+{
+    Register obj = ToRegister(lir->object());
+    Register result = ToRegister(lir->output());
+    masm.load32(Address(obj, UnboxedArrayObject::offsetOfLength()), result);
+}
+
+void
+CodeGenerator::visitUnboxedArrayInitializedLength(LUnboxedArrayInitializedLength* lir)
+{
+    Register obj = ToRegister(lir->object());
+    Register result = ToRegister(lir->output());
+    masm.load32(Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()), result);
+    masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), result);
+}
+
+void
+CodeGenerator::visitIncrementUnboxedArrayInitializedLength(LIncrementUnboxedArrayInitializedLength* lir)
+{
+    Register obj = ToRegister(lir->object());
+    masm.add32(Imm32(1), Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()));
+}
+
+void
 CodeGenerator::visitNotO(LNotO* lir)
 {
     MOZ_ASSERT(lir->mir()->operandMightEmulateUndefined(),
                "This should be constant-folded if the object can't emulate undefined.");
 
     OutOfLineTestObjectWithLabels* ool = new(alloc()) OutOfLineTestObjectWithLabels();
     addOutOfLineCode(ool, lir->mir());
 
@@ -6475,28 +6519,31 @@ CodeGenerator::emitStoreHoleCheck(Regist
         masm.branchTestMagic(Assembler::Equal, dest, &bail);
     } else {
         BaseIndex dest(elements, ToRegister(index), TimesEight, offsetAdjustment);
         masm.branchTestMagic(Assembler::Equal, dest, &bail);
     }
     bailoutFrom(&bail, snapshot);
 }
 
+static ConstantOrRegister
+ToConstantOrRegister(const LAllocation* value, MIRType valueType)
+{
+    if (value->isConstant())
+        return ConstantOrRegister(*value->toConstant());
+    return TypedOrValueRegister(valueType, ToAnyRegister(value));
+}
+
 void
 CodeGenerator::emitStoreElementTyped(const LAllocation* value,
                                      MIRType valueType, MIRType elementType,
                                      Register elements, const LAllocation* index,
                                      int32_t offsetAdjustment)
 {
-    ConstantOrRegister v;
-    if (value->isConstant())
-        v = ConstantOrRegister(*value->toConstant());
-    else
-        v = TypedOrValueRegister(valueType, ToAnyRegister(value));
-
+    ConstantOrRegister v = ToConstantOrRegister(value, valueType);
     if (index->isConstant()) {
         Address dest(elements, ToInt32(index) * sizeof(js::Value) + offsetAdjustment);
         masm.storeUnboxedValue(v, valueType, dest, elementType);
     } else {
         BaseIndex dest(elements, ToRegister(index), TimesEight, offsetAdjustment);
         masm.storeUnboxedValue(v, valueType, dest, elementType);
     }
 }
@@ -6543,124 +6590,203 @@ CodeGenerator::visitStoreElementV(LStore
 }
 
 void
 CodeGenerator::visitStoreElementHoleT(LStoreElementHoleT* lir)
 {
     OutOfLineStoreElementHole* ool = new(alloc()) OutOfLineStoreElementHole(lir);
     addOutOfLineCode(ool, lir->mir());
 
+    Register obj = ToRegister(lir->object());
     Register elements = ToRegister(lir->elements());
     const LAllocation* index = lir->index();
 
-    // OOL path if index >= initializedLength.
-    Address initLength(elements, ObjectElements::offsetOfInitializedLength());
-    masm.branchKey(Assembler::BelowOrEqual, initLength, ToInt32Key(index), ool->entry());
-
-    if (lir->mir()->needsBarrier())
-        emitPreBarrier(elements, index);
-
-    masm.bind(ool->rejoinStore());
-    emitStoreElementTyped(lir->value(), lir->mir()->value()->type(), lir->mir()->elementType(),
-                          elements, index, 0);
+    JSValueType unboxedType = lir->mir()->unboxedType();
+    if (unboxedType == JSVAL_TYPE_MAGIC) {
+        Address initLength(elements, ObjectElements::offsetOfInitializedLength());
+        masm.branchKey(Assembler::BelowOrEqual, initLength, ToInt32Key(index), ool->entry());
+
+        if (lir->mir()->needsBarrier())
+            emitPreBarrier(elements, index);
+
+        masm.bind(ool->rejoinStore());
+        emitStoreElementTyped(lir->value(), lir->mir()->value()->type(), lir->mir()->elementType(),
+                              elements, index, 0);
+    } else {
+        Register temp = ToRegister(lir->getTemp(0));
+        Address initLength(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
+        masm.load32(initLength, temp);
+        masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), temp);
+        masm.branchKey(Assembler::BelowOrEqual, temp, ToInt32Key(index), ool->entry());
+
+        ConstantOrRegister v = ToConstantOrRegister(lir->value(), lir->mir()->value()->type());
+
+        if (index->isConstant()) {
+            Address address(elements, ToInt32(index) * UnboxedTypeSize(unboxedType));
+            EmitUnboxedPreBarrier(masm, address, unboxedType);
+
+            masm.bind(ool->rejoinStore());
+            masm.storeUnboxedProperty(address, unboxedType, v, nullptr);
+        } else {
+            BaseIndex address(elements, ToRegister(index),
+                              ScaleFromElemWidth(UnboxedTypeSize(unboxedType)));
+            EmitUnboxedPreBarrier(masm, address, unboxedType);
+
+            masm.bind(ool->rejoinStore());
+            masm.storeUnboxedProperty(address, unboxedType, v, nullptr);
+        }
+    }
 
     masm.bind(ool->rejoin());
 }
 
 void
 CodeGenerator::visitStoreElementHoleV(LStoreElementHoleV* lir)
 {
     OutOfLineStoreElementHole* ool = new(alloc()) OutOfLineStoreElementHole(lir);
     addOutOfLineCode(ool, lir->mir());
 
+    Register obj = ToRegister(lir->object());
     Register elements = ToRegister(lir->elements());
     const LAllocation* index = lir->index();
     const ValueOperand value = ToValue(lir, LStoreElementHoleV::Value);
 
-    // OOL path if index >= initializedLength.
-    Address initLength(elements, ObjectElements::offsetOfInitializedLength());
-    masm.branchKey(Assembler::BelowOrEqual, initLength, ToInt32Key(index), ool->entry());
-
-    if (lir->mir()->needsBarrier())
-        emitPreBarrier(elements, index);
-
-    masm.bind(ool->rejoinStore());
-    if (lir->index()->isConstant())
-        masm.storeValue(value, Address(elements, ToInt32(lir->index()) * sizeof(js::Value)));
-    else
-        masm.storeValue(value, BaseIndex(elements, ToRegister(lir->index()), TimesEight));
+    JSValueType unboxedType = lir->mir()->unboxedType();
+    if (unboxedType == JSVAL_TYPE_MAGIC) {
+        Address initLength(elements, ObjectElements::offsetOfInitializedLength());
+        masm.branchKey(Assembler::BelowOrEqual, initLength, ToInt32Key(index), ool->entry());
+
+        if (lir->mir()->needsBarrier())
+            emitPreBarrier(elements, index);
+
+        masm.bind(ool->rejoinStore());
+        if (index->isConstant())
+            masm.storeValue(value, Address(elements, ToInt32(index) * sizeof(js::Value)));
+        else
+            masm.storeValue(value, BaseIndex(elements, ToRegister(index), TimesEight));
+    } else {
+        Register temp = ToRegister(lir->getTemp(0));
+        Address initLength(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
+        masm.load32(initLength, temp);
+        masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), temp);
+        masm.branchKey(Assembler::BelowOrEqual, temp, ToInt32Key(index), ool->entry());
+
+        if (index->isConstant()) {
+            Address address(elements, ToInt32(index) * UnboxedTypeSize(unboxedType));
+            EmitUnboxedPreBarrier(masm, address, unboxedType);
+
+            masm.bind(ool->rejoinStore());
+            masm.storeUnboxedProperty(address, unboxedType, ConstantOrRegister(value), nullptr);
+        } else {
+            BaseIndex address(elements, ToRegister(index),
+                              ScaleFromElemWidth(UnboxedTypeSize(unboxedType)));
+            EmitUnboxedPreBarrier(masm, address, unboxedType);
+
+            masm.bind(ool->rejoinStore());
+            masm.storeUnboxedProperty(address, unboxedType, ConstantOrRegister(value), nullptr);
+        }
+    }
 
     masm.bind(ool->rejoin());
 }
 
-typedef bool (*SetDenseElementFn)(JSContext*, HandleNativeObject, int32_t, HandleValue,
-                                  bool strict);
-static const VMFunction SetDenseElementInfo = FunctionInfo<SetDenseElementFn>(SetDenseElement);
+typedef bool (*SetDenseOrUnboxedArrayElementFn)(JSContext*, HandleObject, int32_t,
+                                                HandleValue, bool strict);
+static const VMFunction SetDenseOrUnboxedArrayElementInfo =
+    FunctionInfo<SetDenseOrUnboxedArrayElementFn>(SetDenseOrUnboxedArrayElement);
 
 void
 CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
 {
     Register object, elements;
     LInstruction* ins = ool->ins();
     const LAllocation* index;
     MIRType valueType;
     ConstantOrRegister value;
+    JSValueType unboxedType;
+    LDefinition *temp = nullptr;
 
     if (ins->isStoreElementHoleV()) {
         LStoreElementHoleV* store = ins->toStoreElementHoleV();
         object = ToRegister(store->object());
         elements = ToRegister(store->elements());
         index = store->index();
         valueType = store->mir()->value()->type();
         value = TypedOrValueRegister(ToValue(store, LStoreElementHoleV::Value));
+        unboxedType = store->mir()->unboxedType();
+        temp = store->getTemp(0);
     } else {
         LStoreElementHoleT* store = ins->toStoreElementHoleT();
         object = ToRegister(store->object());
         elements = ToRegister(store->elements());
         index = store->index();
         valueType = store->mir()->value()->type();
         if (store->value()->isConstant())
             value = ConstantOrRegister(*store->value()->toConstant());
         else
             value = TypedOrValueRegister(valueType, ToAnyRegister(store->value()));
+        unboxedType = store->mir()->unboxedType();
+        temp = store->getTemp(0);
     }
 
     // If index == initializedLength, try to bump the initialized length inline.
     // If index > initializedLength, call a stub. Note that this relies on the
     // condition flags sticking from the incoming branch.
     Label callStub;
 #ifdef JS_CODEGEN_MIPS
     // Had to reimplement for MIPS because there are no flags.
-    Address initLength(elements, ObjectElements::offsetOfInitializedLength());
-    masm.branchKey(Assembler::NotEqual, initLength, ToInt32Key(index), &callStub);
+    if (unboxedType == JSVAL_TYPE_MAGIC) {
+        Address initLength(elements, ObjectElements::offsetOfInitializedLength());
+        masm.branchKey(Assembler::NotEqual, initLength, ToInt32Key(index), &callStub);
+    } else {
+        Address initLength(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
+        masm.load32(initLength, ToRegister(temp));
+        masm.and32(Imm32(UnboxedArrayObject::InitializedLengthMask), ToRegister(temp));
+        masm.branchKey(Assembler::NotEqual, ToRegister(temp), ToInt32Key(index), &callStub);
+    }
 #else
     masm.j(Assembler::NotEqual, &callStub);
 #endif
 
     Int32Key key = ToInt32Key(index);
 
-    // Check array capacity.
-    masm.branchKey(Assembler::BelowOrEqual, Address(elements, ObjectElements::offsetOfCapacity()),
-                   key, &callStub);
-
-    // Update initialized length. The capacity guard above ensures this won't overflow,
-    // due to NELEMENTS_LIMIT.
-    masm.bumpKey(&key, 1);
-    masm.storeKey(key, Address(elements, ObjectElements::offsetOfInitializedLength()));
-
-    // Update length if length < initializedLength.
-    Label dontUpdate;
-    masm.branchKey(Assembler::AboveOrEqual, Address(elements, ObjectElements::offsetOfLength()),
-                   key, &dontUpdate);
-    masm.storeKey(key, Address(elements, ObjectElements::offsetOfLength()));
-    masm.bind(&dontUpdate);
-
-    masm.bumpKey(&key, -1);
-
-    if (ins->isStoreElementHoleT() && valueType != MIRType_Double) {
+    if (unboxedType == JSVAL_TYPE_MAGIC) {
+        // Check array capacity.
+        masm.branchKey(Assembler::BelowOrEqual, Address(elements, ObjectElements::offsetOfCapacity()),
+                       key, &callStub);
+
+        // Update initialized length. The capacity guard above ensures this won't overflow,
+        // due to NELEMENTS_LIMIT.
+        masm.bumpKey(&key, 1);
+        masm.storeKey(key, Address(elements, ObjectElements::offsetOfInitializedLength()));
+
+        // Update length if length < initializedLength.
+        Label dontUpdate;
+        masm.branchKey(Assembler::AboveOrEqual, Address(elements, ObjectElements::offsetOfLength()),
+                       key, &dontUpdate);
+        masm.storeKey(key, Address(elements, ObjectElements::offsetOfLength()));
+        masm.bind(&dontUpdate);
+
+        masm.bumpKey(&key, -1);
+    } else {
+        // Check array capacity.
+        masm.checkUnboxedArrayCapacity(object, key, ToRegister(temp), &callStub);
+
+        // Update initialized length.
+        masm.add32(Imm32(1), Address(object, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()));
+
+        // Update length if length < initializedLength.
+        Address lengthAddr(object, UnboxedArrayObject::offsetOfLength());
+        Label dontUpdate;
+        masm.branchKey(Assembler::Above, lengthAddr, key, &dontUpdate);
+        masm.add32(Imm32(1), lengthAddr);
+        masm.bind(&dontUpdate);
+    }
+
+    if (ins->isStoreElementHoleT() && unboxedType == JSVAL_TYPE_MAGIC && valueType != MIRType_Double) {
         // The inline path for StoreElementHoleT does not always store the type tag,
         // so we do the store on the OOL path. We use MIRType_None for the element type
         // so that storeElementTyped will always store the type tag.
         emitStoreElementTyped(ins->toStoreElementHoleT()->value(), valueType, MIRType_None,
                               elements, index, 0);
         masm.jump(ool->rejoin());
     } else {
         // Jump to the inline path where we will store the value.
@@ -6672,17 +6798,17 @@ CodeGenerator::visitOutOfLineStoreElemen
 
     pushArg(Imm32(current->mir()->strict()));
     pushArg(value);
     if (index->isConstant())
         pushArg(Imm32(ToInt32(index)));
     else
         pushArg(ToRegister(index));
     pushArg(object);
-    callVM(SetDenseElementInfo, ins);
+    callVM(SetDenseOrUnboxedArrayElementInfo, ins);
 
     restoreLive(ins);
     masm.jump(ool->rejoin());
 }
 
 template <typename T>
 static void
 StoreUnboxedPointer(MacroAssembler& masm, T address, MIRType type, const LAllocation* value)
@@ -8575,23 +8701,37 @@ CodeGenerator::visitLoadElementHole(LLoa
     Register initLength = ToRegister(lir->initLength());
     const ValueOperand out = ToOutValue(lir);
 
     const MLoadElementHole* mir = lir->mir();
 
     // If the index is out of bounds, load |undefined|. Otherwise, load the
     // value.
     Label undefined, done;
-    if (lir->index()->isConstant()) {
+    if (lir->index()->isConstant())
         masm.branch32(Assembler::BelowOrEqual, initLength, Imm32(ToInt32(lir->index())), &undefined);
-        NativeObject::elementsSizeMustNotOverflow();
-        masm.loadValue(Address(elements, ToInt32(lir->index()) * sizeof(Value)), out);
+    else
+        masm.branch32(Assembler::BelowOrEqual, initLength, ToRegister(lir->index()), &undefined);
+
+    if (mir->unboxedType() != JSVAL_TYPE_MAGIC) {
+        size_t width = UnboxedTypeSize(mir->unboxedType());
+        if (lir->index()->isConstant()) {
+            Address addr(elements, ToInt32(lir->index()) * width);
+            masm.loadUnboxedProperty(addr, mir->unboxedType(), out);
+        } else {
+            BaseIndex addr(elements, ToRegister(lir->index()), ScaleFromElemWidth(width));
+            masm.loadUnboxedProperty(addr, mir->unboxedType(), out);
+        }
     } else {
-        masm.branch32(Assembler::BelowOrEqual, initLength, ToRegister(lir->index()), &undefined);
-        masm.loadValue(BaseObjectElementIndex(elements, ToRegister(lir->index())), out);
+        if (lir->index()->isConstant()) {
+            NativeObject::elementsSizeMustNotOverflow();
+            masm.loadValue(Address(elements, ToInt32(lir->index()) * sizeof(Value)), out);
+        } else {
+            masm.loadValue(BaseObjectElementIndex(elements, ToRegister(lir->index())), out);
+        }
     }
 
     // If a hole check is needed, and the value wasn't a hole, we're done.
     // Otherwise, we'll load undefined.
     if (lir->mir()->needsHoleCheck())
         masm.branchTestMagic(Assembler::NotEqual, out, &done);
     else
         masm.jump(&done);
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -183,16 +183,19 @@ class CodeGenerator : public CodeGenerat
     void visitSetDisjointTypedElements(LSetDisjointTypedElements* lir);
     void visitTypedObjectElements(LTypedObjectElements* lir);
     void visitSetTypedObjectOffset(LSetTypedObjectOffset* lir);
     void visitTypedObjectDescr(LTypedObjectDescr* ins);
     void visitStringLength(LStringLength* lir);
     void visitSubstr(LSubstr* lir);
     void visitInitializedLength(LInitializedLength* lir);
     void visitSetInitializedLength(LSetInitializedLength* lir);
+    void visitUnboxedArrayLength(LUnboxedArrayLength* lir);
+    void visitUnboxedArrayInitializedLength(LUnboxedArrayInitializedLength* lir);
+    void visitIncrementUnboxedArrayInitializedLength(LIncrementUnboxedArrayInitializedLength* lir);
     void visitNotO(LNotO* ins);
     void visitNotV(LNotV* ins);
     void visitBoundsCheck(LBoundsCheck* lir);
     void visitBoundsCheckRange(LBoundsCheckRange* lir);
     void visitBoundsCheckLower(LBoundsCheckLower* lir);
     void visitLoadFixedSlotV(LLoadFixedSlotV* ins);
     void visitLoadFixedSlotT(LLoadFixedSlotT* ins);
     void visitStoreFixedSlotV(LStoreFixedSlotV* ins);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -6472,47 +6472,39 @@ IonBuilder::jsop_compare(JSOp op)
         return false;
     return true;
 }
 
 bool
 IonBuilder::jsop_newarray(uint32_t count)
 {
     JSObject* templateObject = inspector->getTemplateObject(pc);
-    if (!templateObject) {
-        if (info().analysisMode() == Analysis_ArgumentsUsage) {
-            MUnknownValue* unknown = MUnknownValue::New(alloc());
-            current->add(unknown);
-            current->push(unknown);
-            return true;
-        }
-        return abort("No template object for NEWARRAY");
-    }
-
-    MOZ_ASSERT(templateObject->is<ArrayObject>());
-    if (templateObject->group()->unknownProperties()) {
-        if (info().analysisMode() == Analysis_ArgumentsUsage) {
-            MUnknownValue* unknown = MUnknownValue::New(alloc());
-            current->add(unknown);
-            current->push(unknown);
-            return true;
-        }
-        // We will get confused in jsop_initelem_array if we can't find the
-        // object group being initialized.
-        return abort("New array has unknown properties");
-    }
-
-    MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
+    gc::InitialHeap heap;
+    MConstant* templateConst;
+
+    if (templateObject) {
+        heap = templateObject->group()->initialHeap(constraints());
+        templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
+    } else {
+        heap = gc::DefaultHeap;
+        templateConst = MConstant::New(alloc(), NullValue());
+    }
     current->add(templateConst);
 
     MNewArray* ins = MNewArray::New(alloc(), constraints(), count, templateConst,
-                                    templateObject->group()->initialHeap(constraints()),
-                                    NewArray_FullyAllocating);
+                                    heap, NewArray_FullyAllocating, pc);
     current->add(ins);
     current->push(ins);
+
+    ObjectGroup* templateGroup = inspector->getTemplateObjectGroup(pc);
+    if (templateGroup) {
+        TemporaryTypeSet* types = MakeSingletonTypeSet(constraints(), templateGroup);
+        ins->setResultTypeSet(types);
+    }
+
     return true;
 }
 
 bool
 IonBuilder::jsop_newarray_copyonwrite()
 {
     ArrayObject* templateObject = ObjectGroup::getCopyOnWriteObject(script(), pc);
 
@@ -6576,68 +6568,93 @@ IonBuilder::jsop_initelem_array()
 {
     MDefinition* value = current->pop();
     MDefinition* obj = current->peek(-1);
 
     // Make sure that arrays have the type being written to them by the
     // intializer, and that arrays are marked as non-packed when writing holes
     // to them during initialization.
     bool needStub = false;
-    if (obj->isUnknownValue() || shouldAbortOnPreliminaryGroups(obj)) {
+    JSValueType unboxedType = JSVAL_TYPE_MAGIC;
+    if (shouldAbortOnPreliminaryGroups(obj)) {
+        needStub = true;
+    } else if (!obj->resultTypeSet() ||
+               obj->resultTypeSet()->unknownObject() ||
+               obj->resultTypeSet()->getObjectCount() != 1)
+    {
         needStub = true;
     } else {
+        MOZ_ASSERT(obj->resultTypeSet()->getObjectCount() == 1);
         TypeSet::ObjectKey* initializer = obj->resultTypeSet()->getObject(0);
+        if (initializer->clasp() == &UnboxedArrayObject::class_) {
+            if (initializer->group()->unboxedLayout().nativeGroup())
+                needStub = true;
+            else
+                unboxedType = initializer->group()->unboxedLayout().elementType();
+        }
         if (value->type() == MIRType_MagicHole) {
             if (!initializer->hasFlags(constraints(), OBJECT_FLAG_NON_PACKED))
                 needStub = true;
         } else if (!initializer->unknownProperties()) {
             HeapTypeSetKey elemTypes = initializer->property(JSID_VOID);
             if (!TypeSetIncludes(elemTypes.maybeTypes(), value->type(), value->resultTypeSet())) {
                 elemTypes.freeze(constraints());
                 needStub = true;
             }
         }
     }
 
-    if (NeedsPostBarrier(info(), value))
-        current->add(MPostWriteBarrier::New(alloc(), obj, value));
-
     if (needStub) {
         MCallInitElementArray* store = MCallInitElementArray::New(alloc(), obj, GET_UINT24(pc), value);
         current->add(store);
         return resumeAfter(store);
     }
 
     MConstant* id = MConstant::New(alloc(), Int32Value(GET_UINT24(pc)));
     current->add(id);
 
     // Get the elements vector.
-    MElements* elements = MElements::New(alloc(), obj);
+    MElements* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC);
     current->add(elements);
 
-    if (obj->toNewArray()->convertDoubleElements()) {
-        MInstruction* valueDouble = MToDouble::New(alloc(), value);
-        current->add(valueDouble);
-        value = valueDouble;
-    }
-
-    // Store the value.
-    MStoreElement* store = MStoreElement::New(alloc(), elements, id, value, /* needsHoleCheck = */ false);
-    current->add(store);
-
-    // Update the initialized length. (The template object for this array has
-    // the array's ultimate length, so the length field is already correct: no
-    // updating needed.)
-    MSetInitializedLength* initLength = MSetInitializedLength::New(alloc(), elements, id);
-    current->add(initLength);
-
-    if (!resumeAfter(initLength))
-        return false;
-
-   return true;
+    if (unboxedType != JSVAL_TYPE_MAGIC) {
+        // Note: storeUnboxedValue takes care of any post barriers on the value.
+        storeUnboxedValue(obj, elements, 0, id, unboxedType, value);
+
+        MInstruction* increment = MIncrementUnboxedArrayInitializedLength::New(alloc(), obj);
+        current->add(increment);
+
+        if (!resumeAfter(increment))
+            return false;
+    } else {
+        if (NeedsPostBarrier(info(), value))
+            current->add(MPostWriteBarrier::New(alloc(), obj, value));
+
+        if (obj->toNewArray()->convertDoubleElements()) {
+            MInstruction* valueDouble = MToDouble::New(alloc(), value);
+            current->add(valueDouble);
+            value = valueDouble;
+        }
+
+        // Store the value.
+        MStoreElement* store = MStoreElement::New(alloc(), elements, id, value,
+                                                  /* needsHoleCheck = */ false);
+        current->add(store);
+
+        // Update the initialized length. (The template object for this array has
+        // the array's ultimate length, so the length field is already correct: no
+        // updating needed.)
+        MSetInitializedLength* initLength = MSetInitializedLength::New(alloc(), elements, id);
+        current->add(initLength);
+
+        if (!resumeAfter(initLength))
+            return false;
+    }
+
+    return true;
 }
 
 bool
 IonBuilder::jsop_mutateproto()
 {
     MDefinition* value = current->pop();
     MDefinition* obj = current->peek(-1);
 
@@ -8110,37 +8127,39 @@ IonBuilder::pushDerivedTypedObject(bool*
     return true;
 }
 
 bool
 IonBuilder::getElemTryDense(bool* emitted, MDefinition* obj, MDefinition* index)
 {
     MOZ_ASSERT(*emitted == false);
 
-    if (!ElementAccessIsDenseNative(constraints(), obj, index)) {
-        trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
-        return true;
+    JSValueType unboxedType = UnboxedArrayElementType(constraints(), obj, index);
+    if (unboxedType == JSVAL_TYPE_MAGIC) {
+        if (!ElementAccessIsDenseNative(constraints(), obj, index)) {
+            trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
+            return true;
+        }
     }
 
     // Don't generate a fast path if there have been bounds check failures
     // and this access might be on a sparse property.
     if (ElementAccessHasExtraIndexedProperty(constraints(), obj) && failedBoundsCheck_) {
         trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps);
         return true;
     }
 
     // Don't generate a fast path if this pc has seen negative indexes accessed,
     // which will not appear to be extra indexed properties.
     if (inspector->hasSeenNegativeIndexGetElement(pc)) {
         trackOptimizationOutcome(TrackedOutcome::ArraySeenNegativeIndex);
         return true;
     }
 
-    // Emit dense getelem variant.
-    if (!jsop_getelem_dense(obj, index))
+    if (!jsop_getelem_dense(obj, index, unboxedType))
         return false;
 
     trackOptimizationSuccess();
     *emitted = true;
     return true;
 }
 
 JSObject*
@@ -8442,17 +8461,17 @@ IonBuilder::getElemTryCache(bool* emitte
         return false;
 
     trackOptimizationSuccess();
     *emitted = true;
     return true;
 }
 
 bool
-IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index)
+IonBuilder::jsop_getelem_dense(MDefinition* obj, MDefinition* index, JSValueType unboxedType)
 {
     TemporaryTypeSet* types = bytecodeTypes(pc);
 
     MOZ_ASSERT(index->type() == MIRType_Int32 || index->type() == MIRType_Double);
     if (JSOp(*pc) == JSOP_CALLELEM) {
         // Indexed call on an element of an array. Populate the observed types
         // with any objects that could be in the array, to avoid extraneous
         // type barriers.
@@ -8466,38 +8485,43 @@ IonBuilder::jsop_getelem_dense(MDefiniti
     // Reads which are on holes in the object do not have to bail out if
     // undefined values have been observed at this access site and the access
     // cannot hit another indexed property on the object or its prototypes.
     bool readOutOfBounds =
         types->hasType(TypeSet::UndefinedType()) &&
         !ElementAccessHasExtraIndexedProperty(constraints(), obj);
 
     MIRType knownType = MIRType_Value;
-    if (barrier == BarrierKind::NoBarrier)
+    if (unboxedType == JSVAL_TYPE_MAGIC && barrier == BarrierKind::NoBarrier)
         knownType = GetElemKnownType(needsHoleCheck, types);
 
     // Ensure index is an integer.
     MInstruction* idInt32 = MToInt32::New(alloc(), index);
     current->add(idInt32);
     index = idInt32;
 
     // Get the elements vector.
-    MInstruction* elements = MElements::New(alloc(), obj);
+    MInstruction* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC);
     current->add(elements);
 
     // Note: to help GVN, use the original MElements instruction and not
     // MConvertElementsToDoubles as operand. This is fine because converting
     // elements to double does not change the initialized length.
-    MInitializedLength* initLength = MInitializedLength::New(alloc(), elements);
+    MInstruction* initLength;
+    if (unboxedType != JSVAL_TYPE_MAGIC)
+        initLength = MUnboxedArrayInitializedLength::New(alloc(), obj);
+    else
+        initLength = MInitializedLength::New(alloc(), elements);
     current->add(initLength);
 
     // If we can load the element as a definite double, make sure to check that
     // the array has been converted to homogenous doubles first.
     TemporaryTypeSet* objTypes = obj->resultTypeSet();
     bool loadDouble =
+        unboxedType == JSVAL_TYPE_MAGIC &&
         barrier == BarrierKind::NoBarrier &&
         loopDepth_ &&
         !readOutOfBounds &&
         !needsHoleCheck &&
         knownType == MIRType_Double &&
         objTypes &&
         objTypes->convertDoubleElements(constraints()) == TemporaryTypeSet::AlwaysConvertToDoubles;
     if (loadDouble)
@@ -8507,23 +8531,28 @@ IonBuilder::jsop_getelem_dense(MDefiniti
 
     if (!readOutOfBounds) {
         // This load should not return undefined, so likely we're reading
         // in-bounds elements, and the array is packed or its holes are not
         // read. This is the best case: we can separate the bounds check for
         // hoisting.
         index = addBoundsCheck(index, initLength);
 
-        load = MLoadElement::New(alloc(), elements, index, needsHoleCheck, loadDouble);
-        current->add(load);
+        if (unboxedType != JSVAL_TYPE_MAGIC) {
+            load = loadUnboxedValue(elements, 0, index, unboxedType, barrier, types);
+        } else {
+            load = MLoadElement::New(alloc(), elements, index, needsHoleCheck, loadDouble);
+            current->add(load);
+        }
     } else {
         // This load may return undefined, so assume that we *can* read holes,
         // or that we can read out-of-bounds accesses. In this case, the bounds
         // check is part of the opcode.
-        load = MLoadElementHole::New(alloc(), elements, index, initLength, needsHoleCheck);
+        load = MLoadElementHole::New(alloc(), elements, index, initLength,
+                                     unboxedType, needsHoleCheck);
         current->add(load);
 
         // If maybeUndefined was true, the typeset must have undefined, and
         // then either additional types or a barrier. This means we should
         // never have a typed version of LoadElementHole.
         MOZ_ASSERT(knownType == MIRType_Value);
     }
 
@@ -8971,19 +9000,22 @@ IonBuilder::setElemTryTypedArray(bool* e
 }
 
 bool
 IonBuilder::setElemTryDense(bool* emitted, MDefinition* object,
                             MDefinition* index, MDefinition* value)
 {
     MOZ_ASSERT(*emitted == false);
 
-    if (!ElementAccessIsDenseNative(constraints(), object, index)) {
-        trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
-        return true;
+    JSValueType unboxedType = UnboxedArrayElementType(constraints(), object, index);
+    if (unboxedType == JSVAL_TYPE_MAGIC) {
+        if (!ElementAccessIsDenseNative(constraints(), object, index)) {
+            trackOptimizationOutcome(TrackedOutcome::AccessNotDense);
+            return true;
+        }
     }
 
     if (PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current,
                                       &object, nullptr, &value, /* canModify = */ true))
     {
         trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
         return true;
     }
@@ -9007,17 +9039,17 @@ IonBuilder::setElemTryDense(bool* emitte
     // Don't generate a fast path if there have been bounds check failures
     // and this access might be on a sparse property.
     if (ElementAccessHasExtraIndexedProperty(constraints(), object) && failedBoundsCheck_) {
         trackOptimizationOutcome(TrackedOutcome::ProtoIndexedProps);
         return true;
     }
 
     // Emit dense setelem variant.
-    if (!jsop_setelem_dense(conversion, SetElem_Normal, object, index, value))
+    if (!jsop_setelem_dense(conversion, SetElem_Normal, object, index, value, unboxedType))
         return false;
 
     trackOptimizationSuccess();
     *emitted = true;
     return true;
 }
 
 bool
@@ -9092,19 +9124,22 @@ IonBuilder::setElemTryCache(bool* emitte
     trackOptimizationSuccess();
     *emitted = true;
     return true;
 }
 
 bool
 IonBuilder::jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion,
                                SetElemSafety safety,
-                               MDefinition* obj, MDefinition* id, MDefinition* value)
-{
-    MIRType elementType = DenseNativeElementType(constraints(), obj);
+                               MDefinition* obj, MDefinition* id, MDefinition* value,
+                               JSValueType unboxedType)
+{
+    MIRType elementType = MIRType_None;
+    if (unboxedType == JSVAL_TYPE_MAGIC)
+        elementType = DenseNativeElementType(constraints(), obj);
     bool packed = ElementAccessIsPacked(constraints(), obj);
 
     // Writes which are on holes in the object do not have to bail out if they
     // cannot hit another indexed property on the object or its prototypes.
     bool writeOutOfBounds = !ElementAccessHasExtraIndexedProperty(constraints(), obj);
 
     if (NeedsPostBarrier(info(), value))
         current->add(MPostWriteBarrier::New(alloc(), obj, value));
@@ -9113,17 +9148,17 @@ IonBuilder::jsop_setelem_dense(Temporary
     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);
+    MElements* elements = MElements::New(alloc(), obj, unboxedType != JSVAL_TYPE_MAGIC);
     current->add(elements);
 
     // Ensure the value is a double, if double conversion might be needed.
     MDefinition* newValue = value;
     switch (conversion) {
       case TemporaryTypeSet::AlwaysConvertToDoubles:
       case TemporaryTypeSet::MaybeConvertToDoubles: {
         MInstruction* valueDouble = MToDouble::New(alloc(), value);
@@ -9151,58 +9186,68 @@ IonBuilder::jsop_setelem_dense(Temporary
     if (safety == SetElem_Normal) {
         SetElemICInspector icInspect(inspector->setElemICInspector(pc));
         writeHole = icInspect.sawOOBDenseWrite();
     }
 
     // Use MStoreElementHole if this SETELEM has written to out-of-bounds
     // indexes in the past. Otherwise, use MStoreElement so that we can hoist
     // the initialized length and bounds check.
-    MStoreElementCommon* store;
+    MInstruction* store;
+    MStoreElementCommon *common = nullptr;
     if (writeHole && writeOutOfBounds) {
         MOZ_ASSERT(safety == SetElem_Normal);
 
-        MStoreElementHole* ins = MStoreElementHole::New(alloc(), obj, elements, id, newValue);
+        MStoreElementHole* ins = MStoreElementHole::New(alloc(), obj, elements, id, newValue, unboxedType);
         store = ins;
+        common = ins;
 
         current->add(ins);
         current->push(value);
-
-        if (!resumeAfter(ins))
-            return false;
     } else {
-        MInitializedLength* initLength = MInitializedLength::New(alloc(), elements);
+        MInstruction* initLength;
+        if (unboxedType != JSVAL_TYPE_MAGIC)
+            initLength = MUnboxedArrayInitializedLength::New(alloc(), obj);
+        else
+            initLength = MInitializedLength::New(alloc(), elements);
         current->add(initLength);
 
         bool needsHoleCheck;
         if (safety == SetElem_Normal) {
             id = addBoundsCheck(id, initLength);
             needsHoleCheck = !packed && !writeOutOfBounds;
         } else {
             needsHoleCheck = false;
         }
 
-        MStoreElement* ins = MStoreElement::New(alloc(), elements, id, newValue, needsHoleCheck);
-        store = ins;
-
-        current->add(ins);
+        if (unboxedType != JSVAL_TYPE_MAGIC) {
+            store = storeUnboxedValue(obj, elements, 0, id, unboxedType, newValue);
+        } else {
+            MStoreElement* ins = MStoreElement::New(alloc(), elements, id, newValue, needsHoleCheck);
+            store = ins;
+            common = ins;
+
+            current->add(store);
+        }
 
         if (safety == SetElem_Normal)
             current->push(value);
-
-        if (!resumeAfter(ins))
-            return false;
-    }
-
-    // Determine whether a write barrier is required.
-    if (obj->resultTypeSet()->propertyNeedsBarrier(constraints(), JSID_VOID))
-        store->setNeedsBarrier();
-
-    if (elementType != MIRType_None && packed)
-        store->setElementType(elementType);
+    }
+
+    if (!resumeAfter(store))
+        return false;
+
+    if (common) {
+        // Determine whether a write barrier is required.
+        if (obj->resultTypeSet()->propertyNeedsBarrier(constraints(), JSID_VOID))
+            common->setNeedsBarrier();
+
+        if (elementType != MIRType_None && packed)
+            common->setElementType(elementType);
+    }
 
     return true;
 }
 
 
 bool
 IonBuilder::jsop_setelem_typed(Scalar::Type arrayType, SetElemSafety safety,
                                MDefinition* obj, MDefinition* id, MDefinition* value)
@@ -9318,16 +9363,26 @@ IonBuilder::jsop_length_fastPath()
 
             // Read length.
             MArrayLength* length = MArrayLength::New(alloc(), elements);
             current->add(length);
             current->push(length);
             return true;
         }
 
+        // Compute the length for unboxed array objects.
+        if (UnboxedArrayElementType(constraints(), obj, nullptr) != JSVAL_TYPE_MAGIC) {
+            current->pop();
+
+            MUnboxedArrayLength* length = MUnboxedArrayLength::New(alloc(), obj);
+            current->add(length);
+            current->push(length);
+            return true;
+        }
+
         // Compute the length for array typed objects.
         TypedObjectPrediction prediction = typedObjectPrediction(obj);
         if (!prediction.isUseless()) {
             TypeSet::ObjectKey* globalKey = TypeSet::ObjectKey::get(&script()->global());
             if (globalKey->hasFlags(constraints(), OBJECT_FLAG_TYPED_OBJECT_NEUTERED))
                 return false;
 
             MInstruction* length;
@@ -9385,17 +9440,17 @@ IonBuilder::jsop_rest()
     unsigned numFormals = info().nargs() - 1;
     unsigned numRest = numActuals > numFormals ? numActuals - numFormals : 0;
 
     MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     current->add(templateConst);
 
     MNewArray* array = MNewArray::New(alloc(), constraints(), numRest, templateConst,
                                       templateObject->group()->initialHeap(constraints()),
-                                      NewArray_FullyAllocating);
+                                      NewArray_FullyAllocating, pc);
     current->add(array);
 
     if (numRest == 0) {
         // No more updating to do. (Note that in this one case the length from
         // the template object is already correct.)
         current->push(array);
         return true;
     }
@@ -10511,53 +10566,59 @@ IonBuilder::getPropTryDefiniteSlot(bool*
 MInstruction*
 IonBuilder::loadUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
                                 BarrierKind barrier, TemporaryTypeSet* types)
 {
     size_t scaledOffsetConstant = offset / UnboxedTypeSize(unboxedType);
     MInstruction* scaledOffset = MConstant::New(alloc(), Int32Value(scaledOffsetConstant));
     current->add(scaledOffset);
 
+    return loadUnboxedValue(obj, UnboxedPlainObject::offsetOfData(),
+                            scaledOffset, unboxedType, barrier, types);
+}
+
+MInstruction*
+IonBuilder::loadUnboxedValue(MDefinition* elements, size_t elementsOffset,
+                             MDefinition* scaledOffset, JSValueType unboxedType,
+                             BarrierKind barrier, TemporaryTypeSet* types)
+{
+
     MInstruction* load;
     switch (unboxedType) {
       case JSVAL_TYPE_BOOLEAN:
-        load = MLoadUnboxedScalar::New(alloc(), obj, scaledOffset, Scalar::Uint8,
-                                       DoesNotRequireMemoryBarrier,
-                                       UnboxedPlainObject::offsetOfData());
+        load = MLoadUnboxedScalar::New(alloc(), elements, scaledOffset, Scalar::Uint8,
+                                       DoesNotRequireMemoryBarrier, elementsOffset);
         load->setResultType(MIRType_Boolean);
         break;
 
       case JSVAL_TYPE_INT32:
-        load = MLoadUnboxedScalar::New(alloc(), obj, scaledOffset, Scalar::Int32,
-                                       DoesNotRequireMemoryBarrier,
-                                       UnboxedPlainObject::offsetOfData());
+        load = MLoadUnboxedScalar::New(alloc(), elements, scaledOffset, Scalar::Int32,
+                                       DoesNotRequireMemoryBarrier, elementsOffset);
         load->setResultType(MIRType_Int32);
         break;
 
       case JSVAL_TYPE_DOUBLE:
-        load = MLoadUnboxedScalar::New(alloc(), obj, scaledOffset, Scalar::Float64,
-                                       DoesNotRequireMemoryBarrier,
-                                       UnboxedPlainObject::offsetOfData(),
+        load = MLoadUnboxedScalar::New(alloc(), elements, scaledOffset, Scalar::Float64,
+                                       DoesNotRequireMemoryBarrier, elementsOffset,
                                        /* canonicalizeDoubles = */ false);
         load->setResultType(MIRType_Double);
         break;
 
       case JSVAL_TYPE_STRING:
-        load = MLoadUnboxedString::New(alloc(), obj, scaledOffset,
-                                       UnboxedPlainObject::offsetOfData());
+        load = MLoadUnboxedString::New(alloc(), elements, scaledOffset, elementsOffset);
         break;
 
       case JSVAL_TYPE_OBJECT: {
         MLoadUnboxedObjectOrNull::NullBehavior nullBehavior;
         if (types->hasType(TypeSet::NullType()) || barrier != BarrierKind::NoBarrier)
             nullBehavior = MLoadUnboxedObjectOrNull::HandleNull;
         else
             nullBehavior = MLoadUnboxedObjectOrNull::NullNotPossible;
-        load = MLoadUnboxedObjectOrNull::New(alloc(), obj, scaledOffset, nullBehavior,
-                                             UnboxedPlainObject::offsetOfData());
+        load = MLoadUnboxedObjectOrNull::New(alloc(), elements, scaledOffset, nullBehavior,
+                                             elementsOffset);
         break;
       }
 
       default:
         MOZ_CRASH();
     }
 
     current->add(load);
@@ -11430,44 +11491,49 @@ IonBuilder::setPropTryDefiniteSlot(bool*
 MInstruction*
 IonBuilder::storeUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
                                  MDefinition* value)
 {
     size_t scaledOffsetConstant = offset / UnboxedTypeSize(unboxedType);
     MInstruction* scaledOffset = MConstant::New(alloc(), Int32Value(scaledOffsetConstant));
     current->add(scaledOffset);
 
+    return storeUnboxedValue(obj, obj, UnboxedPlainObject::offsetOfData(),
+                             scaledOffset, unboxedType, value);
+}
+
+MInstruction*
+IonBuilder::storeUnboxedValue(MDefinition* obj, MDefinition* elements, int32_t elementsOffset,
+                              MDefinition* scaledOffset, JSValueType unboxedType,
+                              MDefinition* value)
+{
     MInstruction* store;
     switch (unboxedType) {
       case JSVAL_TYPE_BOOLEAN:
-        store = MStoreUnboxedScalar::New(alloc(), obj, scaledOffset, value, Scalar::Uint8,
-                                         DoesNotRequireMemoryBarrier,
-                                         UnboxedPlainObject::offsetOfData());
+        store = MStoreUnboxedScalar::New(alloc(), elements, scaledOffset, value, Scalar::Uint8,
+                                         DoesNotRequireMemoryBarrier, elementsOffset);
         break;
 
       case JSVAL_TYPE_INT32:
-        store = MStoreUnboxedScalar::New(alloc(), obj, scaledOffset, value, Scalar::Int32,
-                                         DoesNotRequireMemoryBarrier,
-                                         UnboxedPlainObject::offsetOfData());
+        store = MStoreUnboxedScalar::New(alloc(), elements, scaledOffset, value, Scalar::Int32,
+                                         DoesNotRequireMemoryBarrier, elementsOffset);
         break;
 
       case JSVAL_TYPE_DOUBLE:
-        store = MStoreUnboxedScalar::New(alloc(), obj, scaledOffset, value, Scalar::Float64,
-                                         DoesNotRequireMemoryBarrier,
-                                         UnboxedPlainObject::offsetOfData());
+        store = MStoreUnboxedScalar::New(alloc(), elements, scaledOffset, value, Scalar::Float64,
+                                         DoesNotRequireMemoryBarrier, elementsOffset);
         break;
 
       case JSVAL_TYPE_STRING:
-        store = MStoreUnboxedString::New(alloc(), obj, scaledOffset, value,
-                                         UnboxedPlainObject::offsetOfData());
+        store = MStoreUnboxedString::New(alloc(), elements, scaledOffset, value, elementsOffset);
         break;
 
       case JSVAL_TYPE_OBJECT:
-        store = MStoreUnboxedObjectOrNull::New(alloc(), obj, scaledOffset, value, obj,
-                                               UnboxedPlainObject::offsetOfData());
+        store = MStoreUnboxedObjectOrNull::New(alloc(), elements, scaledOffset, value, obj,
+                                               elementsOffset);
         break;
 
       default:
         MOZ_CRASH();
     }
 
     current->add(store);
     return store;
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -651,22 +651,23 @@ class IonBuilder
     bool getStaticName(JSObject* staticObject, PropertyName* name, bool* psucceeded,
                        MDefinition* lexicalCheck = nullptr);
     bool setStaticName(JSObject* staticObject, PropertyName* name);
     bool jsop_getgname(PropertyName* name);
     bool jsop_getname(PropertyName* name);
     bool jsop_intrinsic(PropertyName* name);
     bool jsop_bindname(PropertyName* name);
     bool jsop_getelem();
-    bool jsop_getelem_dense(MDefinition* obj, MDefinition* index);
+    bool jsop_getelem_dense(MDefinition* obj, MDefinition* index, JSValueType unboxedType);
     bool jsop_getelem_typed(MDefinition* obj, MDefinition* index, ScalarTypeDescr::Type arrayType);
     bool jsop_setelem();
     bool jsop_setelem_dense(TemporaryTypeSet::DoubleConversion conversion,
                             SetElemSafety safety,
-                            MDefinition* object, MDefinition* index, MDefinition* value);
+                            MDefinition* object, MDefinition* index, MDefinition* value,
+                            JSValueType unboxedType);
     bool jsop_setelem_typed(ScalarTypeDescr::Type arrayType,
                             SetElemSafety safety,
                             MDefinition* object, MDefinition* index, MDefinition* value);
     bool jsop_setelem_typed_object(ScalarTypeDescr::Type arrayType,
                                    SetElemSafety safety,
                                    MDefinition* object, MDefinition* index, MDefinition* value);
     bool jsop_length();
     bool jsop_length_fastPath();
@@ -945,18 +946,26 @@ class IonBuilder
     uint32_t getDefiniteSlot(TemporaryTypeSet* types, PropertyName* name, uint32_t* pnfixed,
                              BaselineInspector::ObjectGroupVector& convertUnboxedGroups);
     MDefinition* convertUnboxedObjects(MDefinition* obj,
                                        const BaselineInspector::ObjectGroupVector& list);
     uint32_t getUnboxedOffset(TemporaryTypeSet* types, PropertyName* name,
                               JSValueType* punboxedType);
     MInstruction* loadUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
                                       BarrierKind barrier, TemporaryTypeSet* types);
+    MInstruction* loadUnboxedValue(MDefinition* elements, size_t elementsOffset,
+                                   MDefinition* scaledOffset, JSValueType unboxedType,
+                                   BarrierKind barrier, TemporaryTypeSet* types);
     MInstruction* storeUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
                                        MDefinition* value);
+    MInstruction* storeUnboxedValue(MDefinition* obj,
+                                    MDefinition* elements, int32_t elementsOffset,
+                                    MDefinition* scaledOffset, JSValueType unboxedType,
+                                    MDefinition* value);
+    bool checkPreliminaryGroups(MDefinition *obj);
     bool freezePropTypeSets(TemporaryTypeSet* types,
                             JSObject* foundProto, PropertyName* name);
     bool canInlinePropertyOpShapes(const BaselineInspector::ReceiverVector& receivers);
 
     TemporaryTypeSet* bytecodeTypes(jsbytecode* pc);
 
     // Use one of the below methods for updating the current block, rather than
     // updating |current| directly. setCurrent() should only be used in cases
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -4079,16 +4079,20 @@ class LElements : public LInstructionHel
 
     explicit LElements(const LAllocation& object) {
         setOperand(0, object);
     }
 
     const LAllocation* object() {
         return getOperand(0);
     }
+
+    const MElements* mir() const {
+        return mir_->toElements();
+    }
 };
 
 // If necessary, convert any int32 elements in a vector into doubles.
 class LConvertElementsToDoubles : public LInstructionHelper<0, 1, 0>
 {
   public:
     LIR_HEADER(ConvertElementsToDoubles)
 
@@ -4176,16 +4180,58 @@ class LSetInitializedLength : public LIn
     const LAllocation* elements() {
         return getOperand(0);
     }
     const LAllocation* index() {
         return getOperand(1);
     }
 };
 
+class LUnboxedArrayLength : public LInstructionHelper<1, 1, 0>
+{
+  public:
+    LIR_HEADER(UnboxedArrayLength)
+
+    explicit LUnboxedArrayLength(const LAllocation& object) {
+        setOperand(0, object);
+    }
+
+    const LAllocation* object() {
+        return getOperand(0);
+    }
+};
+
+class LUnboxedArrayInitializedLength : public LInstructionHelper<1, 1, 0>
+{
+  public:
+    LIR_HEADER(UnboxedArrayInitializedLength)
+
+    explicit LUnboxedArrayInitializedLength(const LAllocation& object) {
+        setOperand(0, object);
+    }
+
+    const LAllocation* object() {
+        return getOperand(0);
+    }
+};
+
+class LIncrementUnboxedArrayInitializedLength : public LInstructionHelper<0, 1, 0>
+{
+  public:
+    LIR_HEADER(IncrementUnboxedArrayInitializedLength)
+
+    explicit LIncrementUnboxedArrayInitializedLength(const LAllocation& object) {
+        setOperand(0, object);
+    }
+
+    const LAllocation* object() {
+        return getOperand(0);
+    }
+};
+
 // Load the length from an elements header.
 class LArrayLength : public LInstructionHelper<1, 1, 0>
 {
   public:
     LIR_HEADER(ArrayLength)
 
     explicit LArrayLength(const LAllocation& elements) {
         setOperand(0, elements);
@@ -4642,26 +4688,27 @@ class LStoreElementT : public LInstructi
         return getOperand(1);
     }
     const LAllocation* value() {
         return getOperand(2);
     }
 };
 
 // Like LStoreElementV, but supports indexes >= initialized length.
-class LStoreElementHoleV : public LInstructionHelper<0, 3 + BOX_PIECES, 0>
+class LStoreElementHoleV : public LInstructionHelper<0, 3 + BOX_PIECES, 1>
 {
   public:
     LIR_HEADER(StoreElementHoleV)
 
     LStoreElementHoleV(const LAllocation& object, const LAllocation& elements,
-                       const LAllocation& index) {
+                       const LAllocation& index, const LDefinition& temp) {
         setOperand(0, object);
         setOperand(1, elements);
         setOperand(2, index);
+        setTemp(0, temp);
     }
 
     static const size_t Value = 3;
 
     const MStoreElementHole* mir() const {
         return mir_->toStoreElementHole();
     }
     const LAllocation* object() {
@@ -4671,27 +4718,29 @@ class LStoreElementHoleV : public LInstr
         return getOperand(1);
     }
     const LAllocation* index() {
         return getOperand(2);
     }
 };
 
 // Like LStoreElementT, but supports indexes >= initialized length.
-class LStoreElementHoleT : public LInstructionHelper<0, 4, 0>
+class LStoreElementHoleT : public LInstructionHelper<0, 4, 1>
 {
   public:
     LIR_HEADER(StoreElementHoleT)
 
     LStoreElementHoleT(const LAllocation& object, const LAllocation& elements,
-                       const LAllocation& index, const LAllocation& value) {
+                       const LAllocation& index, const LAllocation& value,
+                       const LDefinition& temp) {
         setOperand(0, object);
         setOperand(1, elements);
         setOperand(2, index);
         setOperand(3, value);
+        setTemp(0, temp);
     }
 
     const MStoreElementHole* mir() const {
         return mir_->toStoreElementHole();
     }
     const LAllocation* object() {
         return getOperand(0);
     }
--- a/js/src/jit/LOpcodes.h
+++ b/js/src/jit/LOpcodes.h
@@ -209,16 +209,19 @@
     _(LoadUnboxedExpando)           \
     _(TypeBarrierV)                 \
     _(TypeBarrierO)                 \
     _(MonitorTypes)                 \
     _(PostWriteBarrierO)            \
     _(PostWriteBarrierV)            \
     _(InitializedLength)            \
     _(SetInitializedLength)         \
+    _(UnboxedArrayLength)           \
+    _(UnboxedArrayInitializedLength) \
+    _(IncrementUnboxedArrayInitializedLength) \
     _(BoundsCheck)                  \
     _(BoundsCheckRange)             \
     _(BoundsCheckLower)             \
     _(LoadElementV)                 \
     _(LoadElementT)                 \
     _(LoadElementHole)              \
     _(LoadUnboxedScalar)            \
     _(LoadUnboxedPointerV)          \
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2491,16 +2491,34 @@ LIRGenerator::visitSetInitializedLength(
     MOZ_ASSERT(ins->index()->type() == MIRType_Int32);
 
     MOZ_ASSERT(ins->index()->isConstant());
     add(new(alloc()) LSetInitializedLength(useRegister(ins->elements()),
                                            useRegisterOrConstant(ins->index())), ins);
 }
 
 void
+LIRGenerator::visitUnboxedArrayLength(MUnboxedArrayLength* ins)
+{
+    define(new(alloc()) LUnboxedArrayLength(useRegisterAtStart(ins->object())), ins);
+}
+
+void
+LIRGenerator::visitUnboxedArrayInitializedLength(MUnboxedArrayInitializedLength* ins)
+{
+    define(new(alloc()) LUnboxedArrayInitializedLength(useRegisterAtStart(ins->object())), ins);
+}
+
+void
+LIRGenerator::visitIncrementUnboxedArrayInitializedLength(MIncrementUnboxedArrayInitializedLength* ins)
+{
+    add(new(alloc()) LIncrementUnboxedArrayInitializedLength(useRegister(ins->object())), ins);
+}
+
+void
 LIRGenerator::visitNot(MNot* ins)
 {
     MDefinition* op = ins->input();
 
     // String is converted to length of string in the type analysis phase (see
     // TestPolicy).
     MOZ_ASSERT(op->type() != MIRType_String);
 
@@ -2732,27 +2750,32 @@ LIRGenerator::visitStoreElementHole(MSto
 {
     MOZ_ASSERT(ins->elements()->type() == MIRType_Elements);
     MOZ_ASSERT(ins->index()->type() == MIRType_Int32);
 
     const LUse object = useRegister(ins->object());
     const LUse elements = useRegister(ins->elements());
     const LAllocation index = useRegisterOrConstant(ins->index());
 
+    // Use a temp register when adding new elements to unboxed arrays.
+    LDefinition tempDef = LDefinition::BogusTemp();
+    if (ins->unboxedType() != JSVAL_TYPE_MAGIC)
+        tempDef = temp();
+
     LInstruction* lir;
     switch (ins->value()->type()) {
       case MIRType_Value:
-        lir = new(alloc()) LStoreElementHoleV(object, elements, index);
+        lir = new(alloc()) LStoreElementHoleV(object, elements, index, tempDef);
         useBox(lir, LStoreElementHoleV::Value, ins->value());
         break;
 
       default:
       {
         const LAllocation value = useRegisterOrNonDoubleConstant(ins->value());
-        lir = new(alloc()) LStoreElementHoleT(object, elements, index, value);
+        lir = new(alloc()) LStoreElementHoleT(object, elements, index, value, tempDef);
         break;
       }
     }
 
     add(lir, ins);
     assignSafepoint(lir, ins);
 }
 
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -182,16 +182,19 @@ class LIRGenerator : public LIRGenerator
     void visitTypedArrayLength(MTypedArrayLength* ins);
     void visitTypedArrayElements(MTypedArrayElements* ins);
     void visitSetDisjointTypedElements(MSetDisjointTypedElements* ins);
     void visitTypedObjectElements(MTypedObjectElements* ins);
     void visitSetTypedObjectOffset(MSetTypedObjectOffset* ins);
     void visitTypedObjectDescr(MTypedObjectDescr* ins);
     void visitInitializedLength(MInitializedLength* ins);
     void visitSetInitializedLength(MSetInitializedLength* ins);
+    void visitUnboxedArrayLength(MUnboxedArrayLength* ins);
+    void visitUnboxedArrayInitializedLength(MUnboxedArrayInitializedLength* ins);
+    void visitIncrementUnboxedArrayInitializedLength(MIncrementUnboxedArrayInitializedLength* ins);
     void visitNot(MNot* ins);
     void visitBoundsCheck(MBoundsCheck* ins);
     void visitBoundsCheckLower(MBoundsCheckLower* ins);
     void visitLoadElement(MLoadElement* ins);
     void visitLoadElementHole(MLoadElementHole* ins);
     void visitLoadUnboxedObjectOrNull(MLoadUnboxedObjectOrNull* ins);
     void visitLoadUnboxedString(MLoadUnboxedString* ins);
     void visitStoreElement(MStoreElement* ins);
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -592,17 +592,17 @@ IonBuilder::inlineArray(CallInfo& callIn
 
     callInfo.setImplicitlyUsedUnchecked();
 
     MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateArray);
     current->add(templateConst);
 
     MNewArray* ins = MNewArray::New(alloc(), constraints(), initLength, templateConst,
                                     templateArray->group()->initialHeap(constraints()),
-                                    allocating);
+                                    allocating, pc);
     current->add(ins);
     current->push(ins);
 
     if (callInfo.argc() >= 2) {
         // Get the elements vector.
         MElements* elements = MElements::New(alloc(), ins);
         current->add(elements);
 
@@ -1585,17 +1585,17 @@ IonBuilder::inlineConstantStringSplit(Ca
     if (conversion == TemporaryTypeSet::AlwaysConvertToDoubles)
         return InliningStatus_NotInlined;
 
     MConstant* templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject);
     current->add(templateConst);
 
     MNewArray* ins = MNewArray::New(alloc(), constraints(), initLength, templateConst,
                                     templateObject->group()->initialHeap(constraints()),
-                                    NewArray_FullyAllocating);
+                                    NewArray_FullyAllocating, pc);
 
     current->add(ins);
     current->push(ins);
 
     if (!initLength) {
         if (!resumeAfter(ins))
             return InliningStatus_Error;
         return InliningStatus_Inlined;
@@ -2130,17 +2130,17 @@ IonBuilder::inlineUnsafeSetDenseArrayEle
     // value is reflected in the JSID_VOID property of the array.
 
     MDefinition* obj = callInfo.getArg(base + 0);
     MDefinition* id = callInfo.getArg(base + 1);
     MDefinition* elem = callInfo.getArg(base + 2);
 
     TemporaryTypeSet::DoubleConversion conversion =
         obj->resultTypeSet()->convertDoubleElements(constraints());
-    if (!jsop_setelem_dense(conversion, SetElem_Unsafe, obj, id, elem))
+    if (!jsop_setelem_dense(conversion, SetElem_Unsafe, obj, id, elem, JSVAL_TYPE_MAGIC))
         return false;
     return true;
 }
 
 bool
 IonBuilder::inlineUnsafeSetTypedArrayElement(CallInfo& callInfo,
                                              uint32_t base,
                                              Scalar::Type arrayType)
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -662,29 +662,40 @@ MConstant::NewAsmJS(TempAllocator& alloc
 }
 
 MConstant*
 MConstant::NewConstraintlessObject(TempAllocator& alloc, JSObject* v)
 {
     return new(alloc) MConstant(v);
 }
 
-TemporaryTypeSet*
-jit::MakeSingletonTypeSet(CompilerConstraintList* constraints, JSObject* obj)
+static TemporaryTypeSet*
+MakeSingletonTypeSetFromKey(CompilerConstraintList* constraints, TypeSet::ObjectKey* key)
 {
     // Invalidate when this object's ObjectGroup gets unknown properties. This
     // happens for instance when we mutate an object's __proto__, in this case
     // we want to invalidate and mark this TypeSet as containing AnyObject
     // (because mutating __proto__ will change an object's ObjectGroup).
     MOZ_ASSERT(constraints);
-    TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(obj);
     key->hasStableClassAndProto(constraints);
 
     LifoAlloc* alloc = GetJitContext()->temp->lifoAlloc();
-    return alloc->new_<TemporaryTypeSet>(alloc, TypeSet::ObjectType(obj));
+    return alloc->new_<TemporaryTypeSet>(alloc, TypeSet::ObjectType(key));
+}
+
+TemporaryTypeSet*
+jit::MakeSingletonTypeSet(CompilerConstraintList* constraints, JSObject* obj)
+{
+    return MakeSingletonTypeSetFromKey(constraints, TypeSet::ObjectKey::get(obj));
+}
+
+TemporaryTypeSet*
+jit::MakeSingletonTypeSet(CompilerConstraintList* constraints, ObjectGroup* obj)
+{
+    return MakeSingletonTypeSetFromKey(constraints, TypeSet::ObjectKey::get(obj));
 }
 
 static TemporaryTypeSet*
 MakeUnknownTypeSet()
 {
     LifoAlloc* alloc = GetJitContext()->temp->lifoAlloc();
     return alloc->new_<TemporaryTypeSet>(alloc, TypeSet::UnknownType());
 }
@@ -4013,46 +4024,55 @@ MArrayState::Copy(TempAllocator& alloc, 
     if (!res || !res->init(alloc, arr, len))
         return nullptr;
     for (size_t i = 0; i < res->numElements(); i++)
         res->initElement(i, state->getElement(i));
     return res;
 }
 
 MNewArray::MNewArray(CompilerConstraintList* constraints, uint32_t count, MConstant* templateConst,
-                     gc::InitialHeap initialHeap, AllocatingBehaviour allocating)
+                     gc::InitialHeap initialHeap, AllocatingBehaviour allocating, jsbytecode* pc)
   : MUnaryInstruction(templateConst),
     count_(count),
     initialHeap_(initialHeap),
     allocating_(allocating),
-    convertDoubleElements_(false)
-{
-    ArrayObject* obj = templateObject();
+    convertDoubleElements_(false),
+    pc_(pc)
+{
     setResultType(MIRType_Object);
-    if (!obj->isSingleton()) {
-        TemporaryTypeSet* types = MakeSingletonTypeSet(constraints, obj);
+    if (templateObject()) {
+        TemporaryTypeSet* types = MakeSingletonTypeSet(constraints, templateObject());
         setResultTypeSet(types);
         if (types->convertDoubleElements(constraints) == TemporaryTypeSet::AlwaysConvertToDoubles)
             convertDoubleElements_ = true;
     }
 }
 
 bool
 MNewArray::shouldUseVM() const
 {
+    if (!templateObject())
+        return true;
+
+    // Allocate space using the VMCall when mir hints it needs to get allocated
+    // immediately, but only when data doesn't fit the available array slots.
+    if (allocatingBehaviour() == NewArray_Unallocating)
+        return false;
+
+    if (templateObject()->is<UnboxedArrayObject>()) {
+        MOZ_ASSERT(templateObject()->as<UnboxedArrayObject>().capacity() >= count());
+        return !templateObject()->as<UnboxedArrayObject>().hasInlineElements();
+    }
+
     MOZ_ASSERT(count() < NativeObject::NELEMENTS_LIMIT);
 
     size_t arraySlots =
         gc::GetGCKindSlots(templateObject()->asTenured().getAllocKind()) - ObjectElements::VALUES_PER_HEADER;
 
-    // Allocate space using the VMCall when mir hints it needs to get allocated
-    // immediately, but only when data doesn't fit the available array slots.
-    bool allocating = allocatingBehaviour() != NewArray_Unallocating && count() > arraySlots;
-
-    return templateObject()->isSingleton() || allocating;
+    return count() > arraySlots;
 }
 
 bool
 MLoadFixedSlot::mightAlias(const MDefinition* store) const
 {
     if (store->isStoreFixedSlot() && store->toStoreFixedSlot()->slot() != slot())
         return false;
     return true;
@@ -4691,16 +4711,58 @@ jit::ElementAccessIsDenseNative(Compiler
     if (!types)
         return false;
 
     // Typed arrays are native classes but do not have dense elements.
     const Class* clasp = types->getKnownClass(constraints);
     return clasp && clasp->isNative() && !IsAnyTypedArrayClass(clasp);
 }
 
+JSValueType
+jit::UnboxedArrayElementType(CompilerConstraintList* constraints, MDefinition* obj,
+                             MDefinition* id)
+{
+    if (obj->mightBeType(MIRType_String))
+        return JSVAL_TYPE_MAGIC;
+
+    if (id && id->type() != MIRType_Int32 && id->type() != MIRType_Double)
+        return JSVAL_TYPE_MAGIC;
+
+    TemporaryTypeSet* types = obj->resultTypeSet();
+    if (!types || types->unknownObject())
+        return JSVAL_TYPE_MAGIC;
+
+    JSValueType elementType = JSVAL_TYPE_MAGIC;
+    for (unsigned i = 0; i < types->getObjectCount(); i++) {
+        TypeSet::ObjectKey* key = types->getObject(i);
+        if (!key)
+            continue;
+
+        if (key->unknownProperties() || !key->isGroup())
+            return JSVAL_TYPE_MAGIC;
+
+        if (key->clasp() != &UnboxedArrayObject::class_)
+            return JSVAL_TYPE_MAGIC;
+
+        const UnboxedLayout &layout = key->group()->unboxedLayout();
+
+        if (layout.nativeGroup())
+            return JSVAL_TYPE_MAGIC;
+
+        if (elementType == layout.elementType() || elementType == JSVAL_TYPE_MAGIC)
+            elementType = layout.elementType();
+        else
+            return JSVAL_TYPE_MAGIC;
+
+        key->watchStateChangeForUnboxedConvertedToNative(constraints);
+    }
+
+    return elementType;
+}
+
 bool
 jit::ElementAccessIsAnyTypedArray(CompilerConstraintList* constraints,
                                   MDefinition* obj, MDefinition* id,
                                   Scalar::Type* arrayType)
 {
     if (obj->mightBeType(MIRType_String))
         return false;
 
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -2848,16 +2848,19 @@ class MThrow
         return true;
     }
 };
 
 // Fabricate a type set containing only the type of the specified object.
 TemporaryTypeSet*
 MakeSingletonTypeSet(CompilerConstraintList* constraints, JSObject* obj);
 
+TemporaryTypeSet*
+MakeSingletonTypeSet(CompilerConstraintList* constraints, ObjectGroup* obj);
+
 bool
 MergeTypes(MIRType* ptype, TemporaryTypeSet** ptypeSet,
            MIRType newType, TemporaryTypeSet* newTypeSet);
 
 bool
 TypeSetIncludes(TypeSet* types, MIRType input, TypeSet* inputTypes);
 
 bool
@@ -2912,45 +2915,53 @@ class MNewArray
     // Heap where the array should be allocated.
     gc::InitialHeap initialHeap_;
     // Allocate space at initialization or not
     AllocatingBehaviour allocating_;
 
     // Whether values written to this array should be converted to double first.
     bool convertDoubleElements_;
 
+    jsbytecode* pc_;
+
     MNewArray(CompilerConstraintList* constraints, uint32_t count, MConstant* templateConst,
-              gc::InitialHeap initialHeap, AllocatingBehaviour allocating);
+              gc::InitialHeap initialHeap, AllocatingBehaviour allocating, jsbytecode* pc);
 
   public:
     INSTRUCTION_HEADER(NewArray)
 
     static MNewArray* New(TempAllocator& alloc, CompilerConstraintList* constraints,
                           uint32_t count, MConstant* templateConst,
-                          gc::InitialHeap initialHeap, AllocatingBehaviour allocating)
-    {
-        return new(alloc) MNewArray(constraints, count, templateConst, initialHeap, allocating);
+                          gc::InitialHeap initialHeap, AllocatingBehaviour allocating,
+                          jsbytecode* pc)
+    {
+        return new(alloc) MNewArray(constraints, count, templateConst,
+                                    initialHeap, allocating, pc);
     }
 
     uint32_t count() const {
         return count_;
     }
 
-    ArrayObject* templateObject() const {
-        return &getOperand(0)->toConstant()->value().toObject().as<ArrayObject>();
+    JSObject* templateObject() const {
+        return getOperand(0)->toConstant()->value().toObjectOrNull();
     }
 
     gc::InitialHeap initialHeap() const {
         return initialHeap_;
     }
 
     AllocatingBehaviour allocatingBehaviour() const {
         return allocating_;
     }
 
+    jsbytecode* pc() const {
+        return pc_;
+    }
+
     bool convertDoubleElements() const {
         return convertDoubleElements_;
     }
 
     // Returns true if the code generator should call through to the
     // VM rather than the fast path.
     bool shouldUseVM() const;
 
@@ -2963,17 +2974,17 @@ class MNewArray
     virtual AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
 
     bool writeRecoverData(CompactBufferWriter& writer) const override;
     bool canRecoverOnBailout() const override {
         // The template object can safely be used in the recover instruction
         // because it can never be mutated by any other function execution.
-        return true;
+        return templateObject() != nullptr;
     }
 };
 
 class MNewArrayCopyOnWrite : public MNullaryInstruction
 {
     AlwaysTenured<ArrayObject*> templateObject_;
     gc::InitialHeap initialHeap_;
 
@@ -7586,35 +7597,41 @@ class MSlots
     ALLOW_CLONE(MSlots)
 };
 
 // Returns obj->elements.
 class MElements
   : public MUnaryInstruction,
     public SingleObjectPolicy::Data
 {
-    explicit MElements(MDefinition* object)
-      : MUnaryInstruction(object)
+    bool unboxed_;
+
+    explicit MElements(MDefinition* object, bool unboxed)
+      : MUnaryInstruction(object), unboxed_(unboxed)
     {
         setResultType(MIRType_Elements);
         setMovable();
     }
 
   public:
     INSTRUCTION_HEADER(Elements)
 
-    static MElements* New(TempAllocator& alloc, MDefinition* object) {
-        return new(alloc) MElements(object);
+    static MElements* New(TempAllocator& alloc, MDefinition* object, bool unboxed = false) {
+        return new(alloc) MElements(object, unboxed);
     }
 
     MDefinition* object() const {
         return getOperand(0);
     }
-    bool congruentTo(const MDefinition* ins) const override {
-        return congruentIfOperandsEqual(ins);
+    bool unboxed() const {
+        return unboxed_;
+    }
+    bool congruentTo(const MDefinition* ins) const override {
+        return congruentIfOperandsEqual(ins) &&
+               ins->toElements()->unboxed() == unboxed();
     }
     AliasSet getAliasSet() const override {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
     bool mightAlias(const MDefinition* store) const override;
 
     ALLOW_CLONE(MElements)
 };
@@ -7835,16 +7852,106 @@ class MSetInitializedLength
     }
     AliasSet getAliasSet() const override {
         return AliasSet::Store(AliasSet::ObjectFields);
     }
 
     ALLOW_CLONE(MSetInitializedLength)
 };
 
+// Load the length from an unboxed array.
+class MUnboxedArrayLength
+  : public MUnaryInstruction,
+    public SingleObjectPolicy::Data
+{
+    explicit MUnboxedArrayLength(MDefinition* object)
+      : MUnaryInstruction(object)
+    {
+        setResultType(MIRType_Int32);
+        setMovable();
+    }
+
+  public:
+    INSTRUCTION_HEADER(UnboxedArrayLength)
+
+    static MUnboxedArrayLength* New(TempAllocator& alloc, MDefinition* object) {
+        return new(alloc) MUnboxedArrayLength(object);
+    }
+
+    MDefinition* object() const {
+        return getOperand(0);
+    }
+    bool congruentTo(const MDefinition* ins) const override {
+        return congruentIfOperandsEqual(ins);
+    }
+    AliasSet getAliasSet() const override {
+        return AliasSet::Load(AliasSet::ObjectFields);
+    }
+
+    ALLOW_CLONE(MUnboxedArrayLength)
+};
+
+// Load the initialized length from an unboxed array.
+class MUnboxedArrayInitializedLength
+  : public MUnaryInstruction,
+    public SingleObjectPolicy::Data
+{
+    explicit MUnboxedArrayInitializedLength(MDefinition* object)
+      : MUnaryInstruction(object)
+    {
+        setResultType(MIRType_Int32);
+        setMovable();
+    }
+
+  public:
+    INSTRUCTION_HEADER(UnboxedArrayInitializedLength)
+
+    static MUnboxedArrayInitializedLength* New(TempAllocator& alloc, MDefinition* object) {
+        return new(alloc) MUnboxedArrayInitializedLength(object);
+    }
+
+    MDefinition* object() const {
+        return getOperand(0);
+    }
+    bool congruentTo(const MDefinition* ins) const override {
+        return congruentIfOperandsEqual(ins);
+    }
+    AliasSet getAliasSet() const override {
+        return AliasSet::Load(AliasSet::ObjectFields);
+    }
+
+    ALLOW_CLONE(MUnboxedArrayInitializedLength)
+};
+
+// Increment the initialized length of an unboxed array object.
+class MIncrementUnboxedArrayInitializedLength
+  : public MUnaryInstruction,
+    public SingleObjectPolicy::Data
+{
+    explicit MIncrementUnboxedArrayInitializedLength(MDefinition* obj)
+      : MUnaryInstruction(obj)
+    {}
+
+  public:
+    INSTRUCTION_HEADER(IncrementUnboxedArrayInitializedLength)
+
+    static MIncrementUnboxedArrayInitializedLength* New(TempAllocator& alloc, MDefinition* obj) {
+        return new(alloc) MIncrementUnboxedArrayInitializedLength(obj);
+    }
+
+    MDefinition* object() const {
+        return getOperand(0);
+    }
+    AliasSet getAliasSet() const override {
+        return AliasSet::Store(AliasSet::ObjectFields);
+    }
+
+    ALLOW_CLONE(MIncrementUnboxedArrayInitializedLength)
+};
+
 // Load the array length from an elements header.
 class MArrayLength
   : public MUnaryInstruction,
     public NoTypePolicy::Data
 {
     explicit MArrayLength(MDefinition* elements)
       : MUnaryInstruction(elements)
     {
@@ -8354,28 +8461,33 @@ class MLoadElement
     AliasSet getAliasSet() const override {
         return AliasSet::Load(AliasSet::Element);
     }
     bool mightAlias(const MDefinition* store) const override;
 
     ALLOW_CLONE(MLoadElement)
 };
 
-// Load a value from a dense array's element vector. If the index is
-// out-of-bounds, or the indexed slot has a hole, undefined is returned
-// instead.
+// Load a value from the elements vector for a dense native or unboxed array.
+// If the index is out-of-bounds, or the indexed slot has a hole, undefined is
+// returned instead.
 class MLoadElementHole
   : public MTernaryInstruction,
     public SingleObjectPolicy::Data
 {
+    // Unboxed element type, JSVAL_TYPE_MAGIC for dense native elements.
+    JSValueType unboxedType_;
+
     bool needsNegativeIntCheck_;
     bool needsHoleCheck_;
 
-    MLoadElementHole(MDefinition* elements, MDefinition* index, MDefinition* initLength, bool needsHoleCheck)
+    MLoadElementHole(MDefinition* elements, MDefinition* index, MDefinition* initLength,
+                     JSValueType unboxedType, bool needsHoleCheck)
       : MTernaryInstruction(elements, index, initLength),
+        unboxedType_(unboxedType),
         needsNegativeIntCheck_(true),
         needsHoleCheck_(needsHoleCheck)
     {
         setResultType(MIRType_Value);
         setMovable();
 
         // Set the guard flag to make sure we bail when we see a negative
         // index. We can clear this flag (and needsNegativeIntCheck_) in
@@ -8386,47 +8498,56 @@ class MLoadElementHole
         MOZ_ASSERT(index->type() == MIRType_Int32);
         MOZ_ASSERT(initLength->type() == MIRType_Int32);
     }
 
   public:
     INSTRUCTION_HEADER(LoadElementHole)
 
     static MLoadElementHole* New(TempAllocator& alloc, MDefinition* elements, MDefinition* index,
-                                 MDefinition* initLength, bool needsHoleCheck) {
-        return new(alloc) MLoadElementHole(elements, index, initLength, needsHoleCheck);
+                                 MDefinition* initLength, JSValueType unboxedType,
+                                 bool needsHoleCheck) {
+        return new(alloc) MLoadElementHole(elements, index, initLength,
+                                           unboxedType, needsHoleCheck);
     }
 
     MDefinition* elements() const {
         return getOperand(0);
     }
     MDefinition* index() const {
         return getOperand(1);
     }
     MDefinition* initLength() const {
         return getOperand(2);
     }
+    JSValueType unboxedType() const {
+        return unboxedType_;
+    }
     bool needsNegativeIntCheck() const {
         return needsNegativeIntCheck_;
     }
     bool needsHoleCheck() const {
         return needsHoleCheck_;
     }
     bool congruentTo(const MDefinition* ins) const override {
         if (!ins->isLoadElementHole())
             return false;
         const MLoadElementHole* other = ins->toLoadElementHole();
+        if (unboxedType() != other->unboxedType())
+            return false;
         if (needsHoleCheck() != other->needsHoleCheck())
             return false;
         if (needsNegativeIntCheck() != other->needsNegativeIntCheck())
             return false;
         return congruentIfOperandsEqual(other);
     }
     AliasSet getAliasSet() const override {
-        return AliasSet::Load(AliasSet::Element);
+        return AliasSet::Load(unboxedType() == JSVAL_TYPE_MAGIC
+                              ? AliasSet::Element
+                              : AliasSet::UnboxedElement);
     }
     void collectRangeInfoPreTrunc() override;
 
     ALLOW_CLONE(MLoadElementHole)
 };
 
 class MLoadUnboxedObjectOrNull
   : public MBinaryInstruction,
@@ -8626,59 +8747,69 @@ class MStoreElement
     }
     bool fallible() const {
         return needsHoleCheck();
     }
 
     ALLOW_CLONE(MStoreElement)
 };
 
-// Like MStoreElement, but supports indexes >= initialized length. The downside
-// is that we cannot hoist the elements vector and bounds check, since this
-// instruction may update the (initialized) length and reallocate the elements
-// vector.
+// Like MStoreElement, but supports indexes >= initialized length, and can
+// handle unboxed arrays. The downside is that we cannot hoist the elements
+// vector and bounds check, since this instruction may update the (initialized)
+// length and reallocate the elements vector.
 class MStoreElementHole
   : public MAryInstruction<4>,
     public MStoreElementCommon,
     public MixPolicy<SingleObjectPolicy, NoFloatPolicy<3> >::Data
 {
+    JSValueType unboxedType_;
+
     MStoreElementHole(MDefinition* object, MDefinition* elements,
-                      MDefinition* index, MDefinition* value) {
+                      MDefinition* index, MDefinition* value, JSValueType unboxedType)
+      : unboxedType_(unboxedType)
+    {
         initOperand(0, object);
         initOperand(1, elements);
         initOperand(2, index);
         initOperand(3, value);
         MOZ_ASSERT(elements->type() == MIRType_Elements);
         MOZ_ASSERT(index->type() == MIRType_Int32);
     }
 
   public:
     INSTRUCTION_HEADER(StoreElementHole)
 
     static MStoreElementHole* New(TempAllocator& alloc, MDefinition* object, MDefinition* elements,
-                                  MDefinition* index, MDefinition* value) {
-        return new(alloc) MStoreElementHole(object, elements, index, value);
+                                  MDefinition* index, MDefinition* value, JSValueType unboxedType) {
+        return new(alloc) MStoreElementHole(object, elements, index, value, unboxedType);
     }
 
     MDefinition* object() const {
         return getOperand(0);
     }
     MDefinition* elements() const {
         return getOperand(1);
     }
     MDefinition* index() const {
         return getOperand(2);
     }
     MDefinition* value() const {
         return getOperand(3);
     }
+    JSValueType unboxedType() const {
+        return unboxedType_;
+    }
     AliasSet getAliasSet() const override {
         // StoreElementHole can update the initialized length, the array length
         // or reallocate obj->elements.
-        return AliasSet::Store(AliasSet::Element | AliasSet::ObjectFields);
+        return AliasSet::Store(AliasSet::ObjectFields |
+                               ((unboxedType() == JSVAL_TYPE_MAGIC)
+                                ? AliasSet::Element
+                                : AliasSet::UnboxedElement));
     }
 
     ALLOW_CLONE(MStoreElementHole)
 };
 
 // Store an unboxed object or null pointer to a v\ector.
 class MStoreUnboxedObjectOrNull
   : public MAryInstruction<4>,
@@ -13215,16 +13346,18 @@ MControlInstruction* MDefinition::toCont
     MOZ_ASSERT(isControlInstruction());
     return (MControlInstruction*)this;
 }
 
 // Helper functions used to decide how to build MIR.
 
 bool ElementAccessIsDenseNative(CompilerConstraintList* constraints,
                                 MDefinition* obj, MDefinition* id);
+JSValueType UnboxedArrayElementType(CompilerConstraintList* constraints, MDefinition* obj,
+                                    MDefinition* id);
 bool ElementAccessIsAnyTypedArray(CompilerConstraintList* constraints,
                                   MDefinition* obj, MDefinition* id,
                                   Scalar::Type* arrayType);
 bool ElementAccessIsPacked(CompilerConstraintList* constraints, MDefinition* obj);
 bool ElementAccessMightBeCopyOnWrite(CompilerConstraintList* constraints, MDefinition* obj);
 bool ElementAccessHasExtraIndexedProperty(CompilerConstraintList* constraints,
                                           MDefinition* obj);
 MIRType DenseNativeElementType(CompilerConstraintList* constraints, MDefinition* obj);
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -173,16 +173,19 @@ namespace jit {
     _(TypedArrayLength)                                                     \
     _(TypedArrayElements)                                                   \
     _(SetDisjointTypedElements)                                             \
     _(TypedObjectDescr)                                                     \
     _(TypedObjectElements)                                                  \
     _(SetTypedObjectOffset)                                                 \
     _(InitializedLength)                                                    \
     _(SetInitializedLength)                                                 \
+    _(UnboxedArrayLength)                                                   \
+    _(UnboxedArrayInitializedLength)                                        \
+    _(IncrementUnboxedArrayInitializedLength)                               \
     _(Not)                                                                  \
     _(BoundsCheck)                                                          \
     _(BoundsCheckLower)                                                     \
     _(InArray)                                                              \
     _(LoadElement)                                                          \
     _(LoadElementHole)                                                      \
     _(LoadUnboxedScalar)                                                    \
     _(LoadUnboxedObjectOrNull)                                              \
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -1011,16 +1011,41 @@ MacroAssembler::storeUnboxedProperty(T a
 template void
 MacroAssembler::storeUnboxedProperty(Address address, JSValueType type,
                                      ConstantOrRegister value, Label* failure);
 
 template void
 MacroAssembler::storeUnboxedProperty(BaseIndex address, JSValueType type,
                                      ConstantOrRegister value, Label* failure);
 
+void
+MacroAssembler::checkUnboxedArrayCapacity(Register obj, const Int32Key& index, Register temp,
+                                          Label* failure)
+{
+    Address initLengthAddr(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength());
+    Address lengthAddr(obj, UnboxedArrayObject::offsetOfLength());
+
+    Label capacityIsIndex, done;
+    load32(initLengthAddr, temp);
+    branchTest32(Assembler::NonZero, temp, Imm32(UnboxedArrayObject::CapacityMask), &capacityIsIndex);
+    branchKey(Assembler::BelowOrEqual, lengthAddr, index, failure);
+    jump(&done);
+    bind(&capacityIsIndex);
+
+    // Do a partial shift so that we can get an absolute offset from the base
+    // of CapacityArray to use.
+    JS_STATIC_ASSERT(sizeof(UnboxedArrayObject::CapacityArray[0]) == 4);
+    rshiftPtr(Imm32(UnboxedArrayObject::CapacityShift - 2), temp);
+    and32(Imm32(~0x3), temp);
+
+    addPtr(ImmPtr(&UnboxedArrayObject::CapacityArray), temp);
+    branchKey(Assembler::BelowOrEqual, Address(temp, 0), index, failure);
+    bind(&done);
+}
+
 // Inlined version of gc::CheckAllocatorState that checks the bare essentials
 // and bails for anything that cannot be handled with our jit allocators.
 void
 MacroAssembler::checkAllocatorState(Label* fail)
 {
     // Don't execute the inline path if we are tracing allocations.
     if (js::gc::TraceEnabled())
         jump(fail);
@@ -1428,16 +1453,26 @@ MacroAssembler::initGCThing(Register obj
                      Address(obj, InlineTypedObject::offsetOfDataStart() + offset));
             nbytes = (nbytes < sizeof(uintptr_t)) ? 0 : nbytes - sizeof(uintptr_t);
             offset += sizeof(uintptr_t);
         }
     } else if (templateObj->is<UnboxedPlainObject>()) {
         storePtr(ImmWord(0), Address(obj, UnboxedPlainObject::offsetOfExpando()));
         if (initContents)
             initUnboxedObjectContents(obj, &templateObj->as<UnboxedPlainObject>());
+    } else if (templateObj->is<UnboxedArrayObject>()) {
+        MOZ_ASSERT(templateObj->as<UnboxedArrayObject>().hasInlineElements());
+        int elementsOffset = UnboxedArrayObject::offsetOfInlineElements();
+        computeEffectiveAddress(Address(obj, elementsOffset), temp);
+        storePtr(temp, Address(obj, UnboxedArrayObject::offsetOfElements()));
+        store32(Imm32(templateObj->as<UnboxedArrayObject>().length()),
+                Address(obj, UnboxedArrayObject::offsetOfLength()));
+        uint32_t capacityIndex = templateObj->as<UnboxedArrayObject>().capacityIndex();
+        store32(Imm32(capacityIndex << UnboxedArrayObject::CapacityShift),
+                Address(obj, UnboxedArrayObject::offsetOfCapacityIndexAndInitializedLength()));
     } else {
         MOZ_CRASH("Unknown object");
     }
 
 #ifdef JS_GC_TRACE
     RegisterSet regs = RegisterSet::Volatile();
     PushRegsInMask(regs);
     regs.takeUnchecked(obj);
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -713,27 +713,30 @@ class MacroAssembler : public MacroAssem
     template<typename S, typename T>
     void atomicBinopToTypedIntArray(AtomicOp op, Scalar::Type arrayType, const S& value, const T& mem);
 
     void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const BaseIndex& dest,
                                 unsigned numElems = 0);
     void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const Address& dest,
                                 unsigned numElems = 0);
 
-    // Load a property from an UnboxedPlainObject.
+    // Load a property from an UnboxedPlainObject or UnboxedArrayObject.
     template <typename T>
     void loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output);
 
     // Store a property to an UnboxedPlainObject, without triggering barriers.
     // If failure is null, the value definitely has a type suitable for storing
     // in the property.
     template <typename T>
     void storeUnboxedProperty(T address, JSValueType type,
                               ConstantOrRegister value, Label* failure);
 
+    void checkUnboxedArrayCapacity(Register obj, const Int32Key& index, Register temp,
+                                   Label* failure);
+
     Register extractString(const Address& address, Register scratch) {
         return extractObject(address, scratch);
     }
     Register extractString(const ValueOperand& value, Register scratch) {
         return extractObject(value, scratch);
     }
 
     using MacroAssemblerSpecific::extractTag;
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -1209,20 +1209,16 @@ RNewArray::RNewArray(CompactBufferReader
 
 bool
 RNewArray::recover(JSContext* cx, SnapshotIterator& iter) const
 {
     RootedObject templateObject(cx, &iter.read().toObject());
     RootedValue result(cx);
     RootedObjectGroup group(cx);
 
-    // See CodeGenerator::visitNewArrayCallVM
-    if (!templateObject->isSingleton())
-        group = templateObject->group();
-
     JSObject* resultObject = NewDenseArray(cx, count_, group, allocatingBehaviour_);
     if (!resultObject)
         return false;
 
     result.setObject(*resultObject);
     iter.storeInstructionResult(result);
     return true;
 }
--- a/js/src/jit/ScalarReplacement.cpp
+++ b/js/src/jit/ScalarReplacement.cpp
@@ -607,16 +607,20 @@ IsArrayEscaped(MInstruction* ins)
 
     // The array is probably too large to be represented efficiently with
     // MArrayState, and we do not want to make huge allocations during bailouts.
     if (ins->toNewArray()->allocatingBehaviour() == NewArray_Unallocating) {
         JitSpewDef(JitSpew_Escape, "Array is not allocated\n", ins);
         return true;
     }
 
+    JSObject* obj = ins->toNewArray()->templateObject();
+    if (!obj || obj->is<UnboxedArrayObject>())
+        return true;
+
     if (count >= 16) {
         JitSpewDef(JitSpew_Escape, "Array has too many elements\n", ins);
         return true;
     }
 
     // Check if the object is escaped. If the object is not the first argument
     // of either a known Store / Load, then we consider it as escaped. This is a
     // cheap and conservative escape analysis.
--- a/js/src/jit/VMFunctions.cpp
+++ b/js/src/jit/VMFunctions.cpp
@@ -1104,47 +1104,56 @@ ForcedRecompile(JSContext* cx)
 
 bool
 Recompile(JSContext* cx)
 {
     return RecompileImpl(cx, /* force = */ false);
 }
 
 bool
-SetDenseElement(JSContext* cx, HandleNativeObject obj, int32_t index, HandleValue value,
-                bool strict)
+SetDenseOrUnboxedArrayElement(JSContext* cx, HandleObject obj, int32_t index,
+                              HandleValue value, bool strict)
 {
     // This function is called from Ion code for StoreElementHole's OOL path.
-    // In this case we know the object is native and we can use setDenseElement
-    // instead of setDenseElementWithType.
+    // In this case we know the object is native or an unboxed array and we can
+    // use setDenseElement instead of setDenseElementWithType.
 
     NativeObject::EnsureDenseResult result = NativeObject::ED_SPARSE;
     do {
-        if (index < 0)
+        if (index < 0 || obj->is<UnboxedArrayObject>())
             break;
         bool isArray = obj->is<ArrayObject>();
         if (isArray && !obj->as<ArrayObject>().lengthIsWritable())
             break;
         uint32_t idx = uint32_t(index);
-        result = obj->ensureDenseElements(cx, idx, 1);
+        result = obj->as<NativeObject>().ensureDenseElements(cx, idx, 1);
         if (result != NativeObject::ED_OK)
             break;
         if (isArray) {
             ArrayObject& arr = obj->as<ArrayObject>();
             if (idx >= arr.length())
                 arr.setLengthInt32(idx + 1);
         }
-        obj->setDenseElement(idx, value);
+        obj->as<NativeObject>().setDenseElement(idx, value);
         return true;
     } while (false);
 
     if (result == NativeObject::ED_FAILED)
         return false;
     MOZ_ASSERT(result == NativeObject::ED_SPARSE);
 
+    if (index >= 0 && obj->is<UnboxedArrayObject>()) {
+        UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>();
+        if (uint32_t(index) == nobj->initializedLength() &&
+            uint32_t(index) < UnboxedArrayObject::MaximumCapacity)
+        {
+            return nobj->appendElementNoTypeChange(cx, index, value);
+        }
+    }
+
     RootedValue indexVal(cx, Int32Value(index));
     return SetObjectElement(cx, obj, indexVal, value, strict);
 }
 
 void
 AutoDetectInvalidation::setReturnOverride()
 {
     cx_->runtime()->jitRuntime()->setIonReturnOverride(rval_.get());
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -741,18 +741,18 @@ bool ArraySpliceDense(JSContext* cx, Han
 
 bool Recompile(JSContext* cx);
 bool ForcedRecompile(JSContext* cx);
 JSString* RegExpReplace(JSContext* cx, HandleString string, HandleObject regexp,
                         HandleString repl);
 JSString* StringReplace(JSContext* cx, HandleString string, HandleString pattern,
                         HandleString repl);
 
-bool SetDenseElement(JSContext* cx, HandleNativeObject obj, int32_t index, HandleValue value,
-                     bool strict);
+bool SetDenseOrUnboxedArrayElement(JSContext* cx, HandleObject obj, int32_t index,
+                                   HandleValue value, bool strict);
 
 void AssertValidObjectPtr(JSContext* cx, JSObject* obj);
 void AssertValidObjectOrNullPtr(JSContext* cx, JSObject* obj);
 void AssertValidStringPtr(JSContext* cx, JSString* str);
 void AssertValidSymbolPtr(JSContext* cx, JS::Symbol* sym);
 void AssertValidValue(JSContext* cx, Value* v);
 
 void MarkValueFromIon(JSRuntime* rt, Value* vp);
--- a/js/src/jit/x64/MacroAssembler-x64.cpp
+++ b/js/src/jit/x64/MacroAssembler-x64.cpp
@@ -529,18 +529,23 @@ MacroAssemblerX64::branchPtrInNurseryRan
 }
 
 void
 MacroAssemblerX64::branchValueIsNurseryObject(Condition cond, ValueOperand value, Register temp,
                                               Label* label)
 {
     MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual);
 
+    const Nursery& nursery = GetJitContext()->runtime->gcNursery();
+
+    // Avoid creating a bogus ObjectValue below.
+    if (!nursery.exists())
+        return;
+
     // 'Value' representing the start of the nursery tagged as a JSObject
-    const Nursery& nursery = GetJitContext()->runtime->gcNursery();
     Value start = ObjectValue(*reinterpret_cast<JSObject*>(nursery.start()));
 
     movePtr(ImmWord(-ptrdiff_t(start.asRawBits())), ScratchReg);
     addPtr(value.valueReg(), ScratchReg);
     branchPtr(cond == Assembler::Equal ? Assembler::Below : Assembler::AboveOrEqual,
               ScratchReg, Imm32(nursery.nurserySize()), label);
 }
 
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -1133,16 +1133,17 @@ namespace JS {
 class JS_PUBLIC_API(RuntimeOptions) {
   public:
     RuntimeOptions()
       : baseline_(true),
         ion_(true),
         asmJS_(true),
         nativeRegExp_(true),
         unboxedObjects_(false), // Not enabled by default yet
+        unboxedArrays_(false), // Ditto
         werror_(false),
         strictMode_(false),
         extraWarnings_(false),
         varObjFix_(false)
     {
     }
 
     bool baseline() const { return baseline_; }
@@ -1182,16 +1183,22 @@ class JS_PUBLIC_API(RuntimeOptions) {
     }
 
     bool unboxedObjects() const { return unboxedObjects_; }
     RuntimeOptions& setUnboxedObjects(bool flag) {
         unboxedObjects_ = flag;
         return *this;
     }
 
+    bool unboxedArrays() const { return unboxedArrays_; }
+    RuntimeOptions& setUnboxedArrays(bool flag) {
+        unboxedArrays_ = flag;
+        return *this;
+    }
+
     bool werror() const { return werror_; }
     RuntimeOptions& setWerror(bool flag) {
         werror_ = flag;
         return *this;
     }
     RuntimeOptions& toggleWerror() {
         werror_ = !werror_;
         return *this;
@@ -1228,16 +1235,17 @@ class JS_PUBLIC_API(RuntimeOptions) {
     }
 
   private:
     bool baseline_ : 1;
     bool ion_ : 1;
     bool asmJS_ : 1;
     bool nativeRegExp_ : 1;
     bool unboxedObjects_ : 1;
+    bool unboxedArrays_ : 1;
     bool werror_ : 1;
     bool strictMode_ : 1;
     bool extraWarnings_ : 1;
     bool varObjFix_ : 1;
 };
 
 JS_PUBLIC_API(RuntimeOptions&)
 RuntimeOptionsRef(JSRuntime* rt);
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -2282,18 +2282,23 @@ TryReuseArrayGroup(JSObject* obj, ArrayO
 {
     /*
      * Try to change the group of a newly created array narr to the same group
      * as obj. This can only be performed if the original object is an array
      * and has the same prototype.
      */
     MOZ_ASSERT(ObjectGroup::hasDefaultNewGroup(narr->getProto(), &ArrayObject::class_, narr->group()));
 
-    if (obj->is<ArrayObject>() && !obj->isSingleton() && obj->getProto() == narr->getProto())
+    if (obj->is<ArrayObject>() &&
+        !obj->isSingleton() &&
+        !obj->group()->hasUnanalyzedPreliminaryObjects() &&
+        obj->getProto() == narr->getProto())
+    {
         narr->setGroup(obj->group());
+    }
 }
 
 /*
  * Returns true if this is a dense array whose |count| properties starting from
  * |startingIndex| may be accessed (get, set, delete) directly through its
  * contiguous vector of elements without fear of getters, setters, etc. along
  * the prototype chain, or of enumerators requiring notification of
  * modifications.
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -326,16 +326,22 @@ GetGCKindSlots(AllocKind thingKind, cons
      * space for the extra fields in JSFunction, but have no fixed slots.
      */
     if (clasp == FunctionClassPtr)
         nslots = 0;
 
     return nslots;
 }
 
+static inline size_t
+GetGCKindBytes(AllocKind thingKind)
+{
+    return sizeof(JSObject_Slots0) + GetGCKindSlots(thingKind) * sizeof(Value);
+}
+
 // Class to assist in triggering background chunk allocation. This cannot be done
 // while holding the GC or worker thread state lock due to lock ordering issues.
 // As a result, the triggering is delayed using this class until neither of the
 // above locks is held.
 class AutoMaybeStartBackgroundAllocation;
 
 /*
  * A single segment of a SortedArenaList. Each segment has a head and a tail,
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -278,18 +278,30 @@ Snapshot(JSContext* cx, HandleObject pob
 
     do {
         if (JSNewEnumerateOp enumerate = pobj->getOps()->enumerate) {
             // This hook has the full control over what gets enumerated.
             AutoIdVector properties(cx);
             if (!enumerate(cx, pobj, properties))
                  return false;
 
+            RootedId id(cx);
             for (size_t n = 0; n < properties.length(); n++) {
-                if (!Enumerate(cx, pobj, properties[n], true, flags, ht, props))
+                id = properties[n];
+                bool enumerable = true;
+
+                // The enumerate hook does not indicate whether the properties
+                // it returns are enumerable or not. There is no non-effectful
+                // way to determine this from the object, so carve out
+                // exceptions here for places where the property is not
+                // enumerable.
+                if (pobj->is<UnboxedArrayObject>() && id == NameToId(cx->names().length))
+                    enumerable = false;
+
+                if (!Enumerate(cx, pobj, id, enumerable, flags, ht, props))
                     return false;
             }
 
             if (pobj->isNative()) {
                 if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, ht, props))
                     return false;
             }
         } else if (pobj->isNative()) {
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -24,17 +24,17 @@
 #include "jscompartmentinlines.h"
 #include "jsgcinlines.h"
 
 #include "vm/TypeInference-inl.h"
 
 inline js::Shape*
 JSObject::maybeShape() const
 {
-    if (is<js::UnboxedPlainObject>())
+    if (is<js::UnboxedPlainObject>() || is<js::UnboxedArrayObject>())
         return nullptr;
     return *reinterpret_cast<js::Shape**>(uintptr_t(this) + offsetOfShape());
 }
 
 inline js::Shape*
 JSObject::ensureShape(js::ExclusiveContext* cx)
 {
     if (is<js::UnboxedPlainObject>() && !js::UnboxedPlainObject::convertToNative(cx->asJSContext(), this))
@@ -263,16 +263,18 @@ JSObject::create(js::ExclusiveContext* c
     MOZ_ASSERT(group->clasp() != &js::ArrayObject::class_);
     MOZ_ASSERT_IF(!js::ClassCanHaveFixedData(group->clasp()),
                   js::gc::GetGCKindSlots(kind, group->clasp()) == shape->numFixedSlots());
     MOZ_ASSERT_IF(group->clasp()->flags & JSCLASS_BACKGROUND_FINALIZE,
                   IsBackgroundFinalized(kind));
     MOZ_ASSERT_IF(group->clasp()->finalize,
                   heap == js::gc::TenuredHeap ||
                   (group->clasp()->flags & JSCLASS_FINALIZE_FROM_NURSERY));
+    MOZ_ASSERT_IF(group->hasUnanalyzedPreliminaryObjects(),
+                  heap == js::gc::TenuredHeap);
 
     // Non-native classes cannot have reserved slots or private data, and the
     // objects can't have any fixed slots, for compatibility with
     // GetReservedOrProxyPrivateSlot.
     MOZ_ASSERT_IF(!group->clasp()->isNative(), JSCLASS_RESERVED_SLOTS(group->clasp()) == 0);
     MOZ_ASSERT_IF(!group->clasp()->isNative(), !group->clasp()->hasPrivate());
     MOZ_ASSERT_IF(!group->clasp()->isNative(), shape->numFixedSlots() == 0);
     MOZ_ASSERT_IF(!group->clasp()->isNative(), shape->slotSpan() == 0);
@@ -753,21 +755,21 @@ GuessArrayGCKind(size_t numSlots)
 
 inline bool
 ObjectClassIs(HandleObject obj, ESClassValue classValue, JSContext* cx)
 {
     if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
         return Proxy::objectClassIs(obj, classValue, cx);
 
     switch (classValue) {
-      case ESClass_Object: return obj->is<PlainObject>();
+      case ESClass_Object: return obj->is<PlainObject>() || obj->is<UnboxedPlainObject>();
       case ESClass_Array:
       case ESClass_IsArray:
         // There difference between those is only relevant for proxies.
-        return obj->is<ArrayObject>();
+        return obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>();
       case ESClass_Number: return obj->is<NumberObject>();
       case ESClass_String: return obj->is<StringObject>();
       case ESClass_Boolean: return obj->is<BooleanObject>();
       case ESClass_RegExp: return obj->is<RegExpObject>();
       case ESClass_ArrayBuffer: return obj->is<ArrayBufferObject>();
       case ESClass_SharedArrayBuffer: return obj->is<SharedArrayBufferObject>();
       case ESClass_Date: return obj->is<DateObject>();
       case ESClass_Set: return obj->is<SetObject>();
@@ -784,17 +786,17 @@ IsObjectWithClass(const Value& v, ESClas
     RootedObject obj(cx, &v.toObject());
     return ObjectClassIs(obj, classValue, cx);
 }
 
 // ES6 7.2.2
 inline bool
 IsArray(HandleObject obj, JSContext* cx)
 {
-    if (obj->is<ArrayObject>())
+    if (obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>())
         return true;
 
     return ObjectClassIs(obj, ESClass_IsArray, cx);
 }
 
 inline bool
 Unbox(JSContext* cx, HandleObject obj, MutableHandleValue vp)
 {
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -138,16 +138,17 @@ static JS::PersistentRootedValue gInterr
 
 static bool enableDisassemblyDumps = false;
 static bool offthreadCompilation = false;
 static bool enableBaseline = false;
 static bool enableIon = false;
 static bool enableAsmJS = false;
 static bool enableNativeRegExp = false;
 static bool enableUnboxedObjects = false;
+static bool enableUnboxedArrays = false;
 #ifdef JS_GC_ZEAL
 static char gZealStr[128];
 #endif
 
 static bool printTiming = false;
 static const char* jsCacheDir = nullptr;
 static const char* jsCacheAsmJSPath = nullptr;
 static bool jsCachingEnabled = false;
@@ -5809,22 +5810,24 @@ ProcessArgs(JSContext* cx, OptionParser*
 static bool
 SetRuntimeOptions(JSRuntime* rt, const OptionParser& op)
 {
     enableBaseline = !op.getBoolOption("no-baseline");
     enableIon = !op.getBoolOption("no-ion");
     enableAsmJS = !op.getBoolOption("no-asmjs");
     enableNativeRegExp = !op.getBoolOption("no-native-regexp");
     enableUnboxedObjects = op.getBoolOption("unboxed-objects");
+    enableUnboxedArrays = op.getBoolOption("unboxed-arrays");
 
     JS::RuntimeOptionsRef(rt).setBaseline(enableBaseline)
                              .setIon(enableIon)
                              .setAsmJS(enableAsmJS)
                              .setNativeRegExp(enableNativeRegExp)
-                             .setUnboxedObjects(enableUnboxedObjects);
+                             .setUnboxedObjects(enableUnboxedObjects)
+                             .setUnboxedArrays(enableUnboxedArrays);
 
     if (const char* str = op.getStringOption("ion-scalar-replacement")) {
         if (strcmp(str, "on") == 0)
             jit::js_JitOptions.disableScalarReplacement = false;
         else if (strcmp(str, "off") == 0)
             jit::js_JitOptions.disableScalarReplacement = true;
         else
             return OptionFailure("ion-scalar-replacement", str);
@@ -6019,17 +6022,18 @@ SetRuntimeOptions(JSRuntime* rt, const O
 static void
 SetWorkerRuntimeOptions(JSRuntime* rt)
 {
     // Copy option values from the main thread.
     JS::RuntimeOptionsRef(rt).setBaseline(enableBaseline)
                              .setIon(enableIon)
                              .setAsmJS(enableAsmJS)
                              .setNativeRegExp(enableNativeRegExp)
-                             .setUnboxedObjects(enableUnboxedObjects);
+                             .setUnboxedObjects(enableUnboxedObjects)
+                             .setUnboxedArrays(enableUnboxedArrays);
     rt->setOffthreadIonCompilationEnabled(offthreadCompilation);
     rt->profilingScripts = enableDisassemblyDumps;
 
 #ifdef JS_GC_ZEAL
     if (*gZealStr)
         rt->gc.parseAndSetZeal(gZealStr);
 #endif
 }
@@ -6161,17 +6165,18 @@ main(int argc, char** argv, char** envp)
                                          "String arguments to bind as |scriptArgs| in the "
                                          "shell's global")
         || !op.addIntOption('\0', "thread-count", "COUNT", "Use COUNT auxiliary threads "
                             "(default: # of cores - 1)", -1)
         || !op.addBoolOption('\0', "ion", "Enable IonMonkey (default)")
         || !op.addBoolOption('\0', "no-ion", "Disable IonMonkey")
         || !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation")
         || !op.addBoolOption('\0', "no-native-regexp", "Disable native regexp compilation")
-        || !op.addBoolOption('\0', "unboxed-objects", "Allow creating unboxed objects")
+        || !op.addBoolOption('\0', "unboxed-objects", "Allow creating unboxed plain objects")
+        || !op.addBoolOption('\0', "unboxed-arrays", "Allow creating unboxed arrays")
         || !op.addStringOption('\0', "ion-scalar-replacement", "on/off",
                                "Scalar Replacement (default: on, off to disable)")
         || !op.addStringOption('\0', "ion-gvn", "[mode]",
                                "Specify Ion global value numbering:\n"
                                "  off: disable GVN\n"
                                "  on:  enable GVN (default)\n")
         || !op.addStringOption('\0', "ion-licm", "on/off",
                                "Loop invariant code motion (default: on, off to disable)")
--- a/js/src/vm/ArrayObject-inl.h
+++ b/js/src/vm/ArrayObject-inl.h
@@ -33,16 +33,18 @@ ArrayObject::setLength(ExclusiveContext*
 ArrayObject::createArrayInternal(ExclusiveContext* cx, gc::AllocKind kind, gc::InitialHeap heap,
                                  HandleShape shape, HandleObjectGroup group)
 {
     // Create a new array and initialize everything except for its elements.
     MOZ_ASSERT(shape && group);
     MOZ_ASSERT(group->clasp() == shape->getObjectClass());
     MOZ_ASSERT(group->clasp() == &ArrayObject::class_);
     MOZ_ASSERT_IF(group->clasp()->finalize, heap == gc::TenuredHeap);
+    MOZ_ASSERT_IF(group->hasUnanalyzedPreliminaryObjects(),
+                  heap == js::gc::TenuredHeap);
 
     // 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.
     MOZ_ASSERT(shape->numFixedSlots() == 0);
 
     size_t nDynamicSlots = dynamicSlotsCount(0, shape->slotSpan(), group->clasp());
     JSObject* obj = Allocate<JSObject>(cx, kind, nDynamicSlots, heap, group->clasp());
     if (!obj)
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -621,17 +621,17 @@ InitElemOperation(JSContext* cx, HandleO
 }
 
 static MOZ_ALWAYS_INLINE bool
 InitArrayElemOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, uint32_t index, HandleValue val)
 {
     JSOp op = JSOp(*pc);
     MOZ_ASSERT(op == JSOP_INITELEM_ARRAY || op == JSOP_INITELEM_INC);
 
-    MOZ_ASSERT(obj->is<ArrayObject>());
+    MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>());
 
     /*
      * If val is a hole, do not call DefineElement.
      *
      * Furthermore, if the current op is JSOP_INITELEM_INC, always call
      * SetLengthProperty even if it is not the last element initialiser,
      * because it may be followed by JSOP_SPREAD, which will not set the array
      * length if nothing is spread.
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1909,17 +1909,16 @@ CASE(EnableInterruptsPseudoOpcode)
     /* Commence executing the actual opcode. */
     SANITY_CHECKS();
     DISPATCH_TO(op);
 }
 
 /* Various 1-byte no-ops. */
 CASE(JSOP_NOP)
 CASE(JSOP_UNUSED2)
-CASE(JSOP_UNUSED126)
 CASE(JSOP_UNUSED148)
 CASE(JSOP_BACKPATCH)
 CASE(JSOP_UNUSED150)
 CASE(JSOP_UNUSED161)
 CASE(JSOP_UNUSED162)
 CASE(JSOP_UNUSED163)
 CASE(JSOP_UNUSED164)
 CASE(JSOP_UNUSED165)
@@ -3495,51 +3494,35 @@ END_CASE(JSOP_INITELEM_GETTER)
 CASE(JSOP_HOLE)
     PUSH_HOLE();
 END_CASE(JSOP_HOLE)
 
 CASE(JSOP_NEWINIT)
 {
     uint8_t i = GET_UINT8(REGS.pc);
     MOZ_ASSERT(i == JSProto_Array || i == JSProto_Object);
-    RootedObject& obj = rootObject0;
-
-    if (i == JSProto_Array) {
-        NewObjectKind newKind = GenericObject;
-        if (ObjectGroup::useSingletonForAllocationSite(script, REGS.pc, &ArrayObject::class_))
-            newKind = SingletonObject;
-        obj = NewDenseEmptyArray(cx, NullPtr(), newKind);
-        if (!obj || !ObjectGroup::setAllocationSiteObjectGroup(cx, script, REGS.pc, obj,
-                                                               newKind == SingletonObject))
-        {
-            goto error;
-        }
-    } else {
+
+    JSObject* obj;
+    if (i == JSProto_Array)
+        obj = NewArrayOperation(cx, script, REGS.pc, 0);
+    else
         obj = NewObjectOperation(cx, script, REGS.pc);
-        if (!obj)
-            goto error;
-    }
+
+    if (!obj)
+        goto error;
     PUSH_OBJECT(*obj);
 }
 END_CASE(JSOP_NEWINIT)
 
 CASE(JSOP_NEWARRAY)
-{
-    unsigned count = GET_UINT24(REGS.pc);
-    RootedObject& obj = rootObject0;
-    NewObjectKind newKind = GenericObject;
-    if (ObjectGroup::useSingletonForAllocationSite(script, REGS.pc, &ArrayObject::class_))
-        newKind = SingletonObject;
-    obj = NewDenseFullyAllocatedArray(cx, count, NullPtr(), newKind);
-    if (!obj || !ObjectGroup::setAllocationSiteObjectGroup(cx, script, REGS.pc, obj,
-                                                           newKind == SingletonObject))
-    {
+CASE(JSOP_SPREADCALLARRAY)
+{
+    JSObject* obj = NewArrayOperation(cx, script, REGS.pc, GET_UINT24(REGS.pc));
+    if (!obj)
         goto error;
-    }
-
     PUSH_OBJECT(*obj);
 }
 END_CASE(JSOP_NEWARRAY)
 
 CASE(JSOP_NEWARRAY_COPYONWRITE)
 {
     RootedObject& baseobj = rootObject0;
     baseobj = ObjectGroup::getOrFixupCopyOnWriteObject(cx, script, REGS.pc);
@@ -3632,18 +3615,16 @@ END_CASE(JSOP_INITELEM)
 CASE(JSOP_INITELEM_ARRAY)
 {
     MOZ_ASSERT(REGS.stackDepth() >= 2);
     HandleValue val = REGS.stackHandleAt(-1);
 
     RootedObject& obj = rootObject0;
     obj = &REGS.sp[-2].toObject();
 
-    MOZ_ASSERT(obj->is<ArrayObject>());
-
     uint32_t index = GET_UINT24(REGS.pc);
     if (!InitArrayElemOperation(cx, REGS.pc, obj, index, val))
         goto error;
 
     REGS.sp--;
 }
 END_CASE(JSOP_INITELEM_ARRAY)
 
@@ -4698,30 +4679,89 @@ js::NewObjectOperationWithTemplate(JSCon
     // object is not a singleton and has had its preliminary objects analyzed,
     // with the template object a copy of the object to create.
     MOZ_ASSERT(!templateObject->isSingleton());
 
     NewObjectKind newKind = templateObject->group()->shouldPreTenure() ? TenuredObject : GenericObject;
 
     if (templateObject->group()->maybeUnboxedLayout()) {
         RootedObjectGroup group(cx, templateObject->group());
-        JSObject* obj = UnboxedPlainObject::create(cx, group, newKind);
-        if (!obj)
-            return nullptr;
-        return obj;
+        return UnboxedPlainObject::create(cx, group, newKind);
     }
 
     JSObject* obj = CopyInitializerObject(cx, templateObject.as<PlainObject>(), newKind);
     if (!obj)
         return nullptr;
 
     obj->setGroup(templateObject->group());
     return obj;
 }
 
+JSObject*
+js::NewArrayOperation(JSContext* cx, HandleScript script, jsbytecode* pc, uint32_t length,
+                      NewObjectKind newKind /* = GenericObject */)
+{
+    MOZ_ASSERT(newKind != SingletonObject);
+
+    RootedObjectGroup group(cx);
+    if (ObjectGroup::useSingletonForAllocationSite(script, pc, JSProto_Array)) {
+        newKind = SingletonObject;
+    } else {
+        group = ObjectGroup::allocationSiteGroup(cx, script, pc, JSProto_Array);
+        if (!group)
+            return nullptr;
+        if (group->maybePreliminaryObjects())
+            group->maybePreliminaryObjects()->maybeAnalyze(cx, group);
+
+        if (group->shouldPreTenure() || group->maybePreliminaryObjects())
+            newKind = TenuredObject;
+
+        if (group->maybeUnboxedLayout())
+            return UnboxedArrayObject::create(cx, group, length, newKind);
+    }
+
+    ArrayObject* obj = NewDenseFullyAllocatedArray(cx, length, NullPtr(), newKind);
+    if (!obj)
+        return nullptr;
+
+    if (newKind == SingletonObject) {
+        MOZ_ASSERT(obj->isSingleton());
+    } else {
+        obj->setGroup(group);
+
+        if (PreliminaryObjectArray* preliminaryObjects = group->maybePreliminaryObjects())
+            preliminaryObjects->registerNewObject(obj);
+    }
+
+    return obj;
+}
+
+JSObject*
+js::NewArrayOperationWithTemplate(JSContext* cx, HandleObject templateObject)
+{
+    MOZ_ASSERT(!templateObject->isSingleton());
+
+    NewObjectKind newKind = templateObject->group()->shouldPreTenure() ? TenuredObject : GenericObject;
+
+    if (templateObject->is<UnboxedArrayObject>()) {
+        uint32_t length = templateObject->as<UnboxedArrayObject>().length();
+        RootedObjectGroup group(cx, templateObject->group());
+        return UnboxedArrayObject::create(cx, group, length, newKind);
+    }
+
+    ArrayObject* obj = NewDenseFullyAllocatedArray(cx, templateObject->as<ArrayObject>().length(),
+                                                   NullPtr(), newKind);
+    if (!obj)
+        return nullptr;
+
+    MOZ_ASSERT(obj->lastProperty() == templateObject->as<ArrayObject>().lastProperty());
+    obj->setGroup(templateObject->group());
+    return obj;
+}
+
 void
 js::ReportUninitializedLexical(JSContext* cx, HandlePropertyName name)
 {
     JSAutoByteString printable;
     if (AtomToPrintableString(cx, name, &printable)) {
         JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_UNINITIALIZED_LEXICAL,
                              printable.ptr());
     }
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -440,16 +440,23 @@ SpreadCallOperation(JSContext* cx, Handl
 
 JSObject*
 NewObjectOperation(JSContext* cx, HandleScript script, jsbytecode* pc,
                    NewObjectKind newKind = GenericObject);
 
 JSObject*
 NewObjectOperationWithTemplate(JSContext* cx, HandleObject templateObject);
 
+JSObject*
+NewArrayOperation(JSContext* cx, HandleScript script, jsbytecode* pc, uint32_t length,
+                  NewObjectKind newKind = GenericObject);
+
+JSObject*
+NewArrayOperationWithTemplate(JSContext* cx, HandleObject templateObject);
+
 inline bool
 SetConstOperation(JSContext* cx, HandleObject varobj, HandlePropertyName name, HandleValue rval)
 {
     return DefineProperty(cx, varobj, name, rval, nullptr, nullptr,
                           JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
 }
 
 void
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -323,18 +323,21 @@ NativeObject::setLastPropertyShrinkFixed
 void
 NativeObject::setLastPropertyMakeNonNative(Shape* shape)
 {
     MOZ_ASSERT(!inDictionaryMode());
     MOZ_ASSERT(!shape->getObjectClass()->isNative());
     MOZ_ASSERT(shape->compartment() == compartment());
     MOZ_ASSERT(shape->slotSpan() == 0);
     MOZ_ASSERT(shape->numFixedSlots() == 0);
-    MOZ_ASSERT(!hasDynamicElements());
-    MOZ_ASSERT(!hasDynamicSlots());
+
+    if (hasDynamicElements())
+        js_free(getElementsHeader());
+    if (hasDynamicSlots())
+        js_free(slots_);
 
     shape_ = shape;
 }
 
 void
 NativeObject::setLastPropertyMakeNative(ExclusiveContext* cx, Shape* shape)
 {
     MOZ_ASSERT(getClass()->isNative());
@@ -705,16 +708,19 @@ ReallocateElements(ExclusiveContext* cx,
 // chosen capacity would be 2/3 or more of the array's length, the chosen
 // capacity is adjusted (up or down) to be equal to the array's length
 // (assuming length is at least as large as the required capacity). This avoids
 // the allocation of excess elements which are unlikely to be needed, either in
 // this resizing or a subsequent one. The 2/3 factor is chosen so that
 // exceptional resizings will at most triple the capacity, as opposed to the
 // usual doubling.
 //
+// Note: the structure and behavior of this method follow along with
+// UnboxedArrayObject::chooseCapacityIndex. Changes to the allocation strategy
+// in one should generally be matched by the other.
 /* static */ uint32_t
 NativeObject::goodAllocated(uint32_t reqAllocated, uint32_t length = 0)
 {
     static const uint32_t Mebi = 1024 * 1024;
 
     // This table was generated with this JavaScript code and a small amount
     // subsequent reformatting:
     //
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -1163,16 +1163,25 @@ ObjectGroup::allocationSiteGroup(JSConte
                 cx->new_<PreliminaryObjectArrayWithTemplate>(shape);
             if (preliminaryObjects)
                 res->setPreliminaryObjects(preliminaryObjects);
             else
                 cx->recoverFromOutOfMemory();
         }
     }
 
+    if (JSOp(*pc) == JSOP_NEWARRAY && cx->runtime()->options().unboxedArrays()) {
+        PreliminaryObjectArrayWithTemplate* preliminaryObjects =
+            cx->new_<PreliminaryObjectArrayWithTemplate>(nullptr);
+        if (preliminaryObjects)
+            res->setPreliminaryObjects(preliminaryObjects);
+        else
+            cx->recoverFromOutOfMemory();
+    }
+
     if (!table->add(p, key, res))
         return nullptr;
 
     return res;
 }
 
 /* static */ ObjectGroup*
 ObjectGroup::callingAllocationSiteGroup(JSContext* cx, JSProtoKey key)
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -828,18 +828,18 @@ 1234567890123456789012345678901234567890
     \
     /* Object and array literal support. */ \
     /*
      * Pushes newly created object onto the stack.
      *
      * This opcode takes the kind of initializer (JSProto_Array or
      * JSProto_Object).
      *
-     * This opcode has an extra byte so it can be exchanged with JSOP_NEWOBJECT
-     * during emit.
+     * This opcode has three extra bytes so it can be exchanged with
+     * JSOP_NEWOBJECT during emit.
      *   Category: Literals
      *   Type: Object
      *   Operands: uint8_t kind (, uint24_t extra)
      *   Stack: => obj
      */ \
     macro(JSOP_NEWINIT,   89, "newinit",    NULL,         5,  0,  1, JOF_UINT8) \
     /*
      * Pushes newly created array onto the stack.
@@ -1267,17 +1267,28 @@ 1234567890123456789012345678901234567890
      * LIKE JSOP_GETELEM but takes receiver on stack, and the propval is
      * evaluated before the obj.
      *   Category: Literals
      *   Type: Object
      *   Operands:
      *   Stack: receiver, obj, propval => obj[propval]
      */ \
     macro(JSOP_GETELEM_SUPER, 125, "getelem-super", NULL, 1,  3,  1, JOF_BYTE |JOF_ELEM|JOF_LEFTASSOC) \
-    macro(JSOP_UNUSED126,  126, "unused126", NULL,      1,  0,  0,  JOF_BYTE) \
+    /*
+     * Pushes newly created array for a spread call onto the stack. This has
+     * the same semantics as JSOP_NEWARRAY, but is distinguished to avoid
+     * using unboxed arrays in spread calls, which would make compiling spread
+     * calls in baseline more complex.
+     *
+     *   Category: Literals
+     *   Type: Array
+     *   Operands: uint24_t length
+     *   Stack: => obj
+     */ \
+    macro(JSOP_SPREADCALLARRAY, 126, "spreadcallarray", NULL, 4,  0,  1, JOF_UINT24) \
     \
     /*
      * Defines the given function on the current scope.
      *
      * This is used for global scripts and also in some cases for function
      * scripts where use of dynamic scoping inhibits optimization.
      *   Category: Variables and Scopes
      *   Type: Variables
--- a/js/src/vm/Runtime-inl.h
+++ b/js/src/vm/Runtime-inl.h
@@ -47,16 +47,18 @@ NewObjectCache::newObjectFromHit(JSConte
     Entry* entry = &entries[entryIndex];
 
     NativeObject* templateObj = reinterpret_cast<NativeObject*>(&entry->templateObject);
 
     // Do an end run around JSObject::group() to avoid doing AutoUnprotectCell
     // on the templateObj, which is not a GC thing and can't use runtimeFromAnyThread.
     ObjectGroup* group = templateObj->group_;
 
+    MOZ_ASSERT(!group->hasUnanalyzedPreliminaryObjects());
+
     if (group->shouldPreTenure())
         heap = gc::TenuredHeap;
 
     if (cx->runtime()->gc.upcomingZealousGC())
         return nullptr;
 
     NativeObject* obj = static_cast<NativeObject*>(Allocate<JSObject, NoGC>(cx, entry->kind, 0,
                                                                              heap, group->clasp()));
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -638,16 +638,19 @@ ConstraintTypeSet::addType(ExclusiveCont
     } else {
         MOZ_ASSERT(!constraintList);
     }
 }
 
 void
 TypeSet::print(FILE* fp)
 {
+    if (!fp)
+        fp = stderr;
+
     if (flags & TYPE_FLAG_NON_DATA_PROPERTY)
         fprintf(fp, " [non-data]");
 
     if (flags & TYPE_FLAG_NON_WRITABLE_PROPERTY)
         fprintf(fp, " [non-writable]");
 
     if (definiteProperty())
         fprintf(fp, " [definite:%d]", definiteSlot());
@@ -2376,16 +2379,18 @@ TemporaryTypeSet::propertyNeedsBarrier(C
     }
 
     return false;
 }
 
 bool
 js::ClassCanHaveExtraProperties(const Class* clasp)
 {
+    if (clasp == &UnboxedPlainObject::class_ || clasp == &UnboxedArrayObject::class_)
+        return false;
     return clasp->resolve
         || clasp->ops.lookupProperty
         || clasp->ops.getProperty
         || IsAnyTypedArrayClass(clasp);
 }
 
 static bool
 PrototypeHasIndexedProperty(CompilerConstraintList* constraints, JSObject* obj)
@@ -3332,26 +3337,29 @@ PreliminaryObjectArray::sweep()
         if (*ptr && IsAboutToBeFinalizedUnbarriered(ptr))
             *ptr = nullptr;
     }
 }
 
 void
 PreliminaryObjectArrayWithTemplate::trace(JSTracer* trc)
 {
-    TraceEdge(trc, &shape_, "PreliminaryObjectArrayWithTemplate_shape");
+    if (shape_)
+        TraceEdge(trc, &shape_, "PreliminaryObjectArrayWithTemplate_shape");
 }
 
 /* static */ void
 PreliminaryObjectArrayWithTemplate::writeBarrierPre(PreliminaryObjectArrayWithTemplate* objects)
 {
-    if (!objects->shape()->runtimeFromAnyThread()->needsIncrementalBarrier())
+    Shape* shape = objects->shape();
+
+    if (!shape || !shape->runtimeFromAnyThread()->needsIncrementalBarrier())
         return;
 
-    JS::Zone* zone = objects->shape()->zoneFromAnyThread();
+    JS::Zone* zone = shape->zoneFromAnyThread();
     if (zone->needsIncrementalBarrier())
         objects->trace(zone->barrierTracer());
 }
 
 // Return whether shape consists entirely of plain data properties.
 static bool
 OnlyHasDataProperties(Shape* shape)
 {
@@ -3401,43 +3409,47 @@ PreliminaryObjectArrayWithTemplate::mayb
     if (!force && !full())
         return;
 
     AutoEnterAnalysis enter(cx);
 
     ScopedJSDeletePtr<PreliminaryObjectArrayWithTemplate> preliminaryObjects(this);
     group->detachPreliminaryObjects();
 
-    MOZ_ASSERT(shape()->slotSpan() != 0);
-    MOZ_ASSERT(OnlyHasDataProperties(shape()));
-
-    // Make sure all the preliminary objects reflect the properties originally
-    // in the template object.
-    for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
-        JSObject* objBase = preliminaryObjects->get(i);
-        if (!objBase)
-            continue;
-        PlainObject* obj = &objBase->as<PlainObject>();
-
-        if (obj->inDictionaryMode() || !OnlyHasDataProperties(obj->lastProperty()))
-            return;
-
-        if (CommonPrefix(obj->lastProperty(), shape()) != shape())
-            return;
+    if (shape()) {
+        MOZ_ASSERT(shape()->slotSpan() != 0);
+        MOZ_ASSERT(OnlyHasDataProperties(shape()));
+
+        // Make sure all the preliminary objects reflect the properties originally
+        // in the template object.
+        for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+            JSObject* objBase = preliminaryObjects->get(i);
+            if (!objBase)
+                continue;
+            PlainObject* obj = &objBase->as<PlainObject>();
+
+            if (obj->inDictionaryMode() || !OnlyHasDataProperties(obj->lastProperty()))
+                return;
+
+            if (CommonPrefix(obj->lastProperty(), shape()) != shape())
+                return;
+        }
     }
 
     TryConvertToUnboxedLayout(cx, shape(), group, preliminaryObjects);
     if (group->maybeUnboxedLayout())
         return;
 
-    // We weren't able to use an unboxed layout, but since the preliminary
-    // still reflect the template object's properties, and all objects in the
-    // future will be created with those properties, the properties can be
-    // marked as definite for objects in the group.
-    group->addDefiniteProperties(cx, shape());
+    if (shape()) {
+        // We weren't able to use an unboxed layout, but since the preliminary
+        // still reflect the template object's properties, and all objects in the
+        // future will be created with those properties, the properties can be
+        // marked as definite for objects in the group.
+        group->addDefiniteProperties(cx, shape());
+    }
 }
 
 /////////////////////////////////////////////////////////////////////
 // TypeNewScript
 /////////////////////////////////////////////////////////////////////
 
 // Make a TypeNewScript for |group|, and set it up to hold the preliminary
 // objects created with the group.
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -256,30 +256,25 @@ UnboxedLayout::makeConstructorCode(JSCon
 
     layout.setConstructorCode(code);
     return true;
 }
 
 void
 UnboxedLayout::detachFromCompartment()
 {
-    remove();
+    if (isInList())
+        remove();
 }
 
-/////////////////////////////////////////////////////////////////////
-// UnboxedPlainObject
-/////////////////////////////////////////////////////////////////////
-
-bool
-UnboxedPlainObject::setValue(ExclusiveContext* cx, const UnboxedLayout::Property& property,
-                             const Value& v)
+static inline bool
+SetUnboxedValue(ExclusiveContext* cx, JSObject* unboxedObject, jsid id,
+                uint8_t* p, JSValueType type, const Value& v)
 {
-    uint8_t* p = &data_[property.offset];
-
-    switch (property.type) {
+    switch (type) {
       case JSVAL_TYPE_BOOLEAN:
         if (v.isBoolean()) {
             *p = v.toBoolean();
             return true;
         }
         return false;
 
       case JSVAL_TYPE_INT32:
@@ -304,41 +299,41 @@ UnboxedPlainObject::setValue(ExclusiveCo
         }
         return false;
 
       case JSVAL_TYPE_OBJECT:
         if (v.isObjectOrNull()) {
             // Update property types when writing object properties. Types for
             // other properties were captured when the unboxed layout was
             // created.
-            AddTypePropertyId(cx, this, NameToId(property.name), v);
+            AddTypePropertyId(cx, unboxedObject, id, v);
 
             // Manually trigger post barriers on the whole object. If we treat
             // the pointer as a HeapPtrObject we will get confused later if the
             // object is converted to its native representation.
             JSObject* obj = v.toObjectOrNull();
-            if (IsInsideNursery(v.toObjectOrNull()) && !IsInsideNursery(this))
-                cx->asJSContext()->runtime()->gc.storeBuffer.putWholeCellFromMainThread(this);
+            if (IsInsideNursery(v.toObjectOrNull()) && !IsInsideNursery(unboxedObject)) {
+                JSRuntime* rt = cx->asJSContext()->runtime();
+                rt->gc.storeBuffer.putWholeCellFromMainThread(unboxedObject);
+            }
 
             *reinterpret_cast<PreBarrieredObject*>(p) = obj;
             return true;
         }
         return false;
 
       default:
         MOZ_CRASH("Invalid type for unboxed value");
     }
 }
 
-Value
-UnboxedPlainObject::getValue(const UnboxedLayout::Property& property)
+static inline Value
+GetUnboxedValue(uint8_t* p, JSValueType type)
 {
-    uint8_t* p = &data_[property.offset];
-
-    switch (property.type) {
+    switch (type) {
       case JSVAL_TYPE_BOOLEAN:
         return BooleanValue(*p != 0);
 
       case JSVAL_TYPE_INT32:
         return Int32Value(*reinterpret_cast<int32_t*>(p));
 
       case JSVAL_TYPE_DOUBLE:
         return DoubleValue(*reinterpret_cast<double*>(p));
@@ -349,16 +344,35 @@ UnboxedPlainObject::getValue(const Unbox
       case JSVAL_TYPE_OBJECT:
         return ObjectOrNullValue(*reinterpret_cast<JSObject**>(p));
 
       default:
         MOZ_CRASH("Invalid type for unboxed value");
     }
 }
 
+/////////////////////////////////////////////////////////////////////
+// UnboxedPlainObject
+/////////////////////////////////////////////////////////////////////
+
+bool
+UnboxedPlainObject::setValue(ExclusiveContext* cx, const UnboxedLayout::Property& property,
+                             const Value& v)
+{
+    uint8_t* p = &data_[property.offset];
+    return SetUnboxedValue(cx, this, NameToId(property.name), p, property.type, v);
+}
+
+Value
+UnboxedPlainObject::getValue(const UnboxedLayout::Property& property)
+{
+    uint8_t* p = &data_[property.offset];
+    return GetUnboxedValue(p, property.type);
+}
+
 void
 UnboxedPlainObject::trace(JSTracer* trc, JSObject* obj)
 {
     if (obj->as<UnboxedPlainObject>().expando_) {
         TraceManuallyBarrieredEdge(trc,
             reinterpret_cast<NativeObject**>(&obj->as<UnboxedPlainObject>().expando_),
             "unboxed_expando");
     }
@@ -414,16 +428,31 @@ UnboxedPlainObject::containsUnboxedOrExp
         return true;
 
     if (maybeExpando() && maybeExpando()->containsShapeOrElement(cx, id))
         return true;
 
     return false;
 }
 
+static bool
+PropagatePropertyTypes(JSContext* cx, jsid id, ObjectGroup* oldGroup, ObjectGroup* newGroup)
+{
+    HeapTypeSet* typeProperty = oldGroup->maybeGetProperty(id);
+    TypeSet::TypeList types;
+    if (!typeProperty->enumerateTypes(&types)) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+    MOZ_ASSERT(!types.empty());
+    for (size_t j = 0; j < types.length(); j++)
+        AddTypePropertyId(cx, newGroup, nullptr, id, types[j]);
+    return true;
+}
+
 /* static */ bool
 UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
 {
     AutoEnterAnalysis enter(cx);
 
     UnboxedLayout& layout = group->unboxedLayout();
     Rooted<TaggedProto> proto(cx, group->proto());
 
@@ -432,16 +461,18 @@ UnboxedLayout::makeNativeGroup(JSContext
     // Immediately clear any new script on the group. This is done by replacing
     // the existing new script with one for a replacement default new group.
     // This is done so that the size of the replacment group's objects is the
     // same as that for the unboxed group, so that we do not see polymorphic
     // slot accesses later on for sites that see converted objects from this
     // group and objects that were allocated using the replacement new group.
     RootedObjectGroup replacementNewGroup(cx);
     if (layout.newScript()) {
+        MOZ_ASSERT(!layout.isArray());
+
         replacementNewGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto);
         if (!replacementNewGroup)
             return false;
 
         PlainObject* templateObject = NewObjectWithGroup<PlainObject>(cx, replacementNewGroup,
                                                                       layout.getAllocKind(),
                                                                       TenuredObject);
         if (!templateObject)
@@ -461,53 +492,66 @@ UnboxedLayout::makeNativeGroup(JSContext
             return false;
 
         replacementNewGroup->setNewScript(replacementNewScript);
         gc::TraceTypeNewScript(replacementNewGroup);
 
         group->clearNewScript(cx, replacementNewGroup);
     }
 
-    size_t nfixed = gc::GetGCKindSlots(layout.getAllocKind());
-    RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_, proto, nfixed, 0));
+    const Class* clasp = layout.isArray() ? &ArrayObject::class_ : &PlainObject::class_;
+    size_t nfixed = layout.isArray() ? 0 : gc::GetGCKindSlots(layout.getAllocKind());
+
+    if (layout.isArray()) {
+        // The length shape to use for arrays is cached via a modified initial
+        // shape for array objects. Create an array now to make sure this entry
+        // is instantiated.
+        if (!NewDenseEmptyArray(cx))
+            return false;
+    }
+
+    RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, proto, nfixed, 0));
     if (!shape)
         return false;
 
+    MOZ_ASSERT_IF(layout.isArray(), !shape->isEmptyShape() && shape->slotSpan() == 0);
+
+    // Add shapes for each property, if this is for a plain object.
     for (size_t i = 0; i < layout.properties().length(); i++) {
         const UnboxedLayout::Property& property = layout.properties()[i];
 
         StackShape unrootedChild(shape->base()->unowned(), NameToId(property.name), i,
                                  JSPROP_ENUMERATE, 0);
         RootedGeneric<StackShape*> child(cx, &unrootedChild);
         shape = cx->compartment()->propertyTree.getChild(cx, shape, *child);
         if (!shape)
             return false;
     }
 
     ObjectGroup* nativeGroup =
-        ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto,
+        ObjectGroupCompartment::makeGroup(cx, clasp, proto,
                                           group->flags() & OBJECT_FLAG_DYNAMIC_MASK);
     if (!nativeGroup)
         return false;
 
     // Propagate all property types from the old group to the new group.
-    for (size_t i = 0; i < layout.properties().length(); i++) {
-        const UnboxedLayout::Property& property = layout.properties()[i];
-        jsid id = NameToId(property.name);
+    if (layout.isArray()) {
+        if (!PropagatePropertyTypes(cx, JSID_VOID, group, nativeGroup))
+            return false;
+    } else {
+        for (size_t i = 0; i < layout.properties().length(); i++) {
+            const UnboxedLayout::Property& property = layout.properties()[i];
+            jsid id = NameToId(property.name);
+            if (!PropagatePropertyTypes(cx, id, group, nativeGroup))
+                return false;
 
-        HeapTypeSet* typeProperty = group->maybeGetProperty(id);
-        TypeSet::TypeList types;
-        if (!typeProperty->enumerateTypes(&types))
-            return false;
-        MOZ_ASSERT(!types.empty());
-        for (size_t j = 0; j < types.length(); j++)
-            AddTypePropertyId(cx, nativeGroup, nullptr, id, types[j]);
-        HeapTypeSet* nativeProperty = nativeGroup->maybeGetProperty(id);
-        if (nativeProperty->canSetDefinite(i))
-            nativeProperty->setDefinite(i);
+            HeapTypeSet* nativeProperty = nativeGroup->maybeGetProperty(id);
+            if (nativeProperty->canSetDefinite(i))
+                nativeProperty->setDefinite(i);
+        }
     }
 
     layout.nativeGroup_ = nativeGroup;
     layout.nativeShape_ = shape;
     layout.replacementNewGroup_ = replacementNewGroup;
 
     nativeGroup->setOriginalUnboxedGroup(group);
 
@@ -814,17 +858,21 @@ UnboxedPlainObject::obj_getOwnPropertyDe
         desc.setAttributes(JSPROP_ENUMERATE);
         desc.object().set(obj);
         return true;
     }
 
     if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
         if (expando->containsShapeOrElement(cx, id)) {
             RootedObject nexpando(cx, expando);
-            return GetOwnPropertyDescriptor(cx, nexpando, id, desc);
+            if (!GetOwnPropertyDescriptor(cx, nexpando, id, desc))
+                return false;
+            if (desc.object() == nexpando)
+                desc.object().set(obj);
+            return true;
         }
     }
 
     desc.object().set(nullptr);
     return true;
 }
 
 /* static */ bool
@@ -914,29 +962,611 @@ const Class UnboxedPlainObject::class_ =
         nullptr,   /* No unwatch needed, as watch() converts the object to native */
         nullptr,   /* getElements */
         UnboxedPlainObject::obj_enumerate,
         nullptr, /* thisObject */
     }
 };
 
 /////////////////////////////////////////////////////////////////////
+// UnboxedArrayObject
+/////////////////////////////////////////////////////////////////////
+
+/* static */ bool
+UnboxedArrayObject::convertToNative(JSContext* cx, JSObject* obj)
+{
+    const UnboxedLayout& layout = obj->as<UnboxedArrayObject>().layout();
+
+    if (!layout.nativeGroup()) {
+        if (!UnboxedLayout::makeNativeGroup(cx, obj->group()))
+            return false;
+    }
+
+    size_t length = obj->as<UnboxedArrayObject>().length();
+    size_t initlen = obj->as<UnboxedArrayObject>().initializedLength();
+
+    AutoValueVector values(cx);
+    for (size_t i = 0; i < initlen; i++) {
+        if (!values.append(obj->as<UnboxedArrayObject>().getElement(i)))
+            return false;
+    }
+
+    obj->setGroup(layout.nativeGroup());
+
+    ArrayObject* aobj = &obj->as<ArrayObject>();
+    aobj->setLastPropertyMakeNative(cx, layout.nativeShape());
+
+    // Make sure there is at least one element, so that this array does not
+    // use emptyObjectElements.
+    if (!aobj->ensureElements(cx, Max<size_t>(initlen, 1)))
+        return false;
+
+    MOZ_ASSERT(!aobj->getDenseInitializedLength());
+    aobj->setDenseInitializedLength(initlen);
+    aobj->initDenseElements(0, values.begin(), initlen);
+    aobj->setLengthInt32(length);
+
+    return true;
+}
+
+/* static */ UnboxedArrayObject*
+UnboxedArrayObject::create(ExclusiveContext* cx, HandleObjectGroup group, uint32_t length,
+                           NewObjectKind newKind)
+{
+    MOZ_ASSERT(length <= MaximumCapacity);
+
+    MOZ_ASSERT(group->clasp() == &class_);
+    uint32_t elementSize = UnboxedTypeSize(group->unboxedLayout().elementType());
+    uint32_t nbytes = offsetOfInlineElements() + elementSize * length;
+
+    UnboxedArrayObject* res;
+    if (nbytes <= JSObject::MAX_BYTE_SIZE) {
+        gc::AllocKind allocKind = gc::GetGCObjectKindForBytes(nbytes);
+
+        // If there was no provided length information, pick an allocation kind
+        // to accommodate small arrays (as is done for normal native arrays).
+        if (length == 0)
+            allocKind = gc::AllocKind::OBJECT8;
+
+        res = NewObjectWithGroup<UnboxedArrayObject>(cx, group, allocKind, newKind);
+        if (!res)
+            return nullptr;
+        res->setInlineElements();
+
+        size_t capacity = (GetGCKindBytes(allocKind) - offsetOfInlineElements()) / elementSize;
+        res->setCapacityIndex(exactCapacityIndex(capacity));
+    } else {
+        UniquePtr<uint8_t[], JS::FreePolicy> elements(
+            cx->zone()->pod_malloc<uint8_t>(length * elementSize));
+        if (!elements)
+            return nullptr;
+
+        res = NewObjectWithGroup<UnboxedArrayObject>(cx, group, gc::AllocKind::OBJECT0, newKind);
+        if (!res)
+            return nullptr;
+
+        res->elements_ = elements.release();
+        res->setCapacityIndex(CapacityMatchesLengthIndex);
+    }
+
+    res->setLength(length);
+    res->setInitializedLength(0);
+    return res;
+}
+
+bool
+UnboxedArrayObject::setElement(ExclusiveContext* cx, size_t index, const Value& v)
+{
+    MOZ_ASSERT(index < initializedLength());
+    uint8_t* p = elements() + index * elementSize();
+    return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v);
+}
+
+bool
+UnboxedArrayObject::initElement(ExclusiveContext* cx, size_t index, const Value& v)
+{
+    MOZ_ASSERT(index < initializedLength());
+    uint8_t* p = elements() + index * elementSize();
+    if (UnboxedTypeNeedsPreBarrier(elementType()))
+        *reinterpret_cast<void**>(p) = nullptr;
+    return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v);
+}
+
+bool
+UnboxedArrayObject::appendElementNoTypeChange(ExclusiveContext* cx, size_t index, const Value& v)
+{
+    // The caller should check these preconditions, and ensure that writing v
+    // to this object will not change element type information.
+    MOZ_ASSERT(index == initializedLength());
+    MOZ_ASSERT(index < UnboxedArrayObject::MaximumCapacity);
+
+    if (initializedLength() == capacity()) {
+        if (!growElements(cx, index + 1))
+            return false;
+    }
+
+    setInitializedLength(index + 1);
+    JS_ALWAYS_TRUE(initElement(cx, index, v));
+    if (length() <= index)
+        setLength(index + 1);
+
+    return true;
+}
+
+Value
+UnboxedArrayObject::getElement(size_t index)
+{
+    MOZ_ASSERT(index < initializedLength());
+    uint8_t* p = elements() + index * elementSize();
+    return GetUnboxedValue(p, elementType());
+}
+
+/* static */ void
+UnboxedArrayObject::trace(JSTracer* trc, JSObject* obj)
+{
+    JSValueType type = obj->as<UnboxedArrayObject>().elementType();
+    if (!UnboxedTypeNeedsPreBarrier(type))
+        return;
+
+    MOZ_ASSERT(obj->as<UnboxedArrayObject>().elementSize() == sizeof(uintptr_t));
+    size_t initlen = obj->as<UnboxedArrayObject>().initializedLength();
+    void** elements = reinterpret_cast<void**>(obj->as<UnboxedArrayObject>().elements());
+
+    switch (type) {
+      case JSVAL_TYPE_OBJECT:
+        for (size_t i = 0; i < initlen; i++) {
+            HeapPtrObject* heap = reinterpret_cast<HeapPtrObject*>(elements + i);
+            if (*heap)
+                TraceEdge(trc, heap, "unboxed_object");
+        }
+        break;
+
+      case JSVAL_TYPE_STRING:
+        for (size_t i = 0; i < initlen; i++) {
+            HeapPtrString* heap = reinterpret_cast<HeapPtrString*>(elements + i);
+            TraceEdge(trc, heap, "unboxed_string");
+        }
+        break;
+
+      default:
+        MOZ_CRASH();
+    }
+}
+
+/* static */ void
+UnboxedArrayObject::objectMoved(JSObject* obj, const JSObject* old)
+{
+    UnboxedArrayObject& dst = obj->as<UnboxedArrayObject>();
+    const UnboxedArrayObject& src = old->as<UnboxedArrayObject>();
+
+    // Fix up possible inline data pointer.
+    if (src.hasInlineElements())
+        dst.setInlineElements();
+}
+
+/* static */ void
+UnboxedArrayObject::finalize(FreeOp* fop, JSObject* obj)
+{
+    if (!obj->as<UnboxedArrayObject>().hasInlineElements())
+        js_free(obj->as<UnboxedArrayObject>().elements());
+}
+
+// Possible capacities for unboxed arrays. Some of these capacities might seem
+// a little weird, but were chosen to allow the inline data of objects of each
+// size to be fully utilized for arrays of the various types on both 32 bit and
+// 64 bit platforms.
+/* static */ const uint32_t
+UnboxedArrayObject::CapacityArray[] = {
+    UINT32_MAX, // For CapacityMatchesLengthIndex.
+    0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 13, 16, 17, 18, 20, 24, 26, 32, 34, 36, 48, 52, 64, 68,
+    128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288,
+    1048576, 2097152, 3145728, 4194304, 5242880, 6291456, 7340032, 8388608, 9437184, 11534336,
+    13631488, 15728640, 17825792, 20971520, 24117248, 27262976, 31457280, 35651584, 40894464,
+    46137344, 52428800, 59768832, MaximumCapacity
+};
+
+static const uint32_t
+Pow2CapacityIndexes[] = {
+    1,  // 1
+    2,  // 2
+    4,  // 4
+    8,  // 8
+    13, // 16
+    19, // 32
+    24, // 64
+    26, // 128
+    27, // 256
+    28, // 512
+    29, // 1024
+    30, // 2048
+    31, // 4096
+    32, // 8192
+    33, // 16384
+    34, // 32768
+    35, // 65536
+    36, // 131072
+    37, // 262144
+    38, // 524288
+    39  // 1048576
+};
+
+static const uint32_t MebiCapacityIndex = 39;
+
+/* static */ uint32_t
+UnboxedArrayObject::chooseCapacityIndex(uint32_t capacity, uint32_t length)
+{
+    // Note: the structure and behavior of this method follow along with
+    // NativeObject::goodAllocated. Changes to the allocation strategy in one
+    // should generally be matched by the other.
+
+    // Make sure we have enough space to store all possible values for the capacity index.
+    MOZ_ASSERT(mozilla::ArrayLength(CapacityArray) - 1 <= CapacityMask >> CapacityShift);
+
+    // The caller should have ensured the capacity is possible for an unboxed array.
+    MOZ_ASSERT(capacity <= MaximumCapacity);
+
+    static const uint32_t Mebi = 1024 * 1024;
+
+    if (capacity <= Mebi) {
+        capacity = mozilla::RoundUpPow2(capacity);
+
+        // When the required capacity is close to the array length, then round
+        // up to the array length itself, as for NativeObject.
+        if (length >= capacity && capacity > (length / 3) * 2)
+            return CapacityMatchesLengthIndex;
+
+        if (capacity < MinimumDynamicCapacity)
+            capacity = MinimumDynamicCapacity;
+
+        uint32_t bit = mozilla::FloorLog2Size(capacity);
+        MOZ_ASSERT(capacity == uint32_t(1 << bit));
+        MOZ_ASSERT(bit <= 20);
+        MOZ_ASSERT(mozilla::ArrayLength(Pow2CapacityIndexes) == 21);
+
+        uint32_t index = Pow2CapacityIndexes[bit];
+        MOZ_ASSERT(CapacityArray[index] == capacity);
+
+        return index;
+    }
+
+    MOZ_ASSERT(CapacityArray[MebiCapacityIndex] == Mebi);
+
+    for (uint32_t i = MebiCapacityIndex + 1;; i++) {
+        if (CapacityArray[i] >= capacity)
+            return i;
+    }
+
+    MOZ_CRASH("Invalid capacity");
+}
+
+/* static */ uint32_t
+UnboxedArrayObject::exactCapacityIndex(uint32_t capacity)
+{
+    for (size_t i = CapacityMatchesLengthIndex + 1; i < ArrayLength(CapacityArray); i++) {
+        if (CapacityArray[i] == capacity)
+            return i;
+    }
+    MOZ_CRASH();
+}
+
+bool
+UnboxedArrayObject::growElements(ExclusiveContext* cx, size_t cap)
+{
+    // The caller should have checked if this capacity is possible for an
+    // unboxed array, so the only way this call can fail is from OOM.
+    MOZ_ASSERT(cap <= MaximumCapacity);
+
+    uint32_t oldCapacity = capacity();
+    uint32_t newCapacityIndex = chooseCapacityIndex(cap, length());
+    uint32_t newCapacity = computeCapacity(newCapacityIndex, length());
+
+    MOZ_ASSERT(oldCapacity < cap);
+    MOZ_ASSERT(cap <= newCapacity);
+
+    // The allocation size computation below cannot have integer overflows.
+    JS_STATIC_ASSERT(MaximumCapacity < UINT32_MAX / sizeof(double));
+
+    uint8_t* newElements;
+    if (hasInlineElements()) {
+        newElements = cx->zone()->pod_malloc<uint8_t>(newCapacity * elementSize());
+        if (!newElements)
+            return false;
+        js_memcpy(newElements, elements(), initializedLength() * elementSize());
+    } else {
+        newElements = cx->zone()->pod_realloc<uint8_t>(elements(), oldCapacity * elementSize(),
+                                                       newCapacity * elementSize());
+        if (!newElements)
+            return false;
+    }
+
+    elements_ = newElements;
+    setCapacityIndex(newCapacityIndex);
+
+    return true;
+}
+
+void
+UnboxedArrayObject::shrinkElements(ExclusiveContext* cx, size_t cap)
+{
+    if (hasInlineElements())
+        return;
+
+    uint32_t oldCapacity = capacity();
+    uint32_t newCapacityIndex = chooseCapacityIndex(cap, 0);
+    uint32_t newCapacity = computeCapacity(newCapacityIndex, 0);
+
+    MOZ_ASSERT(cap < oldCapacity);
+    MOZ_ASSERT(cap <= newCapacity);
+
+    if (newCapacity >= oldCapacity)
+        return;
+
+    uint8_t* newElements =
+        cx->zone()->pod_realloc<uint8_t>(elements(), oldCapacity * elementSize(),
+                                         newCapacity * elementSize());
+    if (!newElements)
+        return;
+
+    elements_ = newElements;
+    setCapacityIndex(newCapacityIndex);
+}
+
+bool
+UnboxedArrayObject::containsProperty(JSContext* cx, jsid id)
+{
+    if (JSID_IS_INT(id) && uint32_t(JSID_TO_INT(id)) < initializedLength())
+        return true;
+    if (JSID_IS_ATOM(id) && JSID_TO_ATOM(id) == cx->names().length)
+        return true;
+    return false;
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_lookupProperty(JSContext* cx, HandleObject obj,
+                                       HandleId id, MutableHandleObject objp,
+                                       MutableHandleShape propp)
+{
+    if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+        MarkNonNativePropertyFound<CanGC>(propp);
+        objp.set(obj);
+        return true;
+    }
+
+    RootedObject proto(cx, obj->getProto());
+    if (!proto) {
+        objp.set(nullptr);
+        propp.set(nullptr);
+        return true;
+    }
+
+    return LookupProperty(cx, proto, id, objp, propp);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
+                                       Handle<JSPropertyDescriptor> desc,
+                                       ObjectOpResult& result)
+{
+    if (JSID_IS_INT(id) && !desc.getter() && !desc.setter() && desc.attributes() == JSPROP_ENUMERATE) {
+        UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>();
+
+        uint32_t index = JSID_TO_INT(id);
+        if (index < nobj->initializedLength()) {
+            if (nobj->setElement(cx, index, desc.value()))
+                return result.succeed();
+        } else if (index == nobj->initializedLength() && index < MaximumCapacity) {
+            if (nobj->initializedLength() == nobj->capacity()) {
+                if (!nobj->growElements(cx, index + 1))
+                    return false;
+            }
+            nobj->setInitializedLength(index + 1);
+            if (nobj->initElement(cx, index, desc.value())) {
+                if (nobj->length() <= index)
+                    nobj->setLength(index + 1);
+                return result.succeed();
+            }
+            nobj->setInitializedLength(index);
+        }
+    }
+
+    if (!convertToNative(cx, obj))
+        return false;
+
+    return DefineProperty(cx, obj, id, desc, result);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
+{
+    if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+        *foundp = true;
+        return true;
+    }
+
+    RootedObject proto(cx, obj->getProto());
+    if (!proto) {
+        *foundp = false;
+        return true;
+    }
+
+    return HasProperty(cx, proto, id, foundp);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleObject receiver,
+                                    HandleId id, MutableHandleValue vp)
+{
+    if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+        if (JSID_IS_INT(id))
+            vp.set(obj->as<UnboxedArrayObject>().getElement(JSID_TO_INT(id)));
+        else
+            vp.set(Int32Value(obj->as<UnboxedArrayObject>().length()));
+        return true;
+    }
+
+    RootedObject proto(cx, obj->getProto());
+    if (!proto) {
+        vp.setUndefined();
+        return true;
+    }
+
+    return GetProperty(cx, proto, receiver, id, vp);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
+                                    HandleValue receiver, ObjectOpResult& result)
+{
+    if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+        if (receiver.isObject() && obj == &receiver.toObject()) {
+            if (JSID_IS_INT(id)) {
+                if (obj->as<UnboxedArrayObject>().setElement(cx, JSID_TO_INT(id), v))
+                    return result.succeed();
+            } else {
+                uint32_t len;
+                if (!CanonicalizeArrayLengthValue(cx, v, &len))
+                    return false;
+                if (len <= INT32_MAX) {
+                    UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>();
+                    nobj->setLength(len);
+                    if (len < nobj->initializedLength()) {
+                        nobj->setInitializedLength(len);
+                        nobj->shrinkElements(cx, len);
+                    }
+                }
+            }
+
+            if (!convertToNative(cx, obj))
+                return false;
+            return SetProperty(cx, obj, id, v, receiver, result);
+        }
+
+        return SetPropertyByDefining(cx, obj, id, v, receiver, false, result);
+    }
+
+    return SetPropertyOnProto(cx, obj, id, v, receiver, result);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
+                                                 MutableHandle<JSPropertyDescriptor> desc)
+{
+    if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+        if (JSID_IS_INT(id)) {
+            desc.value().set(obj->as<UnboxedArrayObject>().getElement(JSID_TO_INT(id)));
+            desc.setAttributes(JSPROP_ENUMERATE);
+        } else {
+            desc.value().set(Int32Value(obj->as<UnboxedArrayObject>().length()));
+            desc.setAttributes(JSPROP_PERMANENT);
+        }
+        desc.object().set(obj);
+        return true;
+    }
+
+    desc.object().set(nullptr);
+    return true;
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
+                                       ObjectOpResult& result)
+{
+    if (!convertToNative(cx, obj))
+        return false;
+    return DeleteProperty(cx, obj, id, result);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_watch(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable)
+{
+    if (!convertToNative(cx, obj))
+        return false;
+    return WatchProperty(cx, obj, id, callable);
+}
+
+/* static */ bool
+UnboxedArrayObject::obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties)
+{
+    for (size_t i = 0; i < obj->as<UnboxedArrayObject>().initializedLength(); i++) {
+        if (!properties.append(INT_TO_JSID(i)))
+            return false;
+    }
+    return properties.append(NameToId(cx->names().length));
+}
+
+const Class UnboxedArrayObject::class_ = {
+    "Array",
+    Class::NON_NATIVE |
+    JSCLASS_IMPLEMENTS_BARRIERS |
+    0 /* FIXME using this flag can severely hurt performance: JSCLASS_BACKGROUND_FINALIZE */,
+    nullptr,        /* addProperty */
+    nullptr,        /* delProperty */
+    nullptr,        /* getProperty */
+    nullptr,        /* setProperty */
+    nullptr,        /* enumerate   */
+    nullptr,        /* resolve     */
+    nullptr,        /* mayResolve  */
+    nullptr,        /* convert     */
+    UnboxedArrayObject::finalize,
+    nullptr,        /* call        */
+    nullptr,        /* hasInstance */
+    nullptr,        /* construct   */
+    UnboxedArrayObject::trace,
+    JS_NULL_CLASS_SPEC,
+    {
+        nullptr,    /* outerObject */
+        nullptr,    /* innerObject */
+        false,      /* isWrappedNative */
+        nullptr,    /* weakmapKeyDelegateOp */
+        UnboxedArrayObject::objectMoved
+    },
+    {
+        UnboxedArrayObject::obj_lookupProperty,
+        UnboxedArrayObject::obj_defineProperty,
+        UnboxedArrayObject::obj_hasProperty,
+        UnboxedArrayObject::obj_getProperty,
+        UnboxedArrayObject::obj_setProperty,
+        UnboxedArrayObject::obj_getOwnPropertyDescriptor,
+        UnboxedArrayObject::obj_deleteProperty,
+        UnboxedArrayObject::obj_watch,
+        nullptr,   /* No unwatch needed, as watch() converts the object to native */
+        nullptr,   /* getElements */
+        UnboxedArrayObject::obj_enumerate,
+        nullptr,   /* thisObject */
+    }
+};
+
+/////////////////////////////////////////////////////////////////////
 // API
 /////////////////////////////////////////////////////////////////////
 
 static bool
 UnboxedTypeIncludes(JSValueType supertype, JSValueType subtype)
 {
     if (supertype == JSVAL_TYPE_DOUBLE && subtype == JSVAL_TYPE_INT32)
         return true;
     if (supertype == JSVAL_TYPE_OBJECT && subtype == JSVAL_TYPE_NULL)
         return true;
     return false;
 }
 
+static bool
+CombineUnboxedTypes(const Value& value, JSValueType* existing)
+{
+    JSValueType type = value.isDouble() ? JSVAL_TYPE_DOUBLE : value.extractNonDoubleType();
+
+    if (*existing == JSVAL_TYPE_MAGIC || *existing == type || UnboxedTypeIncludes(type, *existing)) {
+        *existing = type;
+        return true;
+    }
+    if (UnboxedTypeIncludes(*existing, type))
+        return true;
+    return false;
+}
+
 // Return whether the property names and types in layout are a subset of the
 // specified vector.
 static bool
 PropertiesAreSuperset(const UnboxedLayout::PropertyVector& properties, UnboxedLayout* layout)
 {
     for (size_t i = 0; i < layout->properties().length(); i++) {
         const UnboxedLayout::Property& layoutProperty = layout->properties()[i];
         bool found = false;
@@ -947,93 +1577,77 @@ PropertiesAreSuperset(const UnboxedLayou
             }
         }
         if (!found)
             return false;
     }
     return true;
 }
 
-bool
-js::TryConvertToUnboxedLayout(ExclusiveContext* cx, Shape* templateShape,
-                              ObjectGroup* group, PreliminaryObjectArray* objects)
+static bool
+CombinePlainObjectProperties(PlainObject* obj, Shape* templateShape,
+                             UnboxedLayout::PropertyVector& properties)
 {
-    // Unboxed objects are nightly only for now. The getenv() call will be
-    // removed when they are on by default. See bug 1153266.
-#ifdef NIGHTLY_BUILD
-    if (!getenv("JS_OPTION_USE_UNBOXED_OBJECTS")) {
-        if (!templateShape->runtimeFromAnyThread()->options().unboxedObjects())
-            return true;
-    }
-#else
-    return true;
-#endif
-
-    if (templateShape->runtimeFromAnyThread()->isSelfHostingGlobal(cx->global()))
-        return true;
-
-    if (templateShape->slotSpan() == 0)
-        return true;
-
-    UnboxedLayout::PropertyVector properties;
-    if (!properties.appendN(UnboxedLayout::Property(), templateShape->slotSpan()))
-        return false;
-
-    size_t objectCount = 0;
-    for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
-        JSObject* obj = objects->get(i);
-        if (!obj)
-            continue;
-
-        objectCount++;
+    // All preliminary objects must have been created with enough space to
+    // fill in their unboxed data inline. This is ensured either by using
+    // the largest allocation kind (which limits the maximum size of an
+    // unboxed object), or by using an allocation kind that covers all
+    // properties in the template, as the space used by unboxed properties
+    // is less than or equal to that used by boxed properties.
+    MOZ_ASSERT(gc::GetGCKindSlots(obj->asTenured().getAllocKind()) >=
+               Min(NativeObject::MAX_FIXED_SLOTS, templateShape->slotSpan()));
 
-        // All preliminary objects must have been created with enough space to
-        // fill in their unboxed data inline. This is ensured either by using
-        // the largest allocation kind (which limits the maximum size of an
-        // unboxed object), or by using an allocation kind that covers all
-        // properties in the template, as the space used by unboxed properties
-        // less than or equal to that used by boxed properties.
-        MOZ_ASSERT(gc::GetGCKindSlots(obj->asTenured().getAllocKind()) >=
-                   Min(NativeObject::MAX_FIXED_SLOTS, templateShape->slotSpan()));
-
-        if (obj->as<PlainObject>().lastProperty() != templateShape ||
-            obj->as<PlainObject>().hasDynamicElements())
-        {
-            // Only use an unboxed representation if all created objects match
-            // the template shape exactly.
-            return true;
-        }
-
-        for (size_t i = 0; i < templateShape->slotSpan(); i++) {
-            Value val = obj->as<PlainObject>().getSlot(i);
-
-            JSValueType& existing = properties[i].type;
-            JSValueType type = val.isDouble() ? JSVAL_TYPE_DOUBLE : val.extractNonDoubleType();
-
-            if (existing == JSVAL_TYPE_MAGIC || existing == type || UnboxedTypeIncludes(type, existing))
-                existing = type;
-            else if (!UnboxedTypeIncludes(existing, type))
-                return true;
-        }
-    }
-
-    if (objectCount <= 1) {
-        // If only one of the objects has been created, it is more likely to
-        // have new properties added later.
-        return true;
+    if (obj->lastProperty() != templateShape || obj->hasDynamicElements()) {
+        // Only use an unboxed representation if all created objects match
+        // the template shape exactly.
+        return false;
     }
 
     for (size_t i = 0; i < templateShape->slotSpan(); i++) {
-        // We can't use an unboxed representation if e.g. all the objects have
-        // a null value for one of the properties, as we can't decide what type
-        // it is supposed to have.
-        if (UnboxedTypeSize(properties[i].type) == 0)
-            return true;
+        Value val = obj->getSlot(i);
+
+        JSValueType& existing = properties[i].type;
+        if (!CombineUnboxedTypes(val, &existing))
+            return false;
     }
 
+    return true;
+}
+
+static bool
+CombineArrayObjectElements(ExclusiveContext* cx, ArrayObject* obj, JSValueType* elementType)
+{
+    if (obj->inDictionaryMode() ||
+        obj->lastProperty()->propid() != AtomToId(cx->names().length) ||
+        !obj->lastProperty()->previous()->isEmptyShape() ||
+        !obj->getDenseInitializedLength())
+    {
+        // Only use an unboxed representation if the object has at
+        // least one element, and no properties.
+        return false;
+    }
+
+    for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) {
+        Value val = obj->getDenseElement(i);
+
+        // For now, unboxed arrays cannot have holes.
+        if (val.isMagic(JS_ELEMENTS_HOLE))
+            return false;
+
+        if (!CombineUnboxedTypes(val, elementType))
+            return false;
+    }
+
+    return true;
+}
+
+static size_t
+ComputePlainObjectLayout(ExclusiveContext* cx, Shape* templateShape,
+                         UnboxedLayout::PropertyVector& properties)
+{
     // Fill in the names for all the object's properties.
     for (Shape::Range<NoGC> r(templateShape); !r.empty(); r.popFront()) {
         size_t slot = r.front().slot();
         MOZ_ASSERT(!properties[slot].name);
         properties[slot].name = JSID_TO_ATOM(r.front().propid())->asPropertyName();
     }
 
     // Fill in all the unboxed object's property offsets.
@@ -1083,108 +1697,274 @@ js::TryConvertToUnboxedLayout(ExclusiveC
             if (UnboxedTypeSize(type) == size) {
                 offset = JS_ROUNDUP(offset, size);
                 properties[j].offset = offset;
                 offset += size;
             }
         }
     }
 
-    // The entire object must be allocatable inline.
-    if (sizeof(JSObject) + offset > JSObject::MAX_BYTE_SIZE)
-        return true;
+    // The final offset is the amount of data needed by the object.
+    return offset;
+}
 
-    UniquePtr<UnboxedLayout, JS::DeletePolicy<UnboxedLayout> > layout;
-    layout.reset(group->zone()->new_<UnboxedLayout>(properties, offset));
-    if (!layout)
-        return false;
-
-    cx->compartment()->unboxedLayouts.insertFront(layout.get());
-
+static bool
+SetLayoutTraceList(ExclusiveContext* cx, UnboxedLayout* layout)
+{
     // Figure out the offsets of any objects or string properties.
     Vector<int32_t, 8, SystemAllocPolicy> objectOffsets, stringOffsets;
-    for (size_t i = 0; i < templateShape->slotSpan(); i++) {
-        MOZ_ASSERT(properties[i].offset != UINT32_MAX);
-        JSValueType type = properties[i].type;
-        if (type == JSVAL_TYPE_OBJECT) {
-            if (!objectOffsets.append(properties[i].offset))
+    for (size_t i = 0; i < layout->properties().length(); i++) {
+        const UnboxedLayout::Property& property = layout->properties()[i];
+        MOZ_ASSERT(property.offset != UINT32_MAX);
+        if (property.type == JSVAL_TYPE_OBJECT) {
+            if (!objectOffsets.append(property.offset))
                 return false;
-        } else if (type == JSVAL_TYPE_STRING) {
-            if (!stringOffsets.append(properties[i].offset))
+        } else if (property.type == JSVAL_TYPE_STRING) {
+            if (!stringOffsets.append(property.offset))
                 return false;
         }
     }
 
     // Construct the layout's trace list.
     if (!objectOffsets.empty() || !stringOffsets.empty()) {
         Vector<int32_t, 8, SystemAllocPolicy> entries;
         if (!entries.appendAll(stringOffsets) ||
             !entries.append(-1) ||
             !entries.appendAll(objectOffsets) ||
             !entries.append(-1) ||
             !entries.append(-1))
         {
             return false;
         }
-        int32_t* traceList = group->zone()->pod_malloc<int32_t>(entries.length());
+        int32_t* traceList = cx->zone()->pod_malloc<int32_t>(entries.length());
         if (!traceList)
             return false;
         PodCopy(traceList, entries.begin(), entries.length());
         layout->setTraceList(traceList);
     }
 
+    return true;
+}
+
+static inline Value
+NextValue(const AutoValueVector& values, size_t* valueCursor)
+{
+    return values[(*valueCursor)++];
+}
+
+static bool
+GetValuesFromPreliminaryArrayObject(ArrayObject* obj, AutoValueVector& values)
+{
+    if (!values.append(Int32Value(obj->length())))
+        return false;
+    if (!values.append(Int32Value(obj->getDenseInitializedLength())))
+        return false;
+    for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) {
+        if (!values.append(obj->getDenseElement(i)))
+            return false;
+    }
+    return true;
+}
+
+void
+UnboxedArrayObject::fillAfterConvert(ExclusiveContext* cx,
+                                     const AutoValueVector& values, size_t* valueCursor)
+{
+    MOZ_ASSERT(CapacityArray[1] == 0);
+    setCapacityIndex(1);
+    setInitializedLength(0);
+    setInlineElements();
+
+    setLength(NextValue(values, valueCursor).toInt32());
+
+    int32_t initlen = NextValue(values, valueCursor).toInt32();
+
+    if (!growElements(cx, initlen))
+        CrashAtUnhandlableOOM("UnboxedArrayObject::fillAfterConvert");
+    setInitializedLength(initlen);
+
+    for (size_t i = 0; i < size_t(initlen); i++)
+        JS_ALWAYS_TRUE(initElement(cx, i, NextValue(values, valueCursor)));
+}
+
+static bool
+GetValuesFromPreliminaryPlainObject(PlainObject* obj, AutoValueVector& values)
+{
+    for (size_t i = 0; i < obj->slotSpan(); i++) {
+        if (!values.append(obj->getSlot(i)))
+            return false;
+    }
+    return true;
+}
+
+void
+UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx,
+                                     const AutoValueVector& values, size_t* valueCursor)
+{
+    initExpando();
+    memset(data(), 0, layout().size());
+    for (size_t i = 0; i < layout().properties().length(); i++)
+        JS_ALWAYS_TRUE(setValue(cx, layout().properties()[i], NextValue(values, valueCursor)));
+}
+
+bool
+js::TryConvertToUnboxedLayout(ExclusiveContext* cx, Shape* templateShape,
+                              ObjectGroup* group, PreliminaryObjectArray* objects)
+{
+    // Unboxed objects are nightly only for now. The getenv() call will be
+    // removed when they are on by default. See bug 1153266.
+#ifdef NIGHTLY_BUILD
+    if (templateShape) {
+        if (!getenv("JS_OPTION_USE_UNBOXED_OBJECTS")) {
+            if (!group->runtimeFromAnyThread()->options().unboxedObjects())
+                return true;
+        }
+    } else {
+        if (!group->runtimeFromAnyThread()->options().unboxedArrays())
+            return true;
+    }
+#else
+    return true;
+#endif
+
+    MOZ_ASSERT_IF(templateShape, !templateShape->getObjectFlags());
+
+    bool isArray = !templateShape;
+
+    const JS::RuntimeOptions& options = group->runtimeFromAnyThread()->options();
+    if (isArray ? !options.unboxedArrays() : !options.unboxedObjects())
+        return true;
+
+    if (group->runtimeFromAnyThread()->isSelfHostingGlobal(cx->global()))
+        return true;
+
+    if (!isArray && templateShape->slotSpan() == 0)
+        return true;
+
+    UnboxedLayout::PropertyVector properties;
+    if (!isArray) {
+        if (!properties.appendN(UnboxedLayout::Property(), templateShape->slotSpan()))
+            return false;
+    }
+    JSValueType elementType = JSVAL_TYPE_MAGIC;
+
+    size_t objectCount = 0;
+    for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+        JSObject* obj = objects->get(i);
+        if (!obj)
+            continue;
+
+        objectCount++;
+
+        if (isArray) {
+            if (!CombineArrayObjectElements(cx, &obj->as<ArrayObject>(), &elementType))
+                return true;
+        } else {
+            if (!CombinePlainObjectProperties(&obj->as<PlainObject>(), templateShape, properties))
+                return true;
+        }
+    }
+
+    size_t layoutSize = 0;
+    if (isArray) {
+        // Don't use an unboxed representation if we couldn't determine an
+        // element type for the objects.
+        if (UnboxedTypeSize(elementType) == 0)
+            return true;
+    } else {
+        if (objectCount <= 1) {
+            // If only one of the objects has been created, it is more likely
+            // to have new properties added later. This heuristic is not used
+            // for array objects, where we might want an unboxed representation
+            // even if there is only one large array.
+            return true;
+        }
+
+        for (size_t i = 0; i < templateShape->slotSpan(); i++) {
+            // We can't use an unboxed representation if e.g. all the objects have
+            // a null value for one of the properties, as we can't decide what type
+            // it is supposed to have.
+            if (UnboxedTypeSize(properties[i].type) == 0)
+                return true;
+        }
+
+        layoutSize = ComputePlainObjectLayout(cx, templateShape, properties);
+
+        // The entire object must be allocatable inline.
+        if (sizeof(JSObject) + layoutSize > JSObject::MAX_BYTE_SIZE)
+            return true;
+    }
+
+    UniquePtr<UnboxedLayout, JS::DeletePolicy<UnboxedLayout> > layout;
+    layout.reset(group->zone()->new_<UnboxedLayout>());
+    if (!layout)
+        return false;
+
+    if (isArray) {
+        layout->initArray(elementType);
+    } else {
+        if (!layout->initProperties(properties, layoutSize))
+            return false;
+
+        // The unboxedLayouts list only tracks layouts for plain objects.
+        cx->compartment()->unboxedLayouts.insertFront(layout.get());
+
+        if (!SetLayoutTraceList(cx, layout.get()))
+            return false;
+    }
+
     // We've determined that all the preliminary objects can use the new layout
-    // just constructed, so convert the existing group to be an
-    // UnboxedPlainObject rather than a PlainObject, and update the preliminary
-    // objects to use the new layout. Do the fallible stuff first before
-    // modifying any objects.
+    // just constructed, so convert the existing group to use the unboxed class,
+    // and update the preliminary objects to use the new layout. Do the
+    // fallible stuff first before modifying any objects.
 
     // Get an empty shape which we can use for the preliminary objects.
-    Shape* newShape = EmptyShape::getInitialShape(cx, &UnboxedPlainObject::class_,
-                                                  group->proto(),
-                                                  templateShape->getObjectFlags());
+    const Class* clasp = isArray ? &UnboxedArrayObject::class_ : &UnboxedPlainObject::class_;
+    Shape* newShape = EmptyShape::getInitialShape(cx, clasp, group->proto(), 0);
     if (!newShape) {
         cx->recoverFromOutOfMemory();
         return false;
     }
 
-    // Accumulate a list of all the properties in each preliminary object, and
+    // Accumulate a list of all the values in each preliminary object, and
     // update their shapes.
-    Vector<Value, 0, SystemAllocPolicy> values;
-    if (!values.reserve(objectCount * templateShape->slotSpan()))
-        return false;
+    AutoValueVector values(cx);
     for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
-        if (!objects->get(i))
+        JSObject* obj = objects->get(i);
+        if (!obj)
             continue;
 
-        RootedNativeObject obj(cx, &objects->get(i)->as<NativeObject>());
-        for (size_t j = 0; j < templateShape->slotSpan(); j++)
-            values.infallibleAppend(obj->getSlot(j));
-
-        // Clear the object to remove any dynamically allocated information.
-        NativeObject::clear(cx, obj);
-
-        obj->setLastPropertyMakeNonNative(newShape);
+        if (isArray) {
+            if (!GetValuesFromPreliminaryArrayObject(&obj->as<ArrayObject>(), values))
+                return false;
+        } else {
+            if (!GetValuesFromPreliminaryPlainObject(&obj->as<PlainObject>(), values))
+                return false;
+        }
     }
 
     if (TypeNewScript* newScript = group->newScript())
         layout->setNewScript(newScript);
 
-    group->setClasp(&UnboxedPlainObject::class_);
+    for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
+        if (JSObject* obj = objects->get(i))
+            obj->as<NativeObject>().setLastPropertyMakeNonNative(newShape);
+    }
+
+    group->setClasp(clasp);
     group->setUnboxedLayout(layout.get());
 
     size_t valueCursor = 0;
     for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) {
-        if (!objects->get(i))
+        JSObject* obj = objects->get(i);
+        if (!obj)
             continue;
-        UnboxedPlainObject* obj = &objects->get(i)->as<UnboxedPlainObject>();
-        obj->initExpando();
-        memset(obj->data(), 0, layout->size());
-        for (size_t j = 0; j < templateShape->slotSpan(); j++) {
-            Value v = values[valueCursor++];
-            JS_ALWAYS_TRUE(obj->setValue(cx, properties[j], v));
-        }
+
+        if (isArray)
+            obj->as<UnboxedArrayObject>().fillAfterConvert(cx, values, &valueCursor);
+        else
+            obj->as<UnboxedPlainObject>().fillAfterConvert(cx, values, &valueCursor);
     }
 
     MOZ_ASSERT(valueCursor == values.length());
     layout.release();
     return true;
 }
--- a/js/src/vm/UnboxedObject.h
+++ b/js/src/vm/UnboxedObject.h
@@ -30,79 +30,97 @@ UnboxedTypeSize(JSValueType type)
 }
 
 static inline bool
 UnboxedTypeNeedsPreBarrier(JSValueType type)
 {
     return type == JSVAL_TYPE_STRING || type == JSVAL_TYPE_OBJECT;
 }
 
-// Class describing the layout of an UnboxedPlainObject.
+// Class tracking information specific to unboxed objects.
 class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
 {
   public:
     struct Property {
         PropertyName* name;
         uint32_t offset;
         JSValueType type;
 
         Property()
           : name(nullptr), offset(UINT32_MAX), type(JSVAL_TYPE_MAGIC)
         {}
     };
 
     typedef Vector<Property, 0, SystemAllocPolicy> PropertyVector;
 
   private:
+    // If objects in this group have ever been converted to native objects,
+    // these store the corresponding native group and initial shape for such
+    // objects. Type information for this object is reflected in nativeGroup.
+    HeapPtrObjectGroup nativeGroup_;
+    HeapPtrShape nativeShape_;
+
+    // The following members are only used for unboxed plain objects.
+
     // All properties on objects with this layout, in enumeration order.
     PropertyVector properties_;
 
     // Byte size of the data for objects with this layout.
     size_t size_;
 
     // Any 'new' script information associated with this layout.
     TypeNewScript* newScript_;
 
     // List for use in tracing objects with this layout. This has the same
     // structure as the trace list on a TypeDescr.
     int32_t* traceList_;
 
-    // If objects in this group have ever been converted to native objects,
-    // these store the corresponding native group and initial shape for such
-    // objects. Type information for this object is reflected in nativeGroup.
-    HeapPtrObjectGroup nativeGroup_;
-    HeapPtrShape nativeShape_;
-
     // If nativeGroup is set and this object originally had a TypeNewScript,
     // this points to the default 'new' group which replaced this one (and
     // which might itself have been cleared since). This link is only needed to
     // keep the replacement group from being GC'ed. If it were GC'ed and a new
     // one regenerated later, that new group might have a different allocation
     // kind from this group.
     HeapPtrObjectGroup replacementNewGroup_;
 
     // If this layout has been used to construct script or JSON constant
     // objects, this code might be filled in to more quickly fill in objects
     // from an array of values.
     HeapPtrJitCode constructorCode_;
 
+    // The following members are only used for unboxed arrays.
+
+    // The type of array elements.
+    JSValueType elementType_;
+
   public:
-    UnboxedLayout(const PropertyVector& properties, size_t size)
-      : size_(size), newScript_(nullptr), traceList_(nullptr),
-        nativeGroup_(nullptr), nativeShape_(nullptr), replacementNewGroup_(nullptr),
-        constructorCode_(nullptr)
-    {
-        properties_.appendAll(properties);
+    UnboxedLayout()
+      : nativeGroup_(nullptr), nativeShape_(nullptr), size_(0), newScript_(nullptr),
+        traceList_(nullptr), replacementNewGroup_(nullptr), constructorCode_(nullptr),
+        elementType_(JSVAL_TYPE_MAGIC)
+    {}
+
+    bool initProperties(const PropertyVector& properties, size_t size) {
+        size_ = size;
+        return properties_.appendAll(properties);
+    }
+
+    void initArray(JSValueType elementType) {
+        elementType_ = elementType;
     }
 
     ~UnboxedLayout() {
         js_delete(newScript_);
         js_free(traceList_);
     }
 
+    bool isArray() {
+        return elementType_ != JSVAL_TYPE_MAGIC;
+    }
+
     void detachFromCompartment();
 
     const PropertyVector& properties() const {
         return properties_;
     }
 
     TypeNewScript* newScript() const {
         return newScript_;
@@ -147,16 +165,20 @@ class UnboxedLayout : public mozilla::Li
     jit::JitCode* constructorCode() const {
         return constructorCode_;
     }
 
     void setConstructorCode(jit::JitCode* code) {
         constructorCode_ = code;
     }
 
+    JSValueType elementType() const {
+        return elementType_;
+    }
+
     inline gc::AllocKind getAllocKind() const;
 
     void trace(JSTracer* trc);
 
     size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
     static bool makeNativeGroup(JSContext* cx, ObjectGroup* group);
     static bool makeConstructorCode(JSContext* cx, HandleObjectGroup group);
@@ -246,16 +268,19 @@ class UnboxedPlainObject : public JSObje
     Value getValue(const UnboxedLayout::Property& property);
 
     static bool convertToNative(JSContext* cx, JSObject* obj);
     static UnboxedPlainObject* create(ExclusiveContext* cx, HandleObjectGroup group,
                                       NewObjectKind newKind);
     static JSObject* createWithProperties(ExclusiveContext* cx, HandleObjectGroup group,
                                           NewObjectKind newKind, IdValuePair* properties);
 
+    void fillAfterConvert(ExclusiveContext* cx,
+                          const AutoValueVector& values, size_t* valueCursor);
+
     static void trace(JSTracer* trc, JSObject* object);
 
     static size_t offsetOfExpando() {
         return offsetof(UnboxedPlainObject, expando_);
     }
 
     static size_t offsetOfData() {
         return offsetof(UnboxedPlainObject, data_[0]);
@@ -267,14 +292,184 @@ class UnboxedPlainObject : public JSObje
 // preliminary objects and their group to the new unboxed representation.
 bool
 TryConvertToUnboxedLayout(ExclusiveContext* cx, Shape* templateShape,
                           ObjectGroup* group, PreliminaryObjectArray* objects);
 
 inline gc::AllocKind
 UnboxedLayout::getAllocKind() const
 {
+    MOZ_ASSERT(size());
     return gc::GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + size());
 }
 
+// Class for an array object using an unboxed representation.
+class UnboxedArrayObject : public JSObject
+{
+    // Elements pointer for the object.
+    uint8_t* elements_;
+
+    // The nominal array length. This always fits in an int32_t.
+    uint32_t length_;
+
+    // Value indicating the allocated capacity and initialized length of the
+    // array. The top CapacityBits bits are an index into CapacityArray, which
+    // indicates the elements capacity. The low InitializedLengthBits store the
+    // initialized length of the array.
+    uint32_t capacityIndexAndInitializedLength_;
+
+    // If the elements are inline, they will point here.
+    uint8_t inlineElements_[1];
+
+  public:
+    static const uint32_t CapacityBits = 6;
+    static const uint32_t CapacityShift = 26;
+
+    static const uint32_t CapacityMask = uint32_t(-1) << CapacityShift;
+    static const uint32_t InitializedLengthMask = (1 << CapacityShift) - 1;
+
+    static const uint32_t MaximumCapacity = InitializedLengthMask;
+    static const uint32_t MinimumDynamicCapacity = 8;
+
+    static const uint32_t CapacityArray[];
+
+    // Capacity index which indicates the array's length is also its capacity.
+    static const uint32_t CapacityMatchesLengthIndex = 0;
+
+  private:
+    static inline uint32_t computeCapacity(uint32_t index, uint32_t length) {
+        if (index == CapacityMatchesLengthIndex)
+            return length;
+        return CapacityArray[index];
+    }
+
+    static uint32_t chooseCapacityIndex(uint32_t capacity, uint32_t length);
+    static uint32_t exactCapacityIndex(uint32_t capacity);
+
+  public:
+    static const Class class_;
+
+    static bool obj_lookupProperty(JSContext* cx, HandleObject obj,
+                                   HandleId id, MutableHandleObject objp,
+                                   MutableHandleShape propp);
+
+    static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
+                                   Handle<JSPropertyDescriptor> desc,
+                                   ObjectOpResult& result);
+
+    static bool obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp);
+
+    static bool obj_getProperty(JSContext* cx, HandleObject obj, HandleObject receiver,
+                                HandleId id, MutableHandleValue vp);
+
+    static bool obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
+                                HandleValue receiver, ObjectOpResult& result);
+
+    static bool obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
+                                             MutableHandle<JSPropertyDescriptor> desc);
+
+    static bool obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
+                                   ObjectOpResult& result);
+
+    static bool obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties);
+    static bool obj_watch(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable);
+
+    const UnboxedLayout& layout() const {
+        return group()->unboxedLayout();
+    }
+
+    const UnboxedLayout& layoutDontCheckGeneration() const {
+        return group()->unboxedLayoutDontCheckGeneration();
+    }
+
+    JSValueType elementType() const {
+        return layoutDontCheckGeneration().elementType();
+    }
+
+    uint32_t elementSize() const {
+        return UnboxedTypeSize(elementType());
+    }
+
+    static bool convertToNative(JSContext* cx, JSObject* obj);
+    static UnboxedArrayObject* create(ExclusiveContext* cx, HandleObjectGroup group,
+                                      uint32_t length, NewObjectKind newKind);
+
+    void fillAfterConvert(ExclusiveContext* cx,
+                          const AutoValueVector& values, size_t* valueCursor);
+
+    static void trace(JSTracer* trc, JSObject* object);
+    static void objectMoved(JSObject* obj, const JSObject* old);
+    static void finalize(FreeOp* fop, JSObject* obj);
+
+    uint8_t* elements() {
+        return elements_;
+    }
+
+    bool hasInlineElements() const {
+        return elements_ == &inlineElements_[0];
+    }
+
+    uint32_t length() const {
+        return length_;
+    }
+
+    uint32_t initializedLength() const {
+        return capacityIndexAndInitializedLength_ & InitializedLengthMask;
+    }
+
+    uint32_t capacityIndex() const {
+        return (capacityIndexAndInitializedLength_ & CapacityMask) >> CapacityShift;
+    }
+
+    uint32_t capacity() const {
+        return computeCapacity(capacityIndex(), length());
+    }
+
+    bool containsProperty(JSContext* cx, jsid id);
+
+    bool setElement(ExclusiveContext* cx, size_t index, const Value& v);
+    bool initElement(ExclusiveContext* cx, size_t index, const Value& v);
+    Value getElement(size_t index);
+
+    bool appendElementNoTypeChange(ExclusiveContext* cx, size_t index, const Value& v);
+
+    bool growElements(ExclusiveContext* cx, size_t cap);
+    void shrinkElements(ExclusiveContext* cx, size_t cap);
+
+    static uint32_t offsetOfElements() {
+        return offsetof(UnboxedArrayObject, elements_);
+    }
+    static uint32_t offsetOfLength() {
+        return offsetof(UnboxedArrayObject, length_);
+    }
+    static uint32_t offsetOfCapacityIndexAndInitializedLength() {
+        return offsetof(UnboxedArrayObject, capacityIndexAndInitializedLength_);
+    }
+    static uint32_t offsetOfInlineElements() {
+        return offsetof(UnboxedArrayObject, inlineElements_);
+    }
+
+  private:
+    void setInlineElements() {
+        elements_ = &inlineElements_[0];
+    }
+
+    void setLength(uint32_t len) {
+        MOZ_ASSERT(len <= INT32_MAX);
+        length_ = len;
+    }
+
+    void setInitializedLength(uint32_t initlen) {
+        MOZ_ASSERT(initlen <= InitializedLengthMask);
+        capacityIndexAndInitializedLength_ =
+            (capacityIndexAndInitializedLength_ & CapacityMask) | initlen;
+    }
+
+    void setCapacityIndex(uint32_t index) {
+        MOZ_ASSERT(index <= (CapacityMask >> CapacityShift));
+        capacityIndexAndInitializedLength_ =
+            (index << CapacityShift) | initializedLength();
+    }
+};
+
 } // namespace js
 
 #endif /* vm_UnboxedObject_h */
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -24,17 +24,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_SUBTRAHEND = 281;
+static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 282;
 static const uint32_t XDR_BYTECODE_VERSION =
     uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);
 
 static_assert(JSErr_Limit == 393,
               "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or "
               "removed MSG_DEFs from js.msg, you should increment "
               "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's "
               "expected JSErr_Limit value.");