Bug 1116855 - Add JIT optimizations for unboxed objects, r=jandem.
authorBrian Hackett <bhackett1024@gmail.com>
Sun, 01 Feb 2015 09:50:04 -0700
changeset 226974 7b4c0588cba87c37523adbad6f46710d95c74ace
parent 226973 102f668f01d1a160143015fbeb6575dd4fd21d2c
child 226975 67494a15ae342917c71b783185f91018828306d8
push id28216
push usercbook@mozilla.com
push dateMon, 02 Feb 2015 12:20:21 +0000
treeherdermozilla-central@3bf7ed413e87 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1116855
milestone38.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 1116855 - Add JIT optimizations for unboxed objects, r=jandem.
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/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/IonCaches.cpp
js/src/jit/IonCaches.h
js/src/jit/Lowering.cpp
js/src/jit/MIR.cpp
js/src/jit/MIR.h
js/src/jit/MacroAssembler.cpp
js/src/jit/MacroAssembler.h
js/src/jit/OptimizationTracking.h
js/src/jit/ScalarReplacement.cpp
js/src/jit/arm/Lowering-arm.cpp
js/src/jit/arm/MacroAssembler-arm.cpp
js/src/jit/arm/MacroAssembler-arm.h
js/src/jit/mips/Lowering-mips.cpp
js/src/jit/mips/MacroAssembler-mips.h
js/src/jit/none/MacroAssembler-none.h
js/src/jit/shared/Lowering-x86-shared.cpp
js/src/jit/x64/MacroAssembler-x64.h
js/src/jit/x86/MacroAssembler-x86.h
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -358,16 +358,21 @@ ICStub::trace(JSTracer *trc)
           case 5: propStub->toImpl<5>()->traceShapes(trc); break;
           case 6: propStub->toImpl<6>()->traceShapes(trc); break;
           case 7: propStub->toImpl<7>()->traceShapes(trc); break;
           case 8: propStub->toImpl<8>()->traceShapes(trc); break;
           default: MOZ_CRASH("Invalid proto stub.");
         }
         break;
       }
+      case ICStub::GetProp_Unboxed: {
+        ICGetProp_Unboxed *propStub = toGetProp_Unboxed();
+        MarkTypeObject(trc, &propStub->type(), "baseline-getprop-unboxed-stub-type");
+        break;
+      }
       case ICStub::GetProp_TypedObject: {
         ICGetProp_TypedObject *propStub = toGetProp_TypedObject();
         MarkShape(trc, &propStub->shape(), "baseline-getprop-typedobject-stub-shape");
         break;
       }
       case ICStub::GetProp_CallDOMProxyNative:
       case ICStub::GetProp_CallDOMProxyWithGenerationNative: {
         ICGetPropCallDOMProxyNativeStub *propStub;
@@ -432,16 +437,21 @@ ICStub::trace(JSTracer *trc)
           case 1: propStub->toImpl<1>()->traceShapes(trc); break;
           case 2: propStub->toImpl<2>()->traceShapes(trc); break;
           case 3: propStub->toImpl<3>()->traceShapes(trc); break;
           case 4: propStub->toImpl<4>()->traceShapes(trc); break;
           default: MOZ_CRASH("Invalid proto stub.");
         }
         break;
       }
+      case ICStub::SetProp_Unboxed: {
+        ICSetProp_Unboxed *propStub = toSetProp_Unboxed();
+        MarkTypeObject(trc, &propStub->type(), "baseline-setprop-unboxed-stub-type");
+        break;
+      }
       case ICStub::SetProp_TypedObject: {
         ICSetProp_TypedObject *propStub = toSetProp_TypedObject();
         MarkShape(trc, &propStub->shape(), "baseline-setprop-typedobject-stub-shape");
         MarkTypeObject(trc, &propStub->type(), "baseline-setprop-typedobject-stub-type");
         break;
       }
       case ICStub::SetProp_CallScripted: {
         ICSetProp_CallScripted *callStub = toSetProp_CallScripted();
@@ -1405,18 +1415,19 @@ DoTypeUpdateFallback(JSContext *cx, Base
       case ICStub::SetElem_Dense:
       case ICStub::SetElem_DenseAdd: {
         MOZ_ASSERT(obj->isNative());
         id = JSID_VOID;
         types::AddTypePropertyId(cx, obj, id, value);
         break;
       }
       case ICStub::SetProp_Native:
-      case ICStub::SetProp_NativeAdd: {
-        MOZ_ASSERT(obj->isNative());
+      case ICStub::SetProp_NativeAdd:
+      case ICStub::SetProp_Unboxed: {
+        MOZ_ASSERT(obj->isNative() || obj->is<UnboxedPlainObject>());
         jsbytecode *pc = stub->getChainFallback()->icEntry()->pc(script);
         if (*pc == JSOP_SETALIASEDVAR || *pc == JSOP_INITALIASEDLEXICAL)
             id = NameToId(ScopeCoordinateName(cx->runtime()->scopeCoordinateNameCache, script, pc));
         else
             id = NameToId(script->getName(pc));
         types::AddTypePropertyId(cx, obj, id, value);
         break;
       }
@@ -6594,16 +6605,50 @@ TryAttachNativeGetPropStub(JSContext *cx
     if (!newStub)
         return false;
     stub->addNewStub(newStub);
     *attached = true;
     return true;
 }
 
 static bool
+TryAttachUnboxedGetPropStub(JSContext *cx, HandleScript script,
+                            ICGetProp_Fallback *stub, HandlePropertyName name, HandleValue val,
+                            bool *attached)
+{
+    MOZ_ASSERT(!*attached);
+
+    if (!cx->runtime()->jitSupportsFloatingPoint)
+        return true;
+
+    if (!val.isObject() || !val.toObject().is<UnboxedPlainObject>())
+        return true;
+    Rooted<UnboxedPlainObject *> obj(cx, &val.toObject().as<UnboxedPlainObject>());
+
+    const UnboxedLayout::Property *property = obj->layout().lookup(name);
+    if (!property)
+        return true;
+
+    ICStub *monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
+
+    ICGetProp_Unboxed::Compiler compiler(cx, monitorStub, obj->type(),
+                                         property->offset + UnboxedPlainObject::offsetOfData(),
+                                         property->type);
+    ICStub *newStub = compiler.getStub(compiler.getStubSpace(script));
+    if (!newStub)
+        return false;
+    stub->addNewStub(newStub);
+
+    StripPreliminaryObjectStubs(cx, stub);
+
+    *attached = true;
+    return true;
+}
+
+static bool
 TryAttachTypedObjectGetPropStub(JSContext *cx, HandleScript script,
                                 ICGetProp_Fallback *stub, HandlePropertyName name, HandleValue val,
                                 bool *attached)
 {
     MOZ_ASSERT(!*attached);
 
     if (!cx->runtime()->jitSupportsFloatingPoint)
         return true;
@@ -6840,16 +6885,21 @@ DoGetPropFallback(JSContext *cx, Baselin
     RootedScript script(cx, frame->script());
 
     if (!TryAttachNativeGetPropStub(cx, script, pc, stub, name, val, oldShape,
                                     res, &attached, &isTemporarilyUnoptimizable))
         return false;
     if (attached)
         return true;
 
+    if (!TryAttachUnboxedGetPropStub(cx, script, stub, name, val, &attached))
+        return false;
+    if (attached)
+        return true;
+
     if (!TryAttachTypedObjectGetPropStub(cx, script, stub, name, val, &attached))
         return false;
     if (attached)
         return true;
 
     if (val.isString() || val.isNumber() || val.isBoolean()) {
         if (!TryAttachPrimitiveGetPropStub(cx, script, pc, stub, name, val, res, &attached))
             return false;
@@ -7751,16 +7801,49 @@ ICGetProp_Generic::Compiler::generateStu
 
     leaveStubFrame(masm);
     EmitUnstowICValues(masm, 1, /* discard = */ true);
     EmitEnterTypeMonitorIC(masm);
     return true;
 }
 
 bool
+ICGetProp_Unboxed::Compiler::generateStubCode(MacroAssembler &masm)
+{
+    Label failure;
+
+    GeneralRegisterSet regs(availableGeneralRegs(1));
+
+    Register scratch = regs.takeAnyExcluding(BaselineTailCallReg);
+
+    // Object and type guard.
+    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
+    Register object = masm.extractObject(R0, ExtractTemp0);
+    masm.loadPtr(Address(BaselineStubReg, ICGetProp_Unboxed::offsetOfType()), scratch);
+    masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfType()), scratch,
+                   &failure);
+
+    // Get the address being read from.
+    masm.load32(Address(BaselineStubReg, ICGetProp_Unboxed::offsetOfFieldOffset()), scratch);
+
+    masm.loadUnboxedProperty(BaseIndex(object, scratch, TimesOne), fieldType_, TypedOrValueRegister(R0));
+
+    // Only monitor the result if its type might change.
+    if (fieldType_ == JSVAL_TYPE_OBJECT)
+        EmitEnterTypeMonitorIC(masm);
+    else
+        EmitReturnFromIC(masm);
+
+    masm.bind(&failure);
+    EmitStubGuardFailure(masm);
+
+    return true;
+}
+
+bool
 ICGetProp_TypedObject::Compiler::generateStubCode(MacroAssembler &masm)
 {
     Label failure;
 
     CheckForNeuteredTypedObject(cx, masm, &failure);
 
     GeneralRegisterSet regs(availableGeneralRegs(1));
 
@@ -8005,16 +8088,50 @@ TryAttachSetAccessorPropStub(JSContext *
         *attached = true;
         return true;
     }
 
     return true;
 }
 
 static bool
+TryAttachUnboxedSetPropStub(JSContext *cx, HandleScript script,
+                            ICSetProp_Fallback *stub, HandleId id,
+                            HandleObject obj, HandleValue rhs, bool *attached)
+{
+    MOZ_ASSERT(!*attached);
+
+    if (!cx->runtime()->jitSupportsFloatingPoint)
+        return true;
+
+    if (!obj->is<UnboxedPlainObject>())
+        return true;
+
+    const UnboxedLayout::Property *property = obj->as<UnboxedPlainObject>().layout().lookup(id);
+    if (!property)
+        return true;
+
+    ICSetProp_Unboxed::Compiler compiler(cx, obj->type(),
+                                         property->offset + UnboxedPlainObject::offsetOfData(),
+                                         property->type);
+    ICUpdatedStub *newStub = compiler.getStub(compiler.getStubSpace(script));
+    if (!newStub)
+        return false;
+    if (compiler.needsUpdateStubs() && !newStub->addUpdateStubForValue(cx, script, obj, id, rhs))
+        return false;
+
+    stub->addNewStub(newStub);
+
+    StripPreliminaryObjectStubs(cx, stub);
+
+    *attached = true;
+    return true;
+}
+
+static bool
 TryAttachTypedObjectSetPropStub(JSContext *cx, HandleScript script,
                                 ICSetProp_Fallback *stub, HandleId id,
                                 HandleObject obj, HandleValue rhs, bool *attached)
 {
     MOZ_ASSERT(!*attached);
 
     if (!cx->runtime()->jitSupportsFloatingPoint)
         return true;
@@ -8149,16 +8266,25 @@ DoSetPropFallback(JSContext *cx, Baselin
     {
         return false;
     }
     if (attached)
         return true;
 
     if (!attached &&
         lhs.isObject() &&
+        !TryAttachUnboxedSetPropStub(cx, script, stub, id, obj, rhs, &attached))
+    {
+        return false;
+    }
+    if (attached)
+        return true;
+
+    if (!attached &&
+        lhs.isObject() &&
         !TryAttachTypedObjectSetPropStub(cx, script, stub, id, obj, rhs, &attached))
     {
         return false;
     }
     if (attached)
         return true;
 
     MOZ_ASSERT(!attached);
@@ -8432,16 +8558,85 @@ ICSetPropNativeAddCompiler::generateStub
     EmitUnstowICValues(masm, 2);
 
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
 }
 
 bool
+ICSetProp_Unboxed::Compiler::generateStubCode(MacroAssembler &masm)
+{
+    Label failure;
+
+    // Guard input is an object.
+    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
+
+    GeneralRegisterSet regs(availableGeneralRegs(2));
+    Register scratch = regs.takeAny();
+
+    // Unbox and type guard.
+    Register object = masm.extractObject(R0, ExtractTemp0);
+    masm.loadPtr(Address(BaselineStubReg, ICSetProp_Unboxed::offsetOfType()), scratch);
+    masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfType()), scratch,
+                   &failure);
+
+    if (needsUpdateStubs()) {
+        // Stow both R0 and R1 (object and value).
+        masm.push(object);
+        masm.push(BaselineStubReg);
+        EmitStowICValues(masm, 2);
+
+        // Move RHS into R0 for TypeUpdate check.
+        masm.moveValue(R1, R0);
+
+        // Call the type update stub.
+        if (!callTypeUpdateIC(masm, sizeof(Value)))
+            return false;
+
+        // Unstow R0 and R1 (object and key)
+        EmitUnstowICValues(masm, 2);
+        masm.pop(BaselineStubReg);
+        masm.pop(object);
+
+        // Trigger post barriers here on the values being written. Fields which
+        // objects can be written to also need update stubs.
+        GeneralRegisterSet saveRegs;
+        saveRegs.add(R0);
+        saveRegs.add(R1);
+        saveRegs.addUnchecked(object);
+        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_));
+
+    masm.storeUnboxedProperty(address, fieldType_,
+                              ConstantOrRegister(TypedOrValueRegister(R1)), &failure);
+
+    // The RHS has to be in R0.
+    masm.moveValue(R1, R0);
+
+    EmitReturnFromIC(masm);
+
+    masm.bind(&failure);
+    EmitStubGuardFailure(masm);
+    return true;
+}
+
+bool
 ICSetProp_TypedObject::Compiler::generateStubCode(MacroAssembler &masm)
 {
     Label failure;
 
     CheckForNeuteredTypedObject(cx, masm, &failure);
 
     // Guard input is an object.
     masm.branchTestObject(Assembler::NotEqual, R0, &failure);
@@ -9061,37 +9256,36 @@ TryAttachCallStub(JSContext *cx, ICCall_
 
         // Keep track of the function's |prototype| property in type
         // information, for use during Ion compilation.
         if (IsIonEnabled(cx))
             types::EnsureTrackPropertyTypes(cx, fun, NameToId(cx->names().prototype));
 
         // Remember the template object associated with any script being called
         // as a constructor, for later use during Ion compilation.
-        RootedPlainObject templateObject(cx);
+        RootedObject templateObject(cx);
         if (constructing) {
             JSObject *thisObject = CreateThisForFunction(cx, fun, MaybeSingletonObject);
             if (!thisObject)
                 return false;
 
-            if (thisObject->is<PlainObject>()) {
-                templateObject = &thisObject->as<PlainObject>();
+            if (thisObject->is<PlainObject>() || thisObject->is<UnboxedPlainObject>()) {
+                templateObject = thisObject;
 
                 // If we are calling a constructor for which the new script
                 // properties analysis has not been performed yet, don't attach a
                 // stub. After the analysis is performed, CreateThisForFunction may
                 // start returning objects with a different type, and the Ion
                 // compiler might get confused.
-                if (templateObject->type()->newScript() &&
-                    !templateObject->type()->newScript()->analyzed())
-                {
+                types::TypeNewScript *newScript = templateObject->type()->newScript();
+                if (newScript && !newScript->analyzed()) {
                     // Clear the object just created from the preliminary objects
                     // on the TypeNewScript, as it will not be used or filled in by
                     // running code.
-                    templateObject->type()->newScript()->unregisterNewObject(templateObject);
+                    newScript->unregisterNewObject(&templateObject->as<PlainObject>());
                     return true;
                 }
             }
         }
 
         JitSpew(JitSpew_BaselineIC,
                 "  Generating Call_Scripted stub (fun=%p, %s:%d, cons=%s, spread=%s)",
                 fun.get(), fun->nonLazyScript()->filename(), fun->nonLazyScript()->lineno(),
@@ -11708,30 +11902,30 @@ ICSetProp_CallNative::Clone(JSContext *c
     RootedShape shape(cx, other.shape_);
     RootedObject holder(cx, other.holder_);
     RootedShape holderShape(cx, other.holderShape_);
     RootedFunction setter(cx, other.setter_);
     return New(space, other.jitCode(), shape, holder, holderShape, setter, other.pcOffset_);
 }
 
 ICCall_Scripted::ICCall_Scripted(JitCode *stubCode, ICStub *firstMonitorStub,
-                                 HandleScript calleeScript, HandleNativeObject templateObject,
+                                 HandleScript calleeScript, HandleObject templateObject,
                                  uint32_t pcOffset)
   : ICMonitoredStub(ICStub::Call_Scripted, stubCode, firstMonitorStub),
     calleeScript_(calleeScript),
     templateObject_(templateObject),
     pcOffset_(pcOffset)
 { }
 
 /* static */ ICCall_Scripted *
 ICCall_Scripted::Clone(JSContext *cx, ICStubSpace *space, ICStub *firstMonitorStub,
                        ICCall_Scripted &other)
 {
     RootedScript calleeScript(cx, other.calleeScript_);
-    RootedNativeObject templateObject(cx, other.templateObject_);
+    RootedObject templateObject(cx, other.templateObject_);
     return New(space, other.jitCode(), firstMonitorStub, calleeScript, templateObject,
                other.pcOffset_);
 }
 
 /* static */ ICCall_AnyScripted *
 ICCall_AnyScripted::Clone(JSContext *, ICStubSpace *space, ICStub *firstMonitorStub,
                           ICCall_AnyScripted &other)
 {
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -425,30 +425,32 @@ class ICEntry
                                 \
     _(GetProp_Fallback)         \
     _(GetProp_ArrayLength)      \
     _(GetProp_Primitive)        \
     _(GetProp_StringLength)     \
     _(GetProp_Native)           \
     _(GetProp_NativeDoesNotExist) \
     _(GetProp_NativePrototype)  \
+    _(GetProp_Unboxed)          \
     _(GetProp_TypedObject)      \
     _(GetProp_CallScripted)     \
     _(GetProp_CallNative)       \
     _(GetProp_CallNativePrototype)\
     _(GetProp_CallDOMProxyNative)\
     _(GetProp_CallDOMProxyWithGenerationNative)\
     _(GetProp_DOMProxyShadowed) \
     _(GetProp_ArgumentsLength)  \
     _(GetProp_ArgumentsCallee)  \
     _(GetProp_Generic)          \
                                 \
     _(SetProp_Fallback)         \
     _(SetProp_Native)           \
     _(SetProp_NativeAdd)        \
+    _(SetProp_Unboxed)          \
     _(SetProp_TypedObject)      \
     _(SetProp_CallScripted)     \
     _(SetProp_CallNative)       \
                                 \
     _(TableSwitch)              \
                                 \
     _(IteratorNew_Fallback)     \
     _(IteratorMore_Fallback)    \
@@ -4518,16 +4520,82 @@ class ICGetPropNativeDoesNotExistCompile
     ICStub *getStubSpecific(ICStubSpace *space, const AutoShapeVector *shapes) {
         return ICGetProp_NativeDoesNotExistImpl<ProtoChainDepth>::New(space, getStubCode(),
                                                                       firstMonitorStub_, shapes);
     }
 
     ICStub *getStub(ICStubSpace *space);
 };
 
+class ICGetProp_Unboxed : public ICMonitoredStub
+{
+    friend class ICStubSpace;
+
+    HeapPtrTypeObject type_;
+    uint32_t fieldOffset_;
+
+    ICGetProp_Unboxed(JitCode *stubCode, ICStub *firstMonitorStub, HandleTypeObject type,
+                      uint32_t fieldOffset)
+      : ICMonitoredStub(ICStub::GetProp_Unboxed, stubCode, firstMonitorStub),
+        type_(type), fieldOffset_(fieldOffset)
+    {
+        (void) fieldOffset_; // Silence clang warning
+    }
+
+  public:
+    static inline ICGetProp_Unboxed *New(ICStubSpace *space, JitCode *code,
+                                         ICStub *firstMonitorStub, HandleTypeObject shape,
+                                         uint32_t fieldOffset)
+    {
+        if (!code)
+            return nullptr;
+        return space->allocate<ICGetProp_Unboxed>(code, firstMonitorStub, shape, fieldOffset);
+    }
+
+    HeapPtrTypeObject &type() {
+        return type_;
+    }
+
+    static size_t offsetOfType() {
+        return offsetof(ICGetProp_Unboxed, type_);
+    }
+    static size_t offsetOfFieldOffset() {
+        return offsetof(ICGetProp_Unboxed, fieldOffset_);
+    }
+
+    class Compiler : public ICStubCompiler {
+      protected:
+        ICStub *firstMonitorStub_;
+        RootedTypeObject type_;
+        uint32_t fieldOffset_;
+        JSValueType fieldType_;
+
+        bool generateStubCode(MacroAssembler &masm);
+
+        virtual int32_t getKey() const {
+            return static_cast<int32_t>(kind) | (static_cast<int32_t>(fieldType_)) << 16;
+        }
+
+      public:
+        Compiler(JSContext *cx, ICStub *firstMonitorStub,
+                 types::TypeObject *type, uint32_t fieldOffset, JSValueType fieldType)
+          : ICStubCompiler(cx, ICStub::GetProp_Unboxed),
+            firstMonitorStub_(firstMonitorStub),
+            type_(cx, type),
+            fieldOffset_(fieldOffset),
+            fieldType_(fieldType)
+        {}
+
+        ICStub *getStub(ICStubSpace *space) {
+            return ICGetProp_Unboxed::New(space, getStubCode(), firstMonitorStub_,
+                                          type_, fieldOffset_);
+        }
+    };
+};
+
 static uint32_t
 SimpleTypeDescrKey(SimpleTypeDescr *descr)
 {
     if (descr->is<ScalarTypeDescr>())
         return uint32_t(descr->as<ScalarTypeDescr>().type()) << 1;
     return (uint32_t(descr->as<ReferenceTypeDescr>().type()) << 1) | 1;
 }
 
@@ -5414,16 +5482,87 @@ class ICSetPropNativeAddCompiler : publi
 
         return ICSetProp_NativeAddImpl<ProtoChainDepth>::New(
                     space, getStubCode(), oldType_, shapes, newShape, newType, offset_);
     }
 
     ICUpdatedStub *getStub(ICStubSpace *space);
 };
 
+class ICSetProp_Unboxed : public ICUpdatedStub
+{
+    friend class ICStubSpace;
+
+    HeapPtrTypeObject type_;
+    uint32_t fieldOffset_;
+
+    ICSetProp_Unboxed(JitCode *stubCode, HandleTypeObject type, uint32_t fieldOffset)
+      : ICUpdatedStub(ICStub::SetProp_Unboxed, stubCode),
+        type_(type),
+        fieldOffset_(fieldOffset)
+    {
+        (void) fieldOffset_; // Silence clang warning
+    }
+
+  public:
+    static inline ICSetProp_Unboxed *New(ICStubSpace *space, JitCode *code,
+                                         HandleTypeObject type, uint32_t fieldOffset)
+    {
+        if (!code)
+            return nullptr;
+        return space->allocate<ICSetProp_Unboxed>(code, type, fieldOffset);
+    }
+
+    HeapPtrTypeObject &type() {
+        return type_;
+    }
+
+    static size_t offsetOfType() {
+        return offsetof(ICSetProp_Unboxed, type_);
+    }
+    static size_t offsetOfFieldOffset() {
+        return offsetof(ICSetProp_Unboxed, fieldOffset_);
+    }
+
+    class Compiler : public ICStubCompiler {
+      protected:
+        RootedTypeObject type_;
+        uint32_t fieldOffset_;
+        JSValueType fieldType_;
+
+        bool generateStubCode(MacroAssembler &masm);
+
+        virtual int32_t getKey() const {
+            return static_cast<int32_t>(kind) |
+                   (static_cast<int32_t>(fieldType_) << 16);
+        }
+
+      public:
+        Compiler(JSContext *cx, types::TypeObject *type, uint32_t fieldOffset,
+                 JSValueType fieldType)
+          : ICStubCompiler(cx, ICStub::SetProp_Unboxed),
+            type_(cx, type),
+            fieldOffset_(fieldOffset),
+            fieldType_(fieldType)
+        {}
+
+        ICUpdatedStub *getStub(ICStubSpace *space) {
+            ICUpdatedStub *stub = ICSetProp_Unboxed::New(space, getStubCode(),
+                                                         type_, fieldOffset_);
+            if (!stub || !stub->initUpdatingChain(cx, space))
+                return nullptr;
+            return stub;
+        }
+
+        bool needsUpdateStubs() {
+            return fieldType_ == JSVAL_TYPE_OBJECT;
+        }
+    };
+};
+
 class ICSetProp_TypedObject : public ICUpdatedStub
 {
     friend class ICStubSpace;
 
     HeapPtrShape shape_;
     HeapPtrTypeObject type_;
     uint32_t fieldOffset_;
     bool isObjectReference_;
@@ -5794,42 +5933,42 @@ class ICCall_Scripted : public ICMonitor
   public:
     // The maximum number of inlineable spread call arguments. Keep this small
     // to avoid controllable stack overflows by attackers passing large arrays
     // to spread call. This value is shared with ICCall_Native.
     static const uint32_t MAX_ARGS_SPREAD_LENGTH = 16;
 
   protected:
     HeapPtrScript calleeScript_;
-    HeapPtrNativeObject templateObject_;
+    HeapPtrObject templateObject_;
     uint32_t pcOffset_;
 
     ICCall_Scripted(JitCode *stubCode, ICStub *firstMonitorStub,
-                    HandleScript calleeScript, HandleNativeObject templateObject,
+                    HandleScript calleeScript, HandleObject templateObject,
                     uint32_t pcOffset);
 
   public:
     static inline ICCall_Scripted *New(
             ICStubSpace *space, JitCode *code, ICStub *firstMonitorStub,
-            HandleScript calleeScript, HandleNativeObject templateObject,
+            HandleScript calleeScript, HandleObject templateObject,
             uint32_t pcOffset)
     {
         if (!code)
             return nullptr;
         return space->allocate<ICCall_Scripted>(code, firstMonitorStub,
                                                 calleeScript, templateObject, pcOffset);
     }
 
     static ICCall_Scripted *Clone(JSContext *cx, ICStubSpace *space, ICStub *firstMonitorStub,
                                   ICCall_Scripted &other);
 
     HeapPtrScript &calleeScript() {
         return calleeScript_;
     }
-    HeapPtrNativeObject &templateObject() {
+    HeapPtrObject &templateObject() {
         return templateObject_;
     }
 
     static size_t offsetOfCalleeScript() {
         return offsetof(ICCall_Scripted, calleeScript_);
     }
     static size_t offsetOfPCOffset() {
         return offsetof(ICCall_Scripted, pcOffset_);
@@ -5867,28 +6006,28 @@ class ICCall_AnyScripted : public ICMoni
 
 // Compiler for Call_Scripted and Call_AnyScripted stubs.
 class ICCallScriptedCompiler : public ICCallStubCompiler {
   protected:
     ICStub *firstMonitorStub_;
     bool isConstructing_;
     bool isSpread_;
     RootedScript calleeScript_;
-    RootedNativeObject templateObject_;
+    RootedObject templateObject_;
     uint32_t pcOffset_;
     bool generateStubCode(MacroAssembler &masm);
 
     virtual int32_t getKey() const {
         return static_cast<int32_t>(kind) | (static_cast<int32_t>(isConstructing_) << 16) |
                (static_cast<int32_t>(isSpread_) << 17);
     }
 
   public:
     ICCallScriptedCompiler(JSContext *cx, ICStub *firstMonitorStub,
-                           HandleScript calleeScript, HandleNativeObject templateObject,
+                           HandleScript calleeScript, HandleObject templateObject,
                            bool isConstructing, bool isSpread, uint32_t pcOffset)
       : ICCallStubCompiler(cx, ICStub::Call_Scripted),
         firstMonitorStub_(firstMonitorStub),
         isConstructing_(isConstructing),
         isSpread_(isSpread),
         calleeScript_(cx, calleeScript),
         templateObject_(cx, templateObject),
         pcOffset_(pcOffset)
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -75,68 +75,94 @@ SetElemICInspector::sawTypedArrayWrite()
     for (ICStub *stub = icEntry_->firstStub(); stub; stub = stub->next()) {
         if (stub->isSetElem_TypedArray())
             return true;
     }
     return false;
 }
 
 bool
-BaselineInspector::maybeShapesForPropertyOp(jsbytecode *pc, ShapeVector &shapes)
+BaselineInspector::maybeInfoForPropertyOp(jsbytecode *pc,
+                                          ShapeVector &nativeShapes,
+                                          TypeObjectVector &unboxedTypes)
 {
-    // Return a list of shapes seen by the baseline IC for the current op.
-    // An empty list indicates no shapes are known, or there was an uncacheable
-    // access.
-    MOZ_ASSERT(shapes.empty());
+    // Return lists of native shapes and unboxed objects seen by the baseline
+    // IC for the current op. Empty lists indicate no shapes/types are known,
+    // or there was an uncacheable access.
+    MOZ_ASSERT(nativeShapes.empty());
+    MOZ_ASSERT(unboxedTypes.empty());
 
     if (!hasBaselineScript())
         return true;
 
     MOZ_ASSERT(isValidPC(pc));
     const ICEntry &entry = icEntryFromPC(pc);
 
     ICStub *stub = entry.firstStub();
     while (stub->next()) {
-        Shape *shape;
+        Shape *shape = nullptr;
+        types::TypeObject *type = nullptr;
         if (stub->isGetProp_Native()) {
             shape = stub->toGetProp_Native()->shape();
         } else if (stub->isSetProp_Native()) {
             shape = stub->toSetProp_Native()->shape();
+        } else if (stub->isGetProp_Unboxed()) {
+            type = stub->toGetProp_Unboxed()->type();
+        } else if (stub->isSetProp_Unboxed()) {
+            type = stub->toSetProp_Unboxed()->type();
         } else {
-            shapes.clear();
+            nativeShapes.clear();
+            unboxedTypes.clear();
             return true;
         }
 
-        // Don't add the same shape twice (this can happen if there are multiple
-        // SetProp_Native stubs with different TypeObject's).
-        bool found = false;
-        for (size_t i = 0; i < shapes.length(); i++) {
-            if (shapes[i] == shape) {
-                found = true;
-                break;
+        // Don't add the same shape/type twice (this can happen if there are
+        // multiple SetProp_Native stubs with different TypeObject's).
+        if (shape) {
+            bool found = false;
+            for (size_t i = 0; i < nativeShapes.length(); i++) {
+                if (nativeShapes[i] == shape) {
+                    found = true;
+                    break;
+                }
             }
+            if (!found && !nativeShapes.append(shape))
+                return false;
+        } else {
+            bool found = false;
+            for (size_t i = 0; i < unboxedTypes.length(); i++) {
+                if (unboxedTypes[i] == type) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found && !unboxedTypes.append(type))
+                return false;
         }
 
-        if (!found && !shapes.append(shape))
-            return false;
-
         stub = stub->next();
     }
 
     if (stub->isGetProp_Fallback()) {
-        if (stub->toGetProp_Fallback()->hadUnoptimizableAccess())
-            shapes.clear();
+        if (stub->toGetProp_Fallback()->hadUnoptimizableAccess()) {
+            nativeShapes.clear();
+            unboxedTypes.clear();
+        }
     } else {
-        if (stub->toSetProp_Fallback()->hadUnoptimizableAccess())
-            shapes.clear();
+        if (stub->toSetProp_Fallback()->hadUnoptimizableAccess()) {
+            nativeShapes.clear();
+            unboxedTypes.clear();
+        }
     }
 
-    // Don't inline if there are more than 5 shapes.
-    if (shapes.length() > 5)
-        shapes.clear();
+    // Don't inline if there are more than 5 shapes/types.
+    if (nativeShapes.length() + unboxedTypes.length() > 5) {
+        nativeShapes.clear();
+        unboxedTypes.clear();
+    }
 
     return true;
 }
 
 ICStub *
 BaselineInspector::monomorphicStub(jsbytecode *pc)
 {
     if (!hasBaselineScript())
@@ -408,33 +434,33 @@ BaselineInspector::hasSeenDoubleResult(j
     if (stub->isUnaryArith_Fallback())
         return stub->toUnaryArith_Fallback()->sawDoubleResult();
     else
         return stub->toBinaryArith_Fallback()->sawDoubleResult();
 
     return false;
 }
 
-NativeObject *
+JSObject *
 BaselineInspector::getTemplateObject(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()->templateObject();
           case ICStub::NewObject_Fallback:
             return stub->toNewObject_Fallback()->templateObject();
           case ICStub::Rest_Fallback:
             return stub->toRest_Fallback()->templateObject();
           case ICStub::Call_Scripted:
-            if (NativeObject *obj = stub->toCall_Scripted()->templateObject())
+            if (JSObject *obj = stub->toCall_Scripted()->templateObject())
                 return obj;
             break;
           default:
             break;
         }
     }
 
     return nullptr;
--- a/js/src/jit/BaselineInspector.h
+++ b/js/src/jit/BaselineInspector.h
@@ -88,33 +88,34 @@ class BaselineInspector
         return ICInspectorType(this, pc, ent);
     }
 
     ICStub *monomorphicStub(jsbytecode *pc);
     bool dimorphicStub(jsbytecode *pc, ICStub **pfirst, ICStub **psecond);
 
   public:
     typedef Vector<Shape *, 4, JitAllocPolicy> ShapeVector;
-    bool maybeShapesForPropertyOp(jsbytecode *pc, ShapeVector &shapes);
+    typedef Vector<types::TypeObject *, 4, JitAllocPolicy> TypeObjectVector;
+    bool maybeInfoForPropertyOp(jsbytecode *pc, ShapeVector &nativeShapes, TypeObjectVector &unboxedTypes);
 
     SetElemICInspector setElemICInspector(jsbytecode *pc) {
         return makeICInspector<SetElemICInspector>(pc, ICStub::SetElem_Fallback);
     }
 
     MIRType expectedResultType(jsbytecode *pc);
     MCompare::CompareType expectedCompareType(jsbytecode *pc);
     MIRType expectedBinaryArithSpecialization(jsbytecode *pc);
 
     bool hasSeenNonNativeGetElement(jsbytecode *pc);
     bool hasSeenNegativeIndexGetElement(jsbytecode *pc);
     bool hasSeenAccessedGetter(jsbytecode *pc);
     bool hasSeenDoubleResult(jsbytecode *pc);
     bool hasSeenNonStringIterMore(jsbytecode *pc);
 
-    NativeObject *getTemplateObject(jsbytecode *pc);
+    JSObject *getTemplateObject(jsbytecode *pc);
     JSObject *getTemplateObjectForNative(jsbytecode *pc, Native native);
     JSObject *getTemplateObjectForClassHook(jsbytecode *pc, const Class *clasp);
 
     DeclEnvObject *templateDeclEnvObject();
     CallObject *templateCallObject();
 
     JSObject *commonGetPropFunction(jsbytecode *pc, Shape **lastProperty, JSFunction **commonGetter,
                                     Shape **globalShape);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -2228,43 +2228,72 @@ CodeGenerator::visitStoreSlotV(LStoreSlo
     masm.storeValue(value, Address(base, offset));
 }
 
 void
 CodeGenerator::emitGetPropertyPolymorphic(LInstruction *ins, Register obj, Register scratch,
                                           const TypedOrValueRegister &output)
 {
     MGetPropertyPolymorphic *mir = ins->mirRaw()->toGetPropertyPolymorphic();
-    MOZ_ASSERT(mir->numShapes() > 1);
-
-    masm.loadObjShape(obj, scratch);
+
+    size_t total = mir->numUnboxedTypes() + mir->numShapes();
+    MOZ_ASSERT(total > 1);
+
+    bool typeInScratch = mir->numUnboxedTypes() > 1;
+    bool shapeInScratch = mir->numShapes() > 1;
 
     Label done;
-    for (size_t i = 0; i < mir->numShapes(); i++) {
+
+    for (size_t i = 0; i < total; i++) {
+        bool unboxedType = i < mir->numUnboxedTypes();
+
+        ImmGCPtr comparePtr = unboxedType
+                              ? ImmGCPtr(mir->unboxedType(i))
+                              : ImmGCPtr(mir->objShape(i - mir->numUnboxedTypes()));
+        Address addr(obj, unboxedType ? JSObject::offsetOfType() : JSObject::offsetOfShape());
+
+        if ((i == 0 && typeInScratch) || (i == mir->numUnboxedTypes() && shapeInScratch))
+            masm.loadPtr(addr, scratch);
+
+        bool inScratch = unboxedType ? typeInScratch : shapeInScratch;
+
         Label next;
-        if (i == mir->numShapes() - 1) {
-            bailoutCmpPtr(Assembler::NotEqual, scratch, ImmGCPtr(mir->objShape(i)),
-                          ins->snapshot());
+        if (i == total - 1) {
+            if (inScratch)
+                bailoutCmpPtr(Assembler::NotEqual, scratch, comparePtr, ins->snapshot());
+            else
+                bailoutCmpPtr(Assembler::NotEqual, addr, comparePtr, ins->snapshot());
         } else {
-            masm.branchPtr(Assembler::NotEqual, scratch, ImmGCPtr(mir->objShape(i)), &next);
+            if (inScratch)
+                masm.branchPtr(Assembler::NotEqual, scratch, comparePtr, &next);
+            else
+                masm.branchPtr(Assembler::NotEqual, addr, comparePtr, &next);
         }
 
-        Shape *shape = mir->shape(i);
-        if (shape->slot() < shape->numFixedSlots()) {
-            // Fixed slot.
-            masm.loadTypedOrValue(Address(obj, NativeObject::getFixedSlotOffset(shape->slot())),
-                                  output);
+        if (unboxedType) {
+            const UnboxedLayout::Property *property =
+                mir->unboxedType(i)->unboxedLayout().lookup(mir->name());
+            Address propertyAddr(obj, UnboxedPlainObject::offsetOfData() + property->offset);
+
+            masm.loadUnboxedProperty(propertyAddr, property->type, output);
         } else {
-            // Dynamic slot.
-            uint32_t offset = (shape->slot() - shape->numFixedSlots()) * sizeof(js::Value);
-            masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), scratch);
-            masm.loadTypedOrValue(Address(scratch, offset), output);
+            Shape *shape = mir->shape(i - mir->numUnboxedTypes());
+            if (shape->slot() < shape->numFixedSlots()) {
+                // Fixed slot.
+                masm.loadTypedOrValue(Address(obj, NativeObject::getFixedSlotOffset(shape->slot())),
+                                      output);
+            } else {
+                // Dynamic slot.
+                uint32_t offset = (shape->slot() - shape->numFixedSlots()) * sizeof(js::Value);
+                masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), scratch);
+                masm.loadTypedOrValue(Address(scratch, offset), output);
+            }
         }
 
-        if (i != mir->numShapes() - 1)
+        if (i != total - 1)
             masm.jump(&done);
         masm.bind(&next);
     }
 
     masm.bind(&done);
 }
 
 void
@@ -2286,47 +2315,82 @@ CodeGenerator::visitGetPropertyPolymorph
     emitGetPropertyPolymorphic(ins, obj, temp, output);
 }
 
 void
 CodeGenerator::emitSetPropertyPolymorphic(LInstruction *ins, Register obj, Register scratch,
                                           const ConstantOrRegister &value)
 {
     MSetPropertyPolymorphic *mir = ins->mirRaw()->toSetPropertyPolymorphic();
-    MOZ_ASSERT(mir->numShapes() > 1);
-
-    masm.loadObjShape(obj, scratch);
+
+    size_t total = mir->numUnboxedTypes() + mir->numShapes();
+    MOZ_ASSERT(total > 1);
+
+    bool typeInScratch = mir->numUnboxedTypes() > 1;
+    bool shapeInScratch = mir->numShapes() > 1;
 
     Label done;
-    for (size_t i = 0; i < mir->numShapes(); i++) {
+    for (size_t i = 0; i < total; i++) {
+        bool unboxedType = i < mir->numUnboxedTypes();
+
+        ImmGCPtr comparePtr = unboxedType
+                              ? ImmGCPtr(mir->unboxedType(i))
+                              : ImmGCPtr(mir->objShape(i - mir->numUnboxedTypes()));
+        Address addr(obj, unboxedType ? JSObject::offsetOfType() : JSObject::offsetOfShape());
+
+        if ((i == 0 && typeInScratch) || (i == mir->numUnboxedTypes() && shapeInScratch))
+            masm.loadPtr(addr, scratch);
+
+        bool inScratch = unboxedType ? typeInScratch : shapeInScratch;
+
         Label next;
-        if (i == mir->numShapes() - 1) {
-            bailoutCmpPtr(Assembler::NotEqual, scratch, ImmGCPtr(mir->objShape(i)),
-                          ins->snapshot());
+        if (i == total - 1) {
+            if (inScratch)
+                bailoutCmpPtr(Assembler::NotEqual, scratch, comparePtr, ins->snapshot());
+            else
+                bailoutCmpPtr(Assembler::NotEqual, addr, comparePtr, ins->snapshot());
         } else {
-            masm.branchPtr(Assembler::NotEqual, scratch, ImmGCPtr(mir->objShape(i)), &next);
+            if (inScratch)
+                masm.branchPtr(Assembler::NotEqual, scratch, comparePtr, &next);
+            else
+                masm.branchPtr(Assembler::NotEqual, addr, comparePtr, &next);
         }
 
-        Shape *shape = mir->shape(i);
-        if (shape->slot() < shape->numFixedSlots()) {
-            // Fixed slot.
-            Address addr(obj, NativeObject::getFixedSlotOffset(shape->slot()));
-            if (mir->needsBarrier())
-                emitPreBarrier(addr);
-            masm.storeConstantOrRegister(value, addr);
+        if (unboxedType) {
+            const UnboxedLayout::Property *property =
+                mir->unboxedType(i)->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));
+
+            masm.storeUnboxedProperty(propertyAddr, property->type, value, nullptr);
         } else {
-            // Dynamic slot.
-            masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), scratch);
-            Address addr(scratch, (shape->slot() - shape->numFixedSlots()) * sizeof(js::Value));
-            if (mir->needsBarrier())
-                emitPreBarrier(addr);
-            masm.storeConstantOrRegister(value, addr);
+            Shape *shape = mir->shape(i - mir->numUnboxedTypes());
+            if (shape->slot() < shape->numFixedSlots()) {
+                // Fixed slot.
+                Address addr(obj, NativeObject::getFixedSlotOffset(shape->slot()));
+                if (mir->needsBarrier())
+                    emitPreBarrier(addr);
+                masm.storeConstantOrRegister(value, addr);
+            } else {
+                // Dynamic slot.
+                masm.loadPtr(Address(obj, NativeObject::offsetOfSlots()), scratch);
+                Address addr(scratch, (shape->slot() - shape->numFixedSlots()) * sizeof(js::Value));
+                if (mir->needsBarrier())
+                    emitPreBarrier(addr);
+                masm.storeConstantOrRegister(value, addr);
+            }
         }
 
-        if (i != mir->numShapes() - 1)
+        if (i != total - 1)
             masm.jump(&done);
         masm.bind(&next);
     }
 
     masm.bind(&done);
 }
 
 void
@@ -4568,17 +4632,17 @@ CodeGenerator::visitCreateThisWithProto(
 typedef JSObject *(*NewGCObjectFn)(JSContext *cx, gc::AllocKind allocKind,
                                    gc::InitialHeap initialHeap, const js::Class *clasp);
 static const VMFunction NewGCObjectInfo =
     FunctionInfo<NewGCObjectFn>(js::jit::NewGCObject);
 
 void
 CodeGenerator::visitCreateThisWithTemplate(LCreateThisWithTemplate *lir)
 {
-    PlainObject *templateObject = lir->mir()->templateObject();
+    JSObject *templateObject = lir->mir()->templateObject();
     gc::AllocKind allocKind = templateObject->asTenured().getAllocKind();
     gc::InitialHeap initialHeap = lir->mir()->initialHeap();
     const js::Class *clasp = templateObject->type()->clasp();
     Register objReg = ToRegister(lir->output());
     Register tempReg = ToRegister(lir->temp());
 
     OutOfLineCode *ool = oolCallVM(NewGCObjectInfo, lir,
                                    (ArgList(), Imm32(allocKind), Imm32(initialHeap),
@@ -4586,17 +4650,18 @@ CodeGenerator::visitCreateThisWithTempla
                                    StoreRegisterTo(objReg));
 
     // Allocate. If the FreeList is empty, call to VM, which may GC.
     masm.newGCThing(objReg, tempReg, templateObject, lir->mir()->initialHeap(), ool->entry());
 
     // Initialize based on the templateObject.
     masm.bind(ool->rejoin());
 
-    bool initFixedSlots = ShouldInitFixedSlots(lir, templateObject);
+    bool initFixedSlots = !templateObject->is<PlainObject>() ||
+                          ShouldInitFixedSlots(lir, &templateObject->as<PlainObject>());
     masm.initGCThing(objReg, tempReg, templateObject, initFixedSlots);
 }
 
 typedef JSObject *(*NewIonArgumentsObjectFn)(JSContext *cx, JitFrameLayout *frame, HandleObject);
 static const VMFunction NewIonArgumentsObjectInfo =
     FunctionInfo<NewIonArgumentsObjectFn>((NewIonArgumentsObjectFn) ArgumentsObject::createForIon);
 
 void
@@ -8368,18 +8433,18 @@ CodeGenerator::visitLoadUnboxedPointerT(
 {
     Register elements = ToRegister(lir->elements());
     const LAllocation *index = lir->index();
     Register out = ToRegister(lir->output());
 
     bool bailOnNull;
     int32_t offsetAdjustment;
     if (lir->mir()->isLoadUnboxedObjectOrNull()) {
-        MOZ_ASSERT(lir->mir()->toLoadUnboxedObjectOrNull()->bailOnNull());
-        bailOnNull = true;
+        bailOnNull = lir->mir()->toLoadUnboxedObjectOrNull()->nullBehavior() !=
+                     MLoadUnboxedObjectOrNull::NullNotPossible;
         offsetAdjustment = lir->mir()->toLoadUnboxedObjectOrNull()->offsetAdjustment();
     } else if (lir->mir()->isLoadUnboxedString()) {
         bailOnNull = false;
         offsetAdjustment = lir->mir()->toLoadUnboxedString()->offsetAdjustment();
     } else {
         MOZ_CRASH();
     }
 
@@ -8406,21 +8471,23 @@ CodeGenerator::visitLoadTypedArrayElemen
     AnyRegister out = ToAnyRegister(lir->output());
 
     Scalar::Type arrayType = lir->mir()->arrayType();
     int width = Scalar::byteSize(arrayType);
 
     Label fail;
     if (lir->index()->isConstant()) {
         Address source(elements, ToInt32(lir->index()) * width + lir->mir()->offsetAdjustment());
-        masm.loadFromTypedArray(arrayType, source, out, temp, &fail);
+        masm.loadFromTypedArray(arrayType, source, out, temp, &fail,
+                                lir->mir()->canonicalizeDoubles());
     } else {
         BaseIndex source(elements, ToRegister(lir->index()), ScaleFromElemWidth(width),
                          lir->mir()->offsetAdjustment());
-        masm.loadFromTypedArray(arrayType, source, out, temp, &fail);
+        masm.loadFromTypedArray(arrayType, source, out, temp, &fail,
+                                lir->mir()->canonicalizeDoubles());
     }
 
     if (fail.used())
         bailoutFrom(&fail, lir->snapshot());
 }
 
 void
 CodeGenerator::visitLoadTypedArrayElementHole(LLoadTypedArrayElementHole *lir)
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -5509,17 +5509,19 @@ MDefinition *
 IonBuilder::createThisScriptedSingleton(JSFunction *target, MDefinition *callee)
 {
     // Get the singleton prototype (if exists)
     JSObject *proto = getSingletonPrototype(target);
     if (!proto)
         return nullptr;
 
     JSObject *templateObject = inspector->getTemplateObject(pc);
-    if (!templateObject || !templateObject->is<PlainObject>())
+    if (!templateObject)
+        return nullptr;
+    if (!templateObject->is<PlainObject>() && !templateObject->is<UnboxedPlainObject>())
         return nullptr;
     if (!templateObject->hasTenuredProto() || templateObject->getProto() != proto)
         return nullptr;
 
     types::TypeObjectKey *templateObjectType = types::TypeObjectKey::get(templateObject->type());
     if (templateObjectType->hasFlags(constraints(), types::OBJECT_FLAG_NEW_SCRIPT_CLEARED))
         return nullptr;
 
@@ -6176,17 +6178,17 @@ IonBuilder::jsop_compare(JSOp op)
     if (ins->isEffectful() && !resumeAfter(ins))
         return false;
     return true;
 }
 
 bool
 IonBuilder::jsop_newarray(uint32_t count)
 {
-    NativeObject *templateObject = inspector->getTemplateObject(pc);
+    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");
@@ -6213,19 +6215,19 @@ IonBuilder::jsop_newarray(uint32_t count
                                     NewArray_FullyAllocating);
     current->add(ins);
     current->push(ins);
 
     types::TemporaryTypeSet::DoubleConversion conversion =
         ins->resultTypeSet()->convertDoubleElements(constraints());
 
     if (conversion == types::TemporaryTypeSet::AlwaysConvertToDoubles)
-        templateObject->setShouldConvertDoubleElements();
+        templateObject->as<ArrayObject>().setShouldConvertDoubleElements();
     else
-        templateObject->clearShouldConvertDoubleElements();
+        templateObject->as<ArrayObject>().clearShouldConvertDoubleElements();
     return true;
 }
 
 bool
 IonBuilder::jsop_newarray_copyonwrite()
 {
     ArrayObject *templateObject = types::GetCopyOnWriteObject(script(), pc);
 
@@ -6806,17 +6808,18 @@ IonBuilder::maybeInsertResume()
     current->add(ins);
 
     return resumeAfter(ins);
 }
 
 static bool
 ClassHasEffectlessLookup(const Class *clasp, PropertyName *name)
 {
-    return clasp->isNative() && !clasp->ops.lookupGeneric;
+    return (clasp == &UnboxedPlainObject::class_) ||
+           (clasp->isNative() && !clasp->ops.lookupGeneric);
 }
 
 static bool
 ClassHasResolveHook(CompileCompartment *comp, const Class *clasp, PropertyName *name)
 {
     // While arrays do not have resolve hooks, the types of their |length|
     // properties are not reflected in type information, so pretend there is a
     // resolve hook for this property.
@@ -7736,19 +7739,23 @@ IonBuilder::pushReferenceLoadFromTypedOb
         load = MLoadElement::New(alloc(), elements, scaledOffset, false, false, adjustment);
         break;
       }
       case ReferenceTypeDescr::TYPE_OBJECT: {
         // Make sure the barrier reflects the possibility of reading null. When
         // there is no other barrier needed we include the null bailout with
         // MLoadUnboxedObjectOrNull, which avoids the need to box the result
         // for a type barrier instruction.
-        bool bailOnNull = barrier == BarrierKind::NoBarrier &&
-                          !observedTypes->hasType(types::Type::NullType());
-        load = MLoadUnboxedObjectOrNull::New(alloc(), elements, scaledOffset, bailOnNull, adjustment);
+        MLoadUnboxedObjectOrNull::NullBehavior nullBehavior;
+        if (barrier == BarrierKind::NoBarrier && !observedTypes->hasType(types::Type::NullType()))
+            nullBehavior = MLoadUnboxedObjectOrNull::BailOnNull;
+        else
+            nullBehavior = MLoadUnboxedObjectOrNull::HandleNull;
+        load = MLoadUnboxedObjectOrNull::New(alloc(), elements, scaledOffset, nullBehavior,
+                                             adjustment);
         break;
       }
       case ReferenceTypeDescr::TYPE_STRING: {
         load = MLoadUnboxedString::New(alloc(), elements, scaledOffset, adjustment);
         observedTypes->addType(types::Type::StringType(), alloc().lifoAlloc());
         break;
       }
     }
@@ -9239,24 +9246,76 @@ IonBuilder::getDefiniteSlot(types::Tempo
             trackOptimizationOutcome(TrackedOutcome::NotFixedSlot);
             return UINT32_MAX;
         }
 
         uint32_t propertySlot = property.maybeTypes()->definiteSlot();
         if (slot == UINT32_MAX) {
             slot = propertySlot;
         } else if (slot != propertySlot) {
-            trackOptimizationOutcome(TrackedOutcome::NotFixedSlot);
+            trackOptimizationOutcome(TrackedOutcome::InconsistentFixedSlot);
             return UINT32_MAX;
         }
     }
 
     return slot;
 }
 
+uint32_t
+IonBuilder::getUnboxedOffset(types::TemporaryTypeSet *types, PropertyName *name, JSValueType *punboxedType)
+{
+    if (!types || types->unknownObject()) {
+        trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
+        return UINT32_MAX;
+    }
+
+    uint32_t offset = UINT32_MAX;
+
+    for (size_t i = 0; i < types->getObjectCount(); i++) {
+        types::TypeObjectKey *type = types->getObject(i);
+        if (!type)
+            continue;
+
+        if (type->unknownProperties()) {
+            trackOptimizationOutcome(TrackedOutcome::UnknownProperties);
+            return UINT32_MAX;
+        }
+
+        if (type->singleton()) {
+            trackOptimizationOutcome(TrackedOutcome::Singleton);
+            return UINT32_MAX;
+        }
+
+        UnboxedLayout *layout = type->asTypeObject()->maybeUnboxedLayout();
+        if (!layout) {
+            trackOptimizationOutcome(TrackedOutcome::NotUnboxed);
+            return UINT32_MAX;
+        }
+
+        const UnboxedLayout::Property *property = layout->lookup(name);
+        if (!property) {
+            trackOptimizationOutcome(TrackedOutcome::StructNoField);
+            return UINT32_MAX;
+        }
+
+        if (offset == UINT32_MAX) {
+            offset = property->offset;
+            *punboxedType = property->type;
+        } else if (offset != property->offset) {
+            trackOptimizationOutcome(TrackedOutcome::InconsistentFieldOffset);
+            return UINT32_MAX;
+        } else if (*punboxedType != property->type) {
+            trackOptimizationOutcome(TrackedOutcome::InconsistentFieldType);
+            return UINT32_MAX;
+        }
+    }
+
+    return offset;
+}
+
 bool
 IonBuilder::jsop_runonce()
 {
     MRunOncePrologue *ins = MRunOncePrologue::New(alloc());
     current->add(ins);
     return resumeAfter(ins);
 }
 
@@ -9674,16 +9733,21 @@ IonBuilder::jsop_getprop(PropertyName *n
     if (!getPropTryTypedObject(&emitted, obj, name) || emitted)
         return emitted;
 
     // Try to emit loads from definite slots.
     trackOptimizationAttempt(TrackedStrategy::GetProp_DefiniteSlot);
     if (!getPropTryDefiniteSlot(&emitted, obj, name, barrier, types) || emitted)
         return emitted;
 
+    // Try to emit loads from unboxed objects.
+    trackOptimizationAttempt(TrackedStrategy::GetProp_Unboxed);
+    if (!getPropTryUnboxed(&emitted, obj, name, barrier, types) || emitted)
+        return emitted;
+
     // Try to inline a common property getter, or make a call.
     trackOptimizationAttempt(TrackedStrategy::GetProp_CommonGetter);
     if (!getPropTryCommonGetter(&emitted, obj, name, types) || emitted)
         return emitted;
 
     // Try to emit a monomorphic/polymorphic access based on baseline caches.
     trackOptimizationAttempt(TrackedStrategy::GetProp_InlineAccess);
     if (!getPropTryInlineAccess(&emitted, obj, name, barrier, types) || emitted)
@@ -10002,16 +10066,102 @@ IonBuilder::getPropTryDefiniteSlot(bool 
     if (!pushTypeBarrier(load, types, barrier))
         return false;
 
     trackOptimizationSuccess();
     *emitted = true;
     return true;
 }
 
+MInstruction *
+IonBuilder::loadUnboxedProperty(MDefinition *obj, size_t offset, JSValueType unboxedType,
+                                BarrierKind barrier, types::TemporaryTypeSet *types)
+{
+    size_t scaledOffsetConstant = offset / UnboxedTypeSize(unboxedType);
+    MInstruction *scaledOffset = MConstant::New(alloc(), Int32Value(scaledOffsetConstant));
+    current->add(scaledOffset);
+
+    MInstruction *load;
+    switch (unboxedType) {
+      case JSVAL_TYPE_BOOLEAN:
+        load = MLoadTypedArrayElement::New(alloc(), obj, scaledOffset, Scalar::Uint8,
+                                           DoesNotRequireMemoryBarrier,
+                                           UnboxedPlainObject::offsetOfData());
+        load->setResultType(MIRType_Boolean);
+        break;
+
+      case JSVAL_TYPE_INT32:
+        load = MLoadTypedArrayElement::New(alloc(), obj, scaledOffset, Scalar::Int32,
+                                           DoesNotRequireMemoryBarrier,
+                                           UnboxedPlainObject::offsetOfData());
+        load->setResultType(MIRType_Int32);
+        break;
+
+      case JSVAL_TYPE_DOUBLE:
+        load = MLoadTypedArrayElement::New(alloc(), obj, scaledOffset, Scalar::Float64,
+                                           DoesNotRequireMemoryBarrier,
+                                           UnboxedPlainObject::offsetOfData(),
+                                           /* canonicalizeDoubles = */ false);
+        load->setResultType(MIRType_Double);
+        break;
+
+      case JSVAL_TYPE_STRING:
+        load = MLoadUnboxedString::New(alloc(), obj, scaledOffset,
+                                       UnboxedPlainObject::offsetOfData());
+        break;
+
+      case JSVAL_TYPE_OBJECT: {
+        MLoadUnboxedObjectOrNull::NullBehavior nullBehavior;
+        if (types->hasType(types::Type::NullType()) || barrier != BarrierKind::NoBarrier)
+            nullBehavior = MLoadUnboxedObjectOrNull::HandleNull;
+        else
+            nullBehavior = MLoadUnboxedObjectOrNull::NullNotPossible;
+        load = MLoadUnboxedObjectOrNull::New(alloc(), obj, scaledOffset, nullBehavior,
+                                             UnboxedPlainObject::offsetOfData());
+        break;
+      }
+
+      default:
+        MOZ_CRASH();
+    }
+
+    current->add(load);
+    return load;
+}
+
+bool
+IonBuilder::getPropTryUnboxed(bool *emitted, MDefinition *obj, PropertyName *name,
+                              BarrierKind barrier, types::TemporaryTypeSet *types)
+{
+    MOZ_ASSERT(*emitted == false);
+
+    JSValueType unboxedType;
+    uint32_t offset = getUnboxedOffset(obj->resultTypeSet(), name, &unboxedType);
+    if (offset == UINT32_MAX)
+        return true;
+
+    if (obj->type() != MIRType_Object) {
+        MGuardObject *guard = MGuardObject::New(alloc(), obj);
+        current->add(guard);
+        obj = guard;
+    }
+
+    if (unboxedType != JSVAL_TYPE_OBJECT)
+        barrier = BarrierKind::NoBarrier;
+
+    MInstruction *load = loadUnboxedProperty(obj, offset, unboxedType, barrier, types);
+    current->push(load);
+
+    if (!pushTypeBarrier(load, types, barrier))
+        return false;
+
+    *emitted = true;
+    return true;
+}
+
 bool
 IonBuilder::getPropTryCommonGetter(bool *emitted, MDefinition *obj, PropertyName *name,
                                    types::TemporaryTypeSet *types)
 {
     MOZ_ASSERT(*emitted == false);
 
     Shape *lastProperty = nullptr;
     JSFunction *commonGetter = nullptr;
@@ -10130,36 +10280,43 @@ IonBuilder::getPropTryCommonGetter(bool 
     // couldn't inline.
     if (!commonGetter->isInterpreted())
         trackOptimizationSuccess();
 
     *emitted = true;
     return true;
 }
 
-static bool
-CanInlinePropertyOpShapes(const BaselineInspector::ShapeVector &shapes)
-{
-    for (size_t i = 0; i < shapes.length(); i++) {
+bool
+IonBuilder::canInlinePropertyOpShapes(const BaselineInspector::ShapeVector &nativeShapes,
+                                      const BaselineInspector::TypeObjectVector &unboxedTypes)
+{
+    if (nativeShapes.empty() && unboxedTypes.empty()) {
+        trackOptimizationOutcome(TrackedOutcome::NoShapeInfo);
+        return false;
+    }
+
+    for (size_t i = 0; i < nativeShapes.length(); i++) {
         // We inline the property access as long as the shape is not in
         // dictionary mode. We cannot be sure that the shape is still a
         // lastProperty, and calling Shape::search() on dictionary mode
         // shapes that aren't lastProperty is invalid.
-        if (shapes[i]->inDictionary())
+        if (nativeShapes[i]->inDictionary()) {
+            trackOptimizationOutcome(TrackedOutcome::InDictionaryMode);
             return false;
+        }
     }
 
     return true;
 }
 
 static bool
 GetPropertyShapes(jsid id, const BaselineInspector::ShapeVector &shapes,
                   BaselineInspector::ShapeVector &propShapes, bool *sameSlot)
 {
-    MOZ_ASSERT(shapes.length() > 1);
     MOZ_ASSERT(propShapes.empty());
 
     if (!propShapes.reserve(shapes.length()))
         return false;
 
     *sameSlot = true;
     for (size_t i = 0; i < shapes.length(); i++) {
         Shape *objShape = shapes[i];
@@ -10185,88 +10342,111 @@ IonBuilder::getPropTryInlineAccess(bool 
 {
     MOZ_ASSERT(*emitted == false);
 
     if (obj->type() != MIRType_Object) {
         trackOptimizationOutcome(TrackedOutcome::NotObject);
         return true;
     }
 
-    BaselineInspector::ShapeVector shapes(alloc());
-    if (!inspector->maybeShapesForPropertyOp(pc, shapes))
-        return false;
-
-    if (shapes.empty()) {
-        trackOptimizationOutcome(TrackedOutcome::NoShapeInfo);
-        return true;
-    }
-
-    if (!CanInlinePropertyOpShapes(shapes)) {
-        trackOptimizationOutcome(TrackedOutcome::InDictionaryMode);
-        return true;
-    }
+    BaselineInspector::ShapeVector nativeShapes(alloc());
+    BaselineInspector::TypeObjectVector unboxedTypes(alloc());
+    if (!inspector->maybeInfoForPropertyOp(pc, nativeShapes, unboxedTypes))
+        return false;
+
+    if (!canInlinePropertyOpShapes(nativeShapes, unboxedTypes))
+        return true;
 
     MIRType rvalType = types->getKnownMIRType();
     if (barrier != BarrierKind::NoBarrier || IsNullOrUndefined(rvalType))
         rvalType = MIRType_Value;
 
-    if (shapes.length() == 1) {
+    if (nativeShapes.length() == 1 && unboxedTypes.empty()) {
         // In the monomorphic case, use separate ShapeGuard and LoadSlot
         // instructions.
         spew("Inlining monomorphic GETPROP");
 
-        Shape *objShape = shapes[0];
+        Shape *objShape = nativeShapes[0];
         obj = addShapeGuard(obj, objShape, Bailout_ShapeGuard);
 
         Shape *shape = objShape->searchLinear(NameToId(name));
         MOZ_ASSERT(shape);
 
         if (!loadSlot(obj, shape, rvalType, barrier, types))
             return false;
 
         trackOptimizationOutcome(TrackedOutcome::Monomorphic);
         *emitted = true;
         return true;
     }
 
-    MOZ_ASSERT(shapes.length() > 1);
+    if (nativeShapes.empty() && unboxedTypes.length() == 1) {
+        spew("Inlining monomorphic unboxed GETPROP");
+
+        types::TypeObject *unboxedType = unboxedTypes[0];
+
+        // Failures in this type guard should be treated the same as a shape guard failure.
+        obj = MGuardObjectType::New(alloc(), obj, unboxedType, /* bailOnEquality = */ false,
+                                    Bailout_ShapeGuard);
+        current->add(obj->toInstruction());
+
+        if (failedShapeGuard_)
+            obj->toGuardObjectType()->setNotMovable();
+
+        const UnboxedLayout::Property *property = unboxedType->unboxedLayout().lookup(name);
+        MInstruction *load = loadUnboxedProperty(obj, property->offset, property->type, barrier, types);
+        current->push(load);
+
+        if (!pushTypeBarrier(load, types, barrier))
+            return false;
+
+        *emitted = true;
+        return true;
+    }
+
+    MOZ_ASSERT(nativeShapes.length() + unboxedTypes.length() > 1);
     spew("Inlining polymorphic GETPROP");
 
     BaselineInspector::ShapeVector propShapes(alloc());
     bool sameSlot;
-    if (!GetPropertyShapes(NameToId(name), shapes, propShapes, &sameSlot))
-        return false;
-
-    if (sameSlot) {
+    if (!GetPropertyShapes(NameToId(name), nativeShapes, propShapes, &sameSlot))
+        return false;
+
+    if (sameSlot && unboxedTypes.empty()) {
         MGuardShapePolymorphic *guard = MGuardShapePolymorphic::New(alloc(), obj);
         current->add(guard);
         obj = guard;
 
         if (failedShapeGuard_)
             guard->setNotMovable();
 
-        for (size_t i = 0; i < shapes.length(); i++) {
-            if (!guard->addShape(shapes[i]))
+        for (size_t i = 0; i < nativeShapes.length(); i++) {
+            if (!guard->addShape(nativeShapes[i]))
                 return false;
         }
 
         if (!loadSlot(obj, propShapes[0], rvalType, barrier, types))
             return false;
 
         trackOptimizationOutcome(TrackedOutcome::Polymorphic);
         *emitted = true;
         return true;
     }
 
     MGetPropertyPolymorphic *load = MGetPropertyPolymorphic::New(alloc(), obj, name);
     current->add(load);
     current->push(load);
 
-    for (size_t i = 0; i < shapes.length(); i++) {
-        if (!load->addShape(shapes[i], propShapes[i]))
+    for (size_t i = 0; i < nativeShapes.length(); i++) {
+        if (!load->addShape(nativeShapes[i], propShapes[i]))
+            return false;
+    }
+
+    for (size_t i = 0; i < unboxedTypes.length(); i++) {
+        if (!load->addUnboxedType(unboxedTypes[i]))
             return false;
     }
 
     if (failedShapeGuard_)
         load->setNotMovable();
 
     load->setResultType(rvalType);
     if (!pushTypeBarrier(load, types, barrier))
@@ -10442,34 +10622,40 @@ IonBuilder::jsop_setprop(PropertyName *n
     if (info().isAnalysis()) {
         bool strict = IsStrictSetPC(pc);
         MInstruction *ins = MCallSetProperty::New(alloc(), obj, value, name, strict);
         current->add(ins);
         current->push(value);
         return resumeAfter(ins);
     }
 
-    // Add post barrier if needed.
-    if (NeedsPostBarrier(info(), value))
-        current->add(MPostWriteBarrier::New(alloc(), obj, value));
-
     // Try to inline a common property setter, or make a call.
     trackOptimizationAttempt(TrackedStrategy::SetProp_CommonSetter);
     if (!setPropTryCommonSetter(&emitted, obj, name, value) || emitted)
         return emitted;
 
     // Try to emit stores to known binary data blocks
     trackOptimizationAttempt(TrackedStrategy::SetProp_TypedObject);
     if (!setPropTryTypedObject(&emitted, obj, name, value) || emitted)
         return emitted;
 
     types::TemporaryTypeSet *objTypes = obj->resultTypeSet();
     bool barrier = PropertyWriteNeedsTypeBarrier(alloc(), constraints(), current, &obj, name, &value,
                                                  /* canModify = */ true);
 
+    // Try to emit stores to unboxed objects.
+    trackOptimizationAttempt(TrackedStrategy::SetProp_Unboxed);
+    if (!setPropTryUnboxed(&emitted, obj, name, value, barrier, objTypes) || emitted)
+        return emitted;
+
+    // Add post barrier if needed. The instructions above manage any post
+    // barriers they need directly.
+    if (NeedsPostBarrier(info(), value))
+        current->add(MPostWriteBarrier::New(alloc(), obj, value));
+
     // Try to emit store from definite slots.
     trackOptimizationAttempt(TrackedStrategy::SetProp_DefiniteSlot);
     if (!setPropTryDefiniteSlot(&emitted, obj, name, value, barrier, objTypes) || emitted)
         return emitted;
 
     // Try to emit a monomorphic/polymorphic store based on baseline caches.
     trackOptimizationAttempt(TrackedStrategy::SetProp_InlineAccess);
     if (!setPropTryInlineAccess(&emitted, obj, name, value, barrier, objTypes) || emitted)
@@ -10745,106 +10931,207 @@ IonBuilder::setPropTryDefiniteSlot(bool 
     if (!resumeAfter(store))
         return false;
 
     trackOptimizationSuccess();
     *emitted = true;
     return true;
 }
 
+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);
+
+    MInstruction *store;
+    switch (unboxedType) {
+      case JSVAL_TYPE_BOOLEAN:
+        store = MStoreTypedArrayElement::New(alloc(), obj, scaledOffset, value, Scalar::Uint8,
+                                             DoesNotRequireMemoryBarrier,
+                                             UnboxedPlainObject::offsetOfData());
+        break;
+
+      case JSVAL_TYPE_INT32:
+        store = MStoreTypedArrayElement::New(alloc(), obj, scaledOffset, value, Scalar::Int32,
+                                             DoesNotRequireMemoryBarrier,
+                                             UnboxedPlainObject::offsetOfData());
+        break;
+
+      case JSVAL_TYPE_DOUBLE:
+        store = MStoreTypedArrayElement::New(alloc(), obj, scaledOffset, value, Scalar::Float64,
+                                             DoesNotRequireMemoryBarrier,
+                                             UnboxedPlainObject::offsetOfData());
+        break;
+
+      case JSVAL_TYPE_STRING:
+        store = MStoreUnboxedString::New(alloc(), obj, scaledOffset, value,
+                                         UnboxedPlainObject::offsetOfData());
+        break;
+
+      case JSVAL_TYPE_OBJECT:
+        store = MStoreUnboxedObjectOrNull::New(alloc(), obj, scaledOffset, value, obj,
+                                               UnboxedPlainObject::offsetOfData());
+        break;
+
+      default:
+        MOZ_CRASH();
+    }
+
+    current->add(store);
+    return store;
+}
+
+bool
+IonBuilder::setPropTryUnboxed(bool *emitted, MDefinition *obj,
+                              PropertyName *name, MDefinition *value,
+                              bool barrier, types::TemporaryTypeSet *objTypes)
+{
+    MOZ_ASSERT(*emitted == false);
+
+    if (barrier) {
+        trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
+        return true;
+    }
+
+    JSValueType unboxedType;
+    uint32_t offset = getUnboxedOffset(obj->resultTypeSet(), name, &unboxedType);
+    if (offset == UINT32_MAX)
+        return true;
+
+    if (obj->type() != MIRType_Object) {
+        MGuardObject *guard = MGuardObject::New(alloc(), obj);
+        current->add(guard);
+        obj = guard;
+    }
+
+    MInstruction *store = storeUnboxedProperty(obj, offset, unboxedType, value);
+
+    current->push(value);
+
+    if (!resumeAfter(store))
+        return false;
+
+    *emitted = true;
+    return true;
+}
+
 bool
 IonBuilder::setPropTryInlineAccess(bool *emitted, MDefinition *obj,
                                    PropertyName *name, MDefinition *value,
                                    bool barrier, types::TemporaryTypeSet *objTypes)
 {
     MOZ_ASSERT(*emitted == false);
 
     if (barrier) {
         trackOptimizationOutcome(TrackedOutcome::NeedsTypeBarrier);
         return true;
     }
 
-    BaselineInspector::ShapeVector shapes(alloc());
-    if (!inspector->maybeShapesForPropertyOp(pc, shapes))
-        return false;
-
-    if (shapes.empty()) {
-        trackOptimizationOutcome(TrackedOutcome::NoShapeInfo);
-        return true;
-    }
-
-    if (!CanInlinePropertyOpShapes(shapes)) {
-        trackOptimizationOutcome(TrackedOutcome::InDictionaryMode);
-        return true;
-    }
-
-    if (shapes.length() == 1) {
+    BaselineInspector::ShapeVector nativeShapes(alloc());
+    BaselineInspector::TypeObjectVector unboxedTypes(alloc());
+    if (!inspector->maybeInfoForPropertyOp(pc, nativeShapes, unboxedTypes))
+        return false;
+
+    if (!canInlinePropertyOpShapes(nativeShapes, unboxedTypes))
+        return true;
+
+    if (nativeShapes.length() == 1 && unboxedTypes.empty()) {
         spew("Inlining monomorphic SETPROP");
 
         // The Baseline IC was monomorphic, so we inline the property access as
         // long as the shape is not in dictionary mode. We cannot be sure
         // that the shape is still a lastProperty, and calling Shape::search
         // on dictionary mode shapes that aren't lastProperty is invalid.
-        Shape *objShape = shapes[0];
+        Shape *objShape = nativeShapes[0];
         obj = addShapeGuard(obj, objShape, Bailout_ShapeGuard);
 
         Shape *shape = objShape->searchLinear(NameToId(name));
         MOZ_ASSERT(shape);
 
         bool needsBarrier = objTypes->propertyNeedsBarrier(constraints(), NameToId(name));
         if (!storeSlot(obj, shape, value, needsBarrier))
             return false;
 
         trackOptimizationOutcome(TrackedOutcome::Monomorphic);
         *emitted = true;
         return true;
     }
 
-    MOZ_ASSERT(shapes.length() > 1);
+    if (nativeShapes.empty() && unboxedTypes.length() == 1) {
+        spew("Inlining monomorphic unboxed SETPROP");
+
+        types::TypeObject *unboxedType = unboxedTypes[0];
+
+        // Failures in this type guard should be treated the same as a shape guard failure.
+        obj = MGuardObjectType::New(alloc(), obj, unboxedType, /* bailOnEquality = */ false,
+                                    Bailout_ShapeGuard);
+        current->add(obj->toInstruction());
+
+        if (failedShapeGuard_)
+            obj->toGuardObjectType()->setNotMovable();
+
+        const UnboxedLayout::Property *property = unboxedType->unboxedLayout().lookup(name);
+        storeUnboxedProperty(obj, property->offset, property->type, value);
+
+        current->push(value);
+
+        *emitted = true;
+        return true;
+    }
+
+    MOZ_ASSERT(nativeShapes.length() + unboxedTypes.length() > 1);
     spew("Inlining polymorphic SETPROP");
 
     BaselineInspector::ShapeVector propShapes(alloc());
     bool sameSlot;
-    if (!GetPropertyShapes(NameToId(name), shapes, propShapes, &sameSlot))
-        return false;
-
-    if (sameSlot) {
+    if (!GetPropertyShapes(NameToId(name), nativeShapes, propShapes, &sameSlot))
+        return false;
+
+    if (sameSlot && unboxedTypes.empty()) {
         MGuardShapePolymorphic *guard = MGuardShapePolymorphic::New(alloc(), obj);
         current->add(guard);
         obj = guard;
 
         if (failedShapeGuard_)
             guard->setNotMovable();
 
-        for (size_t i = 0; i < shapes.length(); i++) {
-            if (!guard->addShape(shapes[i]))
+        for (size_t i = 0; i < nativeShapes.length(); i++) {
+            if (!guard->addShape(nativeShapes[i]))
                 return false;
         }
 
         bool needsBarrier = objTypes->propertyNeedsBarrier(constraints(), NameToId(name));
         if (!storeSlot(obj, propShapes[0], value, needsBarrier))
             return false;
 
         trackOptimizationOutcome(TrackedOutcome::Polymorphic);
         *emitted = true;
         return true;
     }
 
-    MSetPropertyPolymorphic *ins = MSetPropertyPolymorphic::New(alloc(), obj, value);
+    MSetPropertyPolymorphic *ins = MSetPropertyPolymorphic::New(alloc(), obj, value, name);
     current->add(ins);
     current->push(value);
 
-    for (size_t i = 0; i < shapes.length(); i++) {
-        Shape *objShape = shapes[i];
+    for (size_t i = 0; i < nativeShapes.length(); i++) {
+        Shape *objShape = nativeShapes[i];
         Shape *shape =  objShape->searchLinear(NameToId(name));
         MOZ_ASSERT(shape);
         if (!ins->addShape(objShape, shape))
             return false;
     }
 
+    for (size_t i = 0; i < unboxedTypes.length(); i++) {
+        if (!ins->addUnboxedType(unboxedTypes[i]))
+            return false;
+    }
+
     if (objTypes->propertyNeedsBarrier(constraints(), NameToId(name)))
         ins->setNeedsBarrier();
 
     if (!resumeAfter(ins))
         return false;
 
     trackOptimizationOutcome(TrackedOutcome::Polymorphic);
     *emitted = true;
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -7,30 +7,30 @@
 #ifndef jit_IonBuilder_h
 #define jit_IonBuilder_h
 
 // This file declares the data structures for building a MIRGraph from a
 // JSScript.
 
 #include "mozilla/LinkedList.h"
 
+#include "jit/BaselineInspector.h"
 #include "jit/BytecodeAnalysis.h"
 #include "jit/IonAnalysis.h"
 #include "jit/IonOptimizationLevels.h"
 #include "jit/MIR.h"
 #include "jit/MIRGenerator.h"
 #include "jit/MIRGraph.h"
 #include "jit/OptimizationTracking.h"
 
 namespace js {
 namespace jit {
 
 class CodeGenerator;
 class CallInfo;
-class BaselineInspector;
 class BaselineFrameInspector;
 
 // Records information about a baseline frame for compilation that is stable
 // when later used off thread.
 BaselineFrameInspector *
 NewBaselineFrameInspector(TempAllocator *temp, BaselineFrame *frame, CompileInfo *info);
 
 class IonBuilder
@@ -418,16 +418,18 @@ class IonBuilder
     bool getPropTryInferredConstant(bool *emitted, MDefinition *obj, PropertyName *name,
                                     types::TemporaryTypeSet *types);
     bool getPropTryArgumentsLength(bool *emitted, MDefinition *obj);
     bool getPropTryArgumentsCallee(bool *emitted, MDefinition *obj, PropertyName *name);
     bool getPropTryConstant(bool *emitted, MDefinition *obj, PropertyName *name,
                             types::TemporaryTypeSet *types);
     bool getPropTryDefiniteSlot(bool *emitted, MDefinition *obj, PropertyName *name,
                                 BarrierKind barrier, types::TemporaryTypeSet *types);
+    bool getPropTryUnboxed(bool *emitted, MDefinition *obj, PropertyName *name,
+                           BarrierKind barrier, types::TemporaryTypeSet *types);
     bool getPropTryCommonGetter(bool *emitted, MDefinition *obj, PropertyName *name,
                                 types::TemporaryTypeSet *types);
     bool getPropTryInlineAccess(bool *emitted, MDefinition *obj, PropertyName *name,
                                 BarrierKind barrier, types::TemporaryTypeSet *types);
     bool getPropTryTypedObject(bool *emitted, MDefinition *obj, PropertyName *name);
     bool getPropTryScalarPropOfTypedObject(bool *emitted, MDefinition *typedObj,
                                            int32_t fieldOffset,
                                            TypedObjectPrediction fieldTypeReprs);
@@ -448,16 +450,19 @@ class IonBuilder
     bool setPropTryCommonSetter(bool *emitted, MDefinition *obj,
                                 PropertyName *name, MDefinition *value);
     bool setPropTryCommonDOMSetter(bool *emitted, MDefinition *obj,
                                    MDefinition *value, JSFunction *setter,
                                    bool isDOM);
     bool setPropTryDefiniteSlot(bool *emitted, MDefinition *obj,
                                 PropertyName *name, MDefinition *value,
                                 bool barrier, types::TemporaryTypeSet *objTypes);
+    bool setPropTryUnboxed(bool *emitted, MDefinition *obj,
+                           PropertyName *name, MDefinition *value,
+                           bool barrier, types::TemporaryTypeSet *objTypes);
     bool setPropTryInlineAccess(bool *emitted, MDefinition *obj,
                                 PropertyName *name, MDefinition *value,
                                 bool barrier, types::TemporaryTypeSet *objTypes);
     bool setPropTryTypedObject(bool *emitted, MDefinition *obj,
                                PropertyName *name, MDefinition *value);
     bool setPropTryReferencePropOfTypedObject(bool *emitted,
                                               MDefinition *obj,
                                               int32_t fieldOffset,
@@ -873,18 +878,26 @@ class IonBuilder
                                   types::TemporaryTypeSet *pushedTypes);
 
     MGetPropertyCache *getInlineableGetPropertyCache(CallInfo &callInfo);
 
     JSObject *testSingletonProperty(JSObject *obj, PropertyName *name);
     bool testSingletonPropertyTypes(MDefinition *obj, JSObject *singleton, PropertyName *name,
                                     bool *testObject, bool *testString);
     uint32_t getDefiniteSlot(types::TemporaryTypeSet *types, PropertyName *name);
+    uint32_t getUnboxedOffset(types::TemporaryTypeSet *types, PropertyName *name,
+                              JSValueType *punboxedType);
+    MInstruction *loadUnboxedProperty(MDefinition *obj, size_t offset, JSValueType unboxedType,
+                                      BarrierKind barrier, types::TemporaryTypeSet *types);
+    MInstruction *storeUnboxedProperty(MDefinition *obj, size_t offset, JSValueType unboxedType,
+                                       MDefinition *value);
     bool freezePropTypeSets(types::TemporaryTypeSet *types,
                             JSObject *foundProto, PropertyName *name);
+    bool canInlinePropertyOpShapes(const BaselineInspector::ShapeVector &nativeShapes,
+                                   const BaselineInspector::TypeObjectVector &unboxedTypes);
 
     types::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
     // where the block cannot have phis whose type needs to be computed.
 
     bool setCurrentAndSpecializePhis(MBasicBlock *block) {
--- a/js/src/jit/IonCaches.cpp
+++ b/js/src/jit/IonCaches.cpp
@@ -764,33 +764,39 @@ GenerateDOMProxyChecks(JSContext *cx, Ma
 }
 
 static void
 GenerateReadSlot(JSContext *cx, IonScript *ion, MacroAssembler &masm,
                  IonCache::StubAttacher &attacher, JSObject *obj, NativeObject *holder,
                  Shape *shape, Register object, TypedOrValueRegister output,
                  Label *failures = nullptr)
 {
-    MOZ_ASSERT(obj->isNative());
     // If there's a single jump to |failures|, we can patch the shape guard
     // jump directly. Otherwise, jump to the end of the stub, so there's a
     // common point to patch.
     bool multipleFailureJumps = (obj != holder) || (failures != nullptr && failures->used());
 
     // If we have multiple failure jumps but didn't get a label from the
     // outside, make one ourselves.
     Label failures_;
     if (multipleFailureJumps && !failures)
         failures = &failures_;
 
-    // Guard on the shape of the object.
-    attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
-                                   Address(object, JSObject::offsetOfShape()),
-                                   ImmGCPtr(obj->lastProperty()),
-                                   failures);
+    // Guard on the shape or type of the object, depending on whether it is native.
+    if (obj->isNative()) {
+        attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
+                                       Address(object, JSObject::offsetOfShape()),
+                                       ImmGCPtr(obj->lastProperty()),
+                                       failures);
+    } else {
+        attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
+                                       Address(object, JSObject::offsetOfType()),
+                                       ImmGCPtr(obj->type()),
+                                       failures);
+    }
 
     // If we need a scratch register, use either an output register or the
     // object register. After this point, we cannot jump directly to
     // |failures| since we may still have to pop the object register.
     bool restoreScratch = false;
     Register scratchReg = Register::FromCode(0); // Quell compiler warning.
 
     if (obj != holder || !holder->isFixedSlot(shape->slot())) {
@@ -871,16 +877,34 @@ GenerateReadSlot(JSContext *cx, IonScrip
     if (restoreScratch)
         masm.pop(scratchReg);
     masm.bind(failures);
 
     attacher.jumpNextStub(masm);
 
 }
 
+static void
+GenerateReadUnboxed(JSContext *cx, IonScript *ion, MacroAssembler &masm,
+                    IonCache::StubAttacher &attacher, JSObject *obj,
+                    const UnboxedLayout::Property *property,
+                    Register object, TypedOrValueRegister output)
+{
+    // Guard on the type of the object.
+    attacher.branchNextStub(masm, Assembler::NotEqual,
+                            Address(object, JSObject::offsetOfType()),
+                            ImmGCPtr(obj->type()));
+
+    Address address(object, UnboxedPlainObject::offsetOfData() + property->offset);
+
+    masm.loadUnboxedProperty(address, property->type, output);
+
+    attacher.jumpRejoin(masm);
+}
+
 static bool
 EmitGetterCall(JSContext *cx, MacroAssembler &masm,
                IonCache::StubAttacher &attacher, JSObject *obj,
                JSObject *holder, HandleShape shape,
                RegisterSet liveRegs, Register object,
                TypedOrValueRegister output,
                void *returnAddr)
 {
@@ -1165,17 +1189,17 @@ IsCacheableArrayLength(JSContext *cx, Ha
 
 template <class GetPropCache>
 static GetPropertyIC::NativeGetPropCacheability
 CanAttachNativeGetProp(typename GetPropCache::Context cx, const GetPropCache &cache,
                        HandleObject obj, HandlePropertyName name,
                        MutableHandleNativeObject holder, MutableHandleShape shape,
                        bool skipArrayLen = false)
 {
-    if (!obj || !obj->isNative())
+    if (!obj)
         return GetPropertyIC::CanAttachNone;
 
     // The lookup needs to be universally pure, otherwise we risk calling hooks out
     // of turn. We don't mind doing this even when purity isn't required, because we
     // only miss out on shape hashification, which is only a temporary perf cost.
     // The limits were arbitrarily set, anyways.
     JSObject *baseHolder = nullptr;
     if (!LookupPropertyPure(cx, obj, NameToId(name), &baseHolder, shape.address()))
@@ -1301,16 +1325,40 @@ GetPropertyIC::tryAttachNative(JSContext
         break;
       default:
         MOZ_CRASH("Bad NativeGetPropCacheability");
     }
     return linkAndAttachStub(cx, masm, attacher, ion, attachKind);
 }
 
 bool
+GetPropertyIC::tryAttachUnboxed(JSContext *cx, HandleScript outerScript, IonScript *ion,
+                                HandleObject obj, HandlePropertyName name,
+                                void *returnAddr, bool *emitted)
+{
+    MOZ_ASSERT(canAttachStub());
+    MOZ_ASSERT(!*emitted);
+    MOZ_ASSERT(outerScript->ionScript() == ion);
+
+    if (!obj->is<UnboxedPlainObject>())
+        return true;
+    const UnboxedLayout::Property *property = obj->as<UnboxedPlainObject>().layout().lookup(name);
+    if (!property)
+        return true;
+
+    *emitted = true;
+
+    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+
+    RepatchStubAppender attacher(*this);
+    GenerateReadUnboxed(cx, ion, masm, attacher, obj, property, object(), output());
+    return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed");
+}
+
+bool
 GetPropertyIC::tryAttachTypedArrayLength(JSContext *cx, HandleScript outerScript, IonScript *ion,
                                          HandleObject obj, HandlePropertyName name, bool *emitted)
 {
     MOZ_ASSERT(canAttachStub());
     MOZ_ASSERT(!*emitted);
 
     if (!IsAnyTypedArray(obj))
         return true;
@@ -1724,19 +1772,25 @@ GetPropertyIC::tryAttachStub(JSContext *
         return false;
 
     if (!*emitted && !tryAttachProxy(cx, outerScript, ion, obj, name, returnAddr, emitted))
         return false;
 
     if (!*emitted && !tryAttachNative(cx, outerScript, ion, obj, name, returnAddr, emitted))
         return false;
 
+    if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, name, returnAddr, emitted))
+        return false;
+
     if (!*emitted && !tryAttachTypedArrayLength(cx, outerScript, ion, obj, name, emitted))
         return false;
 
+    if (!*emitted)
+        JitSpew(JitSpew_IonIC, "Failed to attach GETPROP cache");
+
     return true;
 }
 
 /* static */ bool
 GetPropertyIC::update(JSContext *cx, size_t cacheIndex,
                       HandleObject obj, MutableHandleValue vp)
 {
     void *returnAddr;
@@ -1831,16 +1885,36 @@ IonCache::reset()
     this->stubCount_ = 0;
 }
 
 void
 IonCache::destroy()
 {
 }
 
+// Jump to failure if a value being written is not a property for obj/id.
+// This might clobber |object|.
+static void
+CheckTypeSetForWrite(MacroAssembler &masm, JSObject *obj, jsid id,
+                     Register object, ConstantOrRegister value, Label *failure)
+{
+    TypedOrValueRegister valReg = value.reg();
+    types::TypeObject *type = obj->type();
+    if (type->unknownProperties())
+        return;
+    types::HeapTypeSet *propTypes = type->maybeGetProperty(id);
+    MOZ_ASSERT(propTypes);
+
+    // guardTypeSet can read from type sets without triggering read barriers.
+    types::TypeSet::readBarrier(propTypes);
+
+    Register scratch = object;
+    masm.guardTypeSet(valReg, propTypes, BarrierKind::TypeSet, scratch, failure);
+}
+
 static void
 GenerateSetSlot(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher,
                 NativeObject *obj, Shape *shape, Register object, ConstantOrRegister value,
                 bool needsTypeBarrier, bool checkTypeset)
 {
     MOZ_ASSERT(obj->isNative());
 
     Label failures, barrierFailure;
@@ -1856,28 +1930,18 @@ GenerateSetSlot(JSContext *cx, MacroAsse
 
         // Obtain and guard on the TypeObject of the object.
         types::TypeObject *type = obj->type();
         masm.branchPtr(Assembler::NotEqual,
                        Address(object, JSObject::offsetOfType()),
                        ImmGCPtr(type), &failures);
 
         if (checkTypeset) {
-            TypedOrValueRegister valReg = value.reg();
-            types::HeapTypeSet *propTypes = type->maybeGetProperty(shape->propid());
-            MOZ_ASSERT(propTypes);
-            MOZ_ASSERT(!propTypes->unknown());
-
-            // guardTypeSet can read from type sets without triggering read barriers.
-            types::TypeSet::readBarrier(propTypes);
-
-            Register scratchReg = object;
-            masm.push(scratchReg);
-
-            masm.guardTypeSet(valReg, propTypes, BarrierKind::TypeSet, scratchReg, &barrierFailure);
+            masm.push(object);
+            CheckTypeSetForWrite(masm, obj, shape->propid(), object, value, &barrierFailure);
             masm.pop(object);
         }
     }
 
     NativeObject::slotsSizeMustNotOverflow();
     if (obj->isFixedSlot(shape->slot())) {
         Address addr(object, NativeObject::getFixedSlotOffset(shape->slot()));
 
@@ -2415,27 +2479,17 @@ GenerateAddSlot(JSContext *cx, MacroAsse
     masm.branchTestObjShape(Assembler::NotEqual, object, oldShape, &failures);
 
     Label failuresPopObject;
     masm.push(object);    // save object reg because we clobber it
 
     // Guard that the incoming value is in the type set for the property
     // if a type barrier is required.
     if (checkTypeset) {
-        TypedOrValueRegister valReg = value.reg();
-        types::TypeObject *type = obj->type();
-        types::HeapTypeSet *propTypes = type->maybeGetProperty(obj->lastProperty()->propid());
-        MOZ_ASSERT(propTypes);
-        MOZ_ASSERT(!propTypes->unknown());
-
-        // guardTypeSet can read from type sets without triggering read barriers.
-        types::TypeSet::readBarrier(propTypes);
-
-        Register scratchReg = object;
-        masm.guardTypeSet(valReg, propTypes, BarrierKind::TypeSet, scratchReg, &failuresPopObject);
+        CheckTypeSetForWrite(masm, obj, obj->lastProperty()->propid(), object, value, &failuresPopObject);
         masm.loadPtr(Address(StackPointer, 0), object);
     }
 
     JSObject *proto = obj->getProto();
     Register protoReg = object;
     while (proto) {
         Shape *protoShape = proto->lastProperty();
 
@@ -2679,16 +2733,95 @@ CanAttachNativeSetProp(JSContext *cx, Ha
         IsCacheableSetPropCallNative(obj, holder, shape))
     {
         return SetPropertyIC::CanAttachCallSetter;
     }
 
     return SetPropertyIC::CanAttachNone;
 }
 
+static void
+GenerateSetUnboxed(JSContext *cx, MacroAssembler &masm, IonCache::StubAttacher &attacher,
+                   JSObject *obj, jsid id, uint32_t unboxedOffset, JSValueType unboxedType,
+                   Register object, ConstantOrRegister value, bool checkTypeset)
+{
+    Label failure, failurePopObject;
+
+    // Guard on the type of the object.
+    masm.branchPtr(Assembler::NotEqual,
+                   Address(object, JSObject::offsetOfType()),
+                   ImmGCPtr(obj->type()), &failure);
+
+    if (checkTypeset) {
+        masm.push(object);
+        CheckTypeSetForWrite(masm, obj, id, object, value, &failurePopObject);
+        masm.pop(object);
+    }
+
+    Address address(object, UnboxedPlainObject::offsetOfData() + unboxedOffset);
+
+    if (cx->zone()->needsIncrementalBarrier()) {
+        if (unboxedType == JSVAL_TYPE_OBJECT)
+            masm.callPreBarrier(address, MIRType_Object);
+        else if (unboxedType == JSVAL_TYPE_STRING)
+            masm.callPreBarrier(address, MIRType_String);
+        else
+            MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(unboxedType));
+    }
+
+    // If the unboxed object's type has known properties, then instances have
+    // never been converted to native objects and the type set check performed
+    // above ensures the value being written can be stored in the unboxed
+    // object.
+    Label *storeFailure = obj->type()->unknownProperties() ? &failure : nullptr;
+
+    masm.storeUnboxedProperty(address, unboxedType, value, storeFailure);
+
+    attacher.jumpRejoin(masm);
+
+    masm.bind(&failurePopObject);
+    masm.pop(object);
+    masm.bind(&failure);
+
+    attacher.jumpNextStub(masm);
+}
+
+bool
+SetPropertyIC::attachSetUnboxed(JSContext *cx, HandleScript outerScript, IonScript *ion,
+                                HandleObject obj, HandleId id,
+                                uint32_t unboxedOffset, JSValueType unboxedType,
+                                bool checkTypeset)
+{
+    MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
+    RepatchStubAppender attacher(*this);
+    GenerateSetUnboxed(cx, masm, attacher, obj, id, unboxedOffset, unboxedType,
+                       object(), value(), needsTypeBarrier());
+    return linkAndAttachStub(cx, masm, attacher, ion, "set_unboxed");
+}
+
+static bool
+CanAttachSetUnboxed(JSContext *cx, HandleObject obj, HandleId id, ConstantOrRegister val,
+                    bool needsTypeBarrier, bool *checkTypeset,
+                    uint32_t *unboxedOffset, JSValueType *unboxedType)
+{
+    if (!obj->is<UnboxedPlainObject>())
+        return false;
+
+    const UnboxedLayout::Property *property = obj->as<UnboxedPlainObject>().layout().lookup(id);
+    if (property) {
+        if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
+            return false;
+        *unboxedOffset = property->offset;
+        *unboxedType = property->type;
+        return true;
+    }
+
+    return false;
+}
+
 bool
 SetPropertyIC::update(JSContext *cx, size_t cacheIndex, HandleObject obj,
                       HandleValue value)
 {
     void *returnAddr;
     RootedScript script(cx, GetTopJitJSScript(cx, &returnAddr));
     IonScript *ion = script->ionScript();
     SetPropertyIC &cache = ion->getCache(cacheIndex).toSetProperty();
@@ -2743,16 +2876,31 @@ SetPropertyIC::update(JSContext *cx, siz
             addedSetterStub = true;
         }
 
         if (!addedSetterStub && canCache == CanAttachCallSetter) {
             if (!cache.attachCallSetter(cx, script, ion, obj, holder, shape, returnAddr))
                 return false;
             addedSetterStub = true;
         }
+
+        checkTypeset = false;
+        uint32_t unboxedOffset;
+        JSValueType unboxedType;
+        if (!addedSetterStub && CanAttachSetUnboxed(cx, obj, id, cache.value(),
+                                                    cache.needsTypeBarrier(),
+                                                    &checkTypeset, &unboxedOffset, &unboxedType))
+        {
+            if (!cache.attachSetUnboxed(cx, script, ion, obj, id, unboxedOffset, unboxedType,
+                                        checkTypeset))
+            {
+                return false;
+            }
+            addedSetterStub = true;
+        }
     }
 
     uint32_t oldSlots = obj->is<NativeObject>() ? obj->as<NativeObject>().numDynamicSlots() : 0;
     RootedShape oldShape(cx, obj->lastProperty());
 
     // Set/Add the property on the object, the inlined cache are setup for the next execution.
     if (!SetProperty(cx, obj, name, value, cache.strict(), cache.pc()))
         return false;
@@ -2762,18 +2910,22 @@ SetPropertyIC::update(JSContext *cx, siz
     if (!addedSetterStub && canCache == MaybeCanAttachAddSlot &&
         IsPropertyAddInlineable(&obj->as<NativeObject>(), id,
                                 cache.value(), oldSlots, oldShape, cache.needsTypeBarrier(),
                                 &checkTypeset))
     {
         RootedNativeObject nobj(cx, &obj->as<NativeObject>());
         if (!cache.attachAddSlot(cx, script, ion, nobj, oldShape, oldType, checkTypeset))
             return false;
+        addedSetterStub = true;
     }
 
+    if (!addedSetterStub)
+        JitSpew(JitSpew_IonIC, "Failed to attach SETPROP cache");
+
     return true;
 }
 
 void
 SetPropertyIC::reset()
 {
     RepatchIonCache::reset();
     hasGenericProxyStub_ = false;
--- a/js/src/jit/IonCaches.h
+++ b/js/src/jit/IonCaches.h
@@ -664,16 +664,20 @@ class GetPropertyIC : public RepatchIonC
     bool tryAttachDOMProxyUnshadowed(JSContext *cx, HandleScript outerScript, IonScript *ion,
                                      HandleObject obj, HandlePropertyName name, bool resetNeeded,
                                      void *returnAddr, bool *emitted);
 
     bool tryAttachNative(JSContext *cx, HandleScript outerScript, IonScript *ion,
                          HandleObject obj, HandlePropertyName name,
                          void *returnAddr, bool *emitted);
 
+    bool tryAttachUnboxed(JSContext *cx, HandleScript outerScript, IonScript *ion,
+                          HandleObject obj, HandlePropertyName name,
+                          void *returnAddr, bool *emitted);
+
     bool tryAttachTypedArrayLength(JSContext *cx, HandleScript outerScript, IonScript *ion,
                                    HandleObject obj, HandlePropertyName name, bool *emitted);
 
     bool tryAttachArgumentsLength(JSContext *cx, HandleScript outerScript, IonScript *ion,
                                   HandleObject obj, HandlePropertyName name, bool *emitted);
 
     static bool update(JSContext *cx, size_t cacheIndex, HandleObject obj, MutableHandleValue vp);
 };
@@ -742,16 +746,21 @@ class SetPropertyIC : public RepatchIonC
     bool attachCallSetter(JSContext *cx, HandleScript outerScript, IonScript *ion,
                           HandleObject obj, HandleObject holder, HandleShape shape,
                           void *returnAddr);
 
     bool attachAddSlot(JSContext *cx, HandleScript outerScript, IonScript *ion,
                        HandleNativeObject obj, HandleShape oldShape, HandleTypeObject oldType,
                        bool checkTypeset);
 
+    bool attachSetUnboxed(JSContext *cx, HandleScript outerScript, IonScript *ion,
+                          HandleObject obj, HandleId id,
+                          uint32_t unboxedOffset, JSValueType unboxedType,
+                          bool checkTypeset);
+
     bool attachGenericProxy(JSContext *cx, HandleScript outerScript, IonScript *ion,
                             void *returnAddr);
 
     bool attachDOMProxyShadowed(JSContext *cx, HandleScript outerScript, IonScript *ion,
                                 HandleObject obj, void *returnAddr);
 
     bool attachDOMProxyUnshadowed(JSContext *cx, HandleScript outerScript, IonScript *ion,
                                   HandleObject obj, void *returnAddr);
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2614,22 +2614,22 @@ void
 LIRGenerator::visitLoadUnboxedObjectOrNull(MLoadUnboxedObjectOrNull *ins)
 {
     MOZ_ASSERT(IsValidElementsType(ins->elements(), ins->offsetAdjustment()));
     MOZ_ASSERT(ins->index()->type() == MIRType_Int32);
 
     if (ins->type() == MIRType_Object) {
         LLoadUnboxedPointerT *lir = new(alloc()) LLoadUnboxedPointerT(useRegister(ins->elements()),
                                                                       useRegisterOrConstant(ins->index()));
-        if (ins->bailOnNull())
+        if (ins->nullBehavior() == MLoadUnboxedObjectOrNull::BailOnNull)
             assignSnapshot(lir, Bailout_TypeBarrierO);
         define(lir, ins);
     } else {
         MOZ_ASSERT(ins->type() == MIRType_Value);
-        MOZ_ASSERT(!ins->bailOnNull());
+        MOZ_ASSERT(ins->nullBehavior() != MLoadUnboxedObjectOrNull::BailOnNull);
 
         LLoadUnboxedPointerV *lir = new(alloc()) LLoadUnboxedPointerV(useRegister(ins->elements()),
                                                                       useRegisterOrConstant(ins->index()));
         defineBox(lir, ins);
     }
 }
 
 void
@@ -2843,17 +2843,17 @@ void
 LIRGenerator::visitLoadTypedArrayElement(MLoadTypedArrayElement *ins)
 {
     MOZ_ASSERT(IsValidElementsType(ins->elements(), ins->offsetAdjustment()));
     MOZ_ASSERT(ins->index()->type() == MIRType_Int32);
 
     const LUse elements = useRegister(ins->elements());
     const LAllocation index = useRegisterOrConstant(ins->index());
 
-    MOZ_ASSERT(IsNumberType(ins->type()));
+    MOZ_ASSERT(IsNumberType(ins->type()) || ins->type() == MIRType_Boolean);
 
     // We need a temp register for Uint32Array with known double result.
     LDefinition tempDef = LDefinition::BogusTemp();
     if (ins->arrayType() == Scalar::Uint32 && IsFloatingPointType(ins->type()))
         tempDef = temp();
 
     if (ins->requiresMemoryBarrier()) {
         LMemoryBarrier *fence = new(alloc()) LMemoryBarrier(MembarBeforeLoad);
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -3642,31 +3642,32 @@ MNewObject::shouldUseVM() const
 {
     PlainObject *obj = templateObject();
     return obj->hasSingletonType() || obj->hasDynamicSlots();
 }
 
 bool
 MCreateThisWithTemplate::canRecoverOnBailout() const
 {
-    MOZ_ASSERT(!templateObject()->denseElementsAreCopyOnWrite());
-    MOZ_ASSERT(!templateObject()->is<ArrayObject>());
+    MOZ_ASSERT(templateObject()->is<PlainObject>() || templateObject()->is<UnboxedPlainObject>());
+    MOZ_ASSERT_IF(templateObject()->is<PlainObject>(),
+                  !templateObject()->as<PlainObject>().denseElementsAreCopyOnWrite());
     return true;
 }
 
 MObjectState::MObjectState(MDefinition *obj)
 {
     // This instruction is only used as a summary for bailout paths.
     setResultType(MIRType_Object);
     setRecoveredOnBailout();
     NativeObject *templateObject = nullptr;
     if (obj->isNewObject())
         templateObject = obj->toNewObject()->templateObject();
     else if (obj->isCreateThisWithTemplate())
-        templateObject = obj->toCreateThisWithTemplate()->templateObject();
+        templateObject = &obj->toCreateThisWithTemplate()->templateObject()->as<PlainObject>();
     else
         templateObject = obj->toNewCallObject()->templateObject();
     numSlots_ = templateObject->slotSpan();
     numFixedSlots_ = templateObject->numFixedSlots();
 }
 
 bool
 MObjectState::init(TempAllocator &alloc, MDefinition *obj)
@@ -4722,20 +4723,22 @@ TryAddTypeBarrierForWrite(TempAllocator 
 }
 
 static MInstruction *
 AddTypeGuard(TempAllocator &alloc, MBasicBlock *current, MDefinition *obj,
              types::TypeObjectKey *type, bool bailOnEquality)
 {
     MInstruction *guard;
 
-    if (type->isTypeObject())
-        guard = MGuardObjectType::New(alloc, obj, type->asTypeObject(), bailOnEquality);
-    else
+    if (type->isTypeObject()) {
+        guard = MGuardObjectType::New(alloc, obj, type->asTypeObject(), bailOnEquality,
+                                      Bailout_ObjectIdentityOrTypeGuard);
+    } else {
         guard = MGuardObjectIdentity::New(alloc, obj, type->asSingleObject(), bailOnEquality);
+    }
 
     current->add(guard);
 
     // For now, never move type object guards.
     guard->setNotMovable();
 
     return guard;
 }
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -4268,18 +4268,18 @@ class MCreateThisWithTemplate
     INSTRUCTION_HEADER(CreateThisWithTemplate)
     static MCreateThisWithTemplate *New(TempAllocator &alloc, types::CompilerConstraintList *constraints,
                                         MConstant *templateConst, gc::InitialHeap initialHeap)
     {
         return new(alloc) MCreateThisWithTemplate(constraints, templateConst, initialHeap);
     }
 
     // Template for |this|, provided by TI.
-    PlainObject *templateObject() const {
-        return &getOperand(0)->toConstant()->value().toObject().as<PlainObject>();
+    JSObject *templateObject() const {
+        return &getOperand(0)->toConstant()->value().toObject();
     }
 
     gc::InitialHeap initialHeap() const {
         return initialHeap_;
     }
 
     // Although creation of |this| modifies global state, it is safely repeatable.
     AliasSet getAliasSet() const MOZ_OVERRIDE {
@@ -8054,65 +8054,74 @@ class MLoadElementHole
 
     ALLOW_CLONE(MLoadElementHole)
 };
 
 class MLoadUnboxedObjectOrNull
   : public MBinaryInstruction,
     public SingleObjectPolicy::Data
 {
-    bool bailOnNull_;
+  public:
+    enum NullBehavior {
+        HandleNull,
+        BailOnNull,
+        NullNotPossible
+    };
+
+  private:
+    NullBehavior nullBehavior_;
     int32_t offsetAdjustment_;
 
     MLoadUnboxedObjectOrNull(MDefinition *elements, MDefinition *index,
-                             bool bailOnNull, int32_t offsetAdjustment)
+                             NullBehavior nullBehavior, int32_t offsetAdjustment)
       : MBinaryInstruction(elements, index),
-        bailOnNull_(bailOnNull),
+        nullBehavior_(nullBehavior),
         offsetAdjustment_(offsetAdjustment)
     {
-        if (bailOnNull) {
+        if (nullBehavior == BailOnNull) {
             // Don't eliminate loads which bail out on a null pointer, for the
             // same reason as MLoadElement.
             setGuard();
         }
-        setResultType(bailOnNull ? MIRType_Object : MIRType_Value);
+        setResultType(nullBehavior == HandleNull ? MIRType_Value : MIRType_Object);
         setMovable();
         MOZ_ASSERT(IsValidElementsType(elements, offsetAdjustment));
         MOZ_ASSERT(index->type() == MIRType_Int32);
     }
 
   public:
     INSTRUCTION_HEADER(LoadUnboxedObjectOrNull)
 
     static MLoadUnboxedObjectOrNull *New(TempAllocator &alloc,
                                          MDefinition *elements, MDefinition *index,
-                                         bool bailOnNull, int32_t offsetAdjustment) {
-        return new(alloc) MLoadUnboxedObjectOrNull(elements, index, bailOnNull, offsetAdjustment);
+                                         NullBehavior nullBehavior, int32_t offsetAdjustment) {
+        return new(alloc) MLoadUnboxedObjectOrNull(elements, index, nullBehavior,
+                                                   offsetAdjustment);
     }
 
     MDefinition *elements() const {
         return getOperand(0);
     }
     MDefinition *index() const {
         return getOperand(1);
     }
-    bool bailOnNull() const {
-        return bailOnNull_;
+    NullBehavior nullBehavior() const {
+        return nullBehavior_;
     }
     int32_t offsetAdjustment() const {
         return offsetAdjustment_;
     }
     bool fallible() const {
-        return bailOnNull();
+        return nullBehavior() == BailOnNull;
     }
     bool congruentTo(const MDefinition *ins) const MOZ_OVERRIDE {
         if (!ins->isLoadUnboxedObjectOrNull())
             return false;
         const MLoadUnboxedObjectOrNull *other = ins->toLoadUnboxedObjectOrNull();
-        if (bailOnNull() != other->bailOnNull())
+        if (nullBehavior() != other->nullBehavior())
             return false;
         if (offsetAdjustment() != other->offsetAdjustment())
             return false;
         return congruentIfOperandsEqual(other);
     }
     AliasSet getAliasSet() const MOZ_OVERRIDE {
         return AliasSet::Load(AliasSet::Element);
     }
@@ -8135,17 +8144,18 @@ class MLoadUnboxedString
         MOZ_ASSERT(IsValidElementsType(elements, offsetAdjustment));
         MOZ_ASSERT(index->type() == MIRType_Int32);
     }
 
   public:
     INSTRUCTION_HEADER(LoadUnboxedString)
 
     static MLoadUnboxedString *New(TempAllocator &alloc,
-                                   MDefinition *elements, MDefinition *index, int32_t offsetAdjustment) {
+                                   MDefinition *elements, MDefinition *index,
+                                   int32_t offsetAdjustment = 0) {
         return new(alloc) MLoadUnboxedString(elements, index, offsetAdjustment);
     }
 
     MDefinition *elements() const {
         return getOperand(0);
     }
     MDefinition *index() const {
         return getOperand(1);
@@ -8326,17 +8336,17 @@ class MStoreUnboxedObjectOrNull
     }
 
   public:
     INSTRUCTION_HEADER(StoreUnboxedObjectOrNull)
 
     static MStoreUnboxedObjectOrNull *New(TempAllocator &alloc,
                                           MDefinition *elements, MDefinition *index,
                                           MDefinition *value, MDefinition *typedObj,
-                                          int32_t offsetAdjustment) {
+                                          int32_t offsetAdjustment = 0) {
         return new(alloc) MStoreUnboxedObjectOrNull(elements, index, value, typedObj,
                                                     offsetAdjustment);
     }
     MDefinition *elements() const {
         return getOperand(0);
     }
     MDefinition *index() const {
         return getOperand(1);
@@ -8381,17 +8391,17 @@ class MStoreUnboxedString
         MOZ_ASSERT(index->type() == MIRType_Int32);
     }
 
   public:
     INSTRUCTION_HEADER(StoreUnboxedString)
 
     static MStoreUnboxedString *New(TempAllocator &alloc,
                                     MDefinition *elements, MDefinition *index,
-                                    MDefinition *value, int32_t offsetAdjustment) {
+                                    MDefinition *value, int32_t offsetAdjustment = 0) {
         return new(alloc) MStoreUnboxedString(elements, index, value, offsetAdjustment);
     }
     MDefinition *elements() const {
         return getOperand(0);
     }
     MDefinition *index() const {
         return getOperand(1);
     }
@@ -8576,24 +8586,26 @@ enum MemoryBarrierRequirement
 
 class MLoadTypedArrayElement
   : public MBinaryInstruction,
     public SingleObjectPolicy::Data
 {
     Scalar::Type arrayType_;
     bool requiresBarrier_;
     int32_t offsetAdjustment_;
+    bool canonicalizeDoubles_;
 
     MLoadTypedArrayElement(MDefinition *elements, MDefinition *index,
                            Scalar::Type arrayType, MemoryBarrierRequirement requiresBarrier,
-                           int32_t offsetAdjustment)
+                           int32_t offsetAdjustment, bool canonicalizeDoubles)
       : MBinaryInstruction(elements, index),
         arrayType_(arrayType),
         requiresBarrier_(requiresBarrier == DoesRequireMemoryBarrier),
-        offsetAdjustment_(offsetAdjustment)
+        offsetAdjustment_(offsetAdjustment),
+        canonicalizeDoubles_(canonicalizeDoubles)
     {
         setResultType(MIRType_Value);
         if (requiresBarrier_)
             setGuard();         // Not removable or movable
         else
             setMovable();
         MOZ_ASSERT(IsValidElementsType(elements, offsetAdjustment));
         MOZ_ASSERT(index->type() == MIRType_Int32);
@@ -8601,32 +8613,37 @@ class MLoadTypedArrayElement
     }
 
   public:
     INSTRUCTION_HEADER(LoadTypedArrayElement)
 
     static MLoadTypedArrayElement *New(TempAllocator &alloc, MDefinition *elements, MDefinition *index,
                                        Scalar::Type arrayType,
                                        MemoryBarrierRequirement requiresBarrier=DoesNotRequireMemoryBarrier,
-                                       int32_t offsetAdjustment = 0)
+                                       int32_t offsetAdjustment = 0,
+                                       bool canonicalizeDoubles = true)
     {
         return new(alloc) MLoadTypedArrayElement(elements, index, arrayType,
-                                                 requiresBarrier, offsetAdjustment);
+                                                 requiresBarrier, offsetAdjustment,
+                                                 canonicalizeDoubles);
     }
 
     Scalar::Type arrayType() const {
         return arrayType_;
     }
     bool fallible() const {
         // Bailout if the result does not fit in an int32.
         return arrayType_ == Scalar::Uint32 && type() == MIRType_Int32;
     }
     bool requiresMemoryBarrier() const {
         return requiresBarrier_;
     }
+    bool canonicalizeDoubles() const {
+        return canonicalizeDoubles_;
+    }
     MDefinition *elements() const {
         return getOperand(0);
     }
     MDefinition *index() const {
         return getOperand(1);
     }
     int32_t offsetAdjustment() const {
         return offsetAdjustment_;
@@ -8644,16 +8661,18 @@ class MLoadTypedArrayElement
             return false;
         if (!ins->isLoadTypedArrayElement())
             return false;
         const MLoadTypedArrayElement *other = ins->toLoadTypedArrayElement();
         if (arrayType_ != other->arrayType_)
             return false;
         if (offsetAdjustment() != other->offsetAdjustment())
             return false;
+        if (canonicalizeDoubles() != other->canonicalizeDoubles())
+            return false;
         return congruentIfOperandsEqual(other);
     }
 
     void printOpcode(FILE *fp) const MOZ_OVERRIDE;
 
     void computeRange(TempAllocator &alloc) MOZ_OVERRIDE;
 
     bool canProduceFloat32() const MOZ_OVERRIDE { return arrayType_ == Scalar::Float32; }
@@ -9335,47 +9354,45 @@ class MGetPropertyCache
         }
         return AliasSet::Store(AliasSet::Any);
     }
 
     void setBlock(MBasicBlock *block) MOZ_OVERRIDE;
     bool updateForReplacement(MDefinition *ins) MOZ_OVERRIDE;
 };
 
-// Emit code to load a value from an object's slots if its shape matches
-// one of the shapes observed by the baseline IC, else bails out.
+// Emit code to load a value from an object if its shape/type matches one of
+// the shapes/types observed by the baseline IC, else bails out.
 class MGetPropertyPolymorphic
   : public MUnaryInstruction,
     public SingleObjectPolicy::Data
 {
     struct Entry {
         // The shape to guard against.
         Shape *objShape;
 
         // The property to laod.
         Shape *shape;
     };
 
-    Vector<Entry, 4, JitAllocPolicy> shapes_;
+    Vector<Entry, 4, JitAllocPolicy> nativeShapes_;
+    Vector<types::TypeObject *, 4, JitAllocPolicy> unboxedTypes_;
     AlwaysTenuredPropertyName name_;
 
     MGetPropertyPolymorphic(TempAllocator &alloc, MDefinition *obj, PropertyName *name)
       : MUnaryInstruction(obj),
-        shapes_(alloc),
+        nativeShapes_(alloc),
+        unboxedTypes_(alloc),
         name_(name)
     {
         setGuard();
         setMovable();
         setResultType(MIRType_Value);
     }
 
-    PropertyName *name() const {
-        return name_;
-    }
-
   public:
     INSTRUCTION_HEADER(GetPropertyPolymorphic)
 
     static MGetPropertyPolymorphic *New(TempAllocator &alloc, MDefinition *obj, PropertyName *name) {
         return new(alloc) MGetPropertyPolymorphic(alloc, obj, name);
     }
 
     bool congruentTo(const MDefinition *ins) const MOZ_OVERRIDE {
@@ -9385,32 +9402,45 @@ class MGetPropertyPolymorphic
             return false;
         return congruentIfOperandsEqual(ins);
     }
 
     bool addShape(Shape *objShape, Shape *shape) {
         Entry entry;
         entry.objShape = objShape;
         entry.shape = shape;
-        return shapes_.append(entry);
+        return nativeShapes_.append(entry);
+    }
+    bool addUnboxedType(types::TypeObject *type) {
+        return unboxedTypes_.append(type);
     }
     size_t numShapes() const {
-        return shapes_.length();
+        return nativeShapes_.length();
     }
     Shape *objShape(size_t i) const {
-        return shapes_[i].objShape;
+        return nativeShapes_[i].objShape;
     }
     Shape *shape(size_t i) const {
-        return shapes_[i].shape;
+        return nativeShapes_[i].shape;
+    }
+    size_t numUnboxedTypes() const {
+        return unboxedTypes_.length();
+    }
+    types::TypeObject *unboxedType(size_t i) const {
+        return unboxedTypes_[i];
+    }
+    PropertyName *name() const {
+        return name_;
     }
     MDefinition *obj() const {
         return getOperand(0);
     }
     AliasSet getAliasSet() const MOZ_OVERRIDE {
-        return AliasSet::Load(AliasSet::ObjectFields | AliasSet::FixedSlot | AliasSet::DynamicSlot);
+        return AliasSet::Load(AliasSet::ObjectFields | AliasSet::FixedSlot | AliasSet::DynamicSlot |
+                              (unboxedTypes_.empty() ? 0 : (AliasSet::TypedArrayElement | AliasSet::Element)));
     }
 
     bool mightAlias(const MDefinition *store) const MOZ_OVERRIDE;
 };
 
 // Emit code to store a value to an object's slots if its shape matches
 // one of the shapes observed by the baseline IC, else bails out.
 class MSetPropertyPolymorphic
@@ -9420,62 +9450,81 @@ class MSetPropertyPolymorphic
     struct Entry {
         // The shape to guard against.
         Shape *objShape;
 
         // The property to load.
         Shape *shape;
     };
 
-    Vector<Entry, 4, JitAllocPolicy> shapes_;
+    Vector<Entry, 4, JitAllocPolicy> nativeShapes_;
+    Vector<types::TypeObject *, 4, JitAllocPolicy> unboxedTypes_;
+    AlwaysTenuredPropertyName name_;
     bool needsBarrier_;
 
-    MSetPropertyPolymorphic(TempAllocator &alloc, MDefinition *obj, MDefinition *value)
+    MSetPropertyPolymorphic(TempAllocator &alloc, MDefinition *obj, MDefinition *value,
+                            PropertyName *name)
       : MBinaryInstruction(obj, value),
-        shapes_(alloc),
+        nativeShapes_(alloc),
+        unboxedTypes_(alloc),
+        name_(name),
         needsBarrier_(false)
     {
     }
 
   public:
     INSTRUCTION_HEADER(SetPropertyPolymorphic)
 
-    static MSetPropertyPolymorphic *New(TempAllocator &alloc, MDefinition *obj, MDefinition *value) {
-        return new(alloc) MSetPropertyPolymorphic(alloc, obj, value);
+    static MSetPropertyPolymorphic *New(TempAllocator &alloc, MDefinition *obj, MDefinition *value,
+                                        PropertyName *name) {
+        return new(alloc) MSetPropertyPolymorphic(alloc, obj, value, name);
     }
 
     bool addShape(Shape *objShape, Shape *shape) {
         Entry entry;
         entry.objShape = objShape;
         entry.shape = shape;
-        return shapes_.append(entry);
+        return nativeShapes_.append(entry);
+    }
+    bool addUnboxedType(types::TypeObject *type) {
+        return unboxedTypes_.append(type);
     }
     size_t numShapes() const {
-        return shapes_.length();
+        return nativeShapes_.length();
     }
     Shape *objShape(size_t i) const {
-        return shapes_[i].objShape;
+        return nativeShapes_[i].objShape;
     }
     Shape *shape(size_t i) const {
-        return shapes_[i].shape;
+        return nativeShapes_[i].shape;
+    }
+    size_t numUnboxedTypes() const {
+        return unboxedTypes_.length();
+    }
+    types::TypeObject *unboxedType(size_t i) const {
+        return unboxedTypes_[i];
+    }
+    PropertyName *name() const {
+        return name_;
     }
     MDefinition *obj() const {
         return getOperand(0);
     }
     MDefinition *value() const {
         return getOperand(1);
     }
     bool needsBarrier() const {
         return needsBarrier_;
     }
     void setNeedsBarrier() {
         needsBarrier_ = true;
     }
     AliasSet getAliasSet() const MOZ_OVERRIDE {
-        return AliasSet::Store(AliasSet::ObjectFields | AliasSet::FixedSlot | AliasSet::DynamicSlot);
+        return AliasSet::Store(AliasSet::ObjectFields | AliasSet::FixedSlot | AliasSet::DynamicSlot |
+                               (unboxedTypes_.empty() ? 0 : (AliasSet::TypedArrayElement | AliasSet::Element)));
     }
 };
 
 class MDispatchInstruction
   : public MControlInstruction,
     public SingleObjectPolicy::Data
 {
     // Map from JSFunction* -> MBasicBlock.
@@ -9800,51 +9849,59 @@ class MGuardShapePolymorphic
 
 // Guard on an object's type, inclusively or exclusively.
 class MGuardObjectType
   : public MUnaryInstruction,
     public SingleObjectPolicy::Data
 {
     AlwaysTenured<types::TypeObject *> typeObject_;
     bool bailOnEquality_;
-
-    MGuardObjectType(MDefinition *obj, types::TypeObject *typeObject, bool bailOnEquality)
+    BailoutKind bailoutKind_;
+
+    MGuardObjectType(MDefinition *obj, types::TypeObject *typeObject, bool bailOnEquality,
+                     BailoutKind bailoutKind)
       : MUnaryInstruction(obj),
         typeObject_(typeObject),
-        bailOnEquality_(bailOnEquality)
+        bailOnEquality_(bailOnEquality),
+        bailoutKind_(bailoutKind)
     {
         setGuard();
         setMovable();
         setResultType(MIRType_Object);
     }
 
   public:
     INSTRUCTION_HEADER(GuardObjectType)
 
     static MGuardObjectType *New(TempAllocator &alloc, MDefinition *obj, types::TypeObject *typeObject,
-                                 bool bailOnEquality) {
-        return new(alloc) MGuardObjectType(obj, typeObject, bailOnEquality);
+                                 bool bailOnEquality, BailoutKind bailoutKind) {
+        return new(alloc) MGuardObjectType(obj, typeObject, bailOnEquality, bailoutKind);
     }
 
     MDefinition *obj() const {
         return getOperand(0);
     }
     const types::TypeObject *typeObject() const {
         return typeObject_;
     }
     bool bailOnEquality() const {
         return bailOnEquality_;
     }
+    BailoutKind bailoutKind() const {
+        return bailoutKind_;
+    }
     bool congruentTo(const MDefinition *ins) const MOZ_OVERRIDE {
         if (!ins->isGuardObjectType())
             return false;
         if (typeObject() != ins->toGuardObjectType()->typeObject())
             return false;
         if (bailOnEquality() != ins->toGuardObjectType()->bailOnEquality())
             return false;
+        if (bailoutKind() != ins->toGuardObjectType()->bailoutKind())
+            return false;
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const MOZ_OVERRIDE {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 };
 
 // Guard on an object's identity, inclusively or exclusively.
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -301,17 +301,17 @@ MacroAssembler::storeToTypedFloatArray(S
                                        const Address &dest)
 {
     StoreToTypedFloatArray(*this, arrayType, value, dest);
 }
 
 template<typename T>
 void
 MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const T &src, AnyRegister dest, Register temp,
-                                   Label *fail)
+                                   Label *fail, bool canonicalizeDoubles)
 {
     switch (arrayType) {
       case Scalar::Int8:
         load8SignExtend(src, dest.gpr());
         break;
       case Scalar::Uint8:
       case Scalar::Uint8Clamped:
         load8ZeroExtend(src, dest.gpr());
@@ -339,27 +339,28 @@ MacroAssembler::loadFromTypedArray(Scala
         }
         break;
       case Scalar::Float32:
         loadFloat32(src, dest.fpu());
         canonicalizeFloat(dest.fpu());
         break;
       case Scalar::Float64:
         loadDouble(src, dest.fpu());
-        canonicalizeDouble(dest.fpu());
+        if (canonicalizeDoubles)
+            canonicalizeDouble(dest.fpu());
         break;
       default:
         MOZ_CRASH("Invalid typed array type");
     }
 }
 
 template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const Address &src, AnyRegister dest,
-                                                 Register temp, Label *fail);
+                                                 Register temp, Label *fail, bool canonicalizeDoubles);
 template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const BaseIndex &src, AnyRegister dest,
-                                                 Register temp, Label *fail);
+                                                 Register temp, Label *fail, bool canonicalizeDoubles);
 
 template<typename T>
 void
 MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const T &src, const ValueOperand &dest,
                                    bool allowDouble, Register temp, Label *fail)
 {
     switch (arrayType) {
       case Scalar::Int8:
@@ -616,16 +617,224 @@ template void
 MacroAssembler::atomicBinopToTypedIntArray(AtomicOp op, Scalar::Type arrayType,
                                            const Register &value, const Address &mem,
                                            Register temp1, Register temp2, AnyRegister output);
 template void
 MacroAssembler::atomicBinopToTypedIntArray(AtomicOp op, Scalar::Type arrayType,
                                            const Register &value, const BaseIndex &mem,
                                            Register temp1, Register temp2, AnyRegister output);
 
+template <typename T>
+void
+MacroAssembler::loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output)
+{
+    switch (type) {
+      case JSVAL_TYPE_BOOLEAN:
+      case JSVAL_TYPE_INT32:
+      case JSVAL_TYPE_STRING: {
+        Register outReg;
+        if (output.hasValue()) {
+            outReg = output.valueReg().scratchReg();
+        } else {
+            MOZ_ASSERT(output.type() == MIRTypeFromValueType(type));
+            outReg = output.typedReg().gpr();
+        }
+
+        switch (type) {
+          case JSVAL_TYPE_BOOLEAN:
+            load8ZeroExtend(address, outReg);
+            break;
+          case JSVAL_TYPE_INT32:
+            load32(address, outReg);
+            break;
+          case JSVAL_TYPE_STRING:
+            loadPtr(address, outReg);
+            break;
+          default:
+            MOZ_CRASH();
+        }
+
+        if (output.hasValue())
+            tagValue(type, outReg, output.valueReg());
+        break;
+      }
+
+      case JSVAL_TYPE_OBJECT:
+        if (output.hasValue()) {
+            Register scratch = output.valueReg().scratchReg();
+            loadPtr(address, scratch);
+
+            Label notNull, done;
+            branchPtr(Assembler::NotEqual, scratch, ImmWord(0), &notNull);
+
+            moveValue(NullValue(), output.valueReg());
+            jump(&done);
+
+            bind(&notNull);
+            tagValue(JSVAL_TYPE_OBJECT, scratch, output.valueReg());
+
+            bind(&done);
+        } else {
+            // Reading null can't be possible here, as otherwise the result
+            // would be a value (either because null has been read before or
+            // because there is a barrier).
+            Register reg = output.typedReg().gpr();
+            loadPtr(address, reg);
+#ifdef DEBUG
+            Label ok;
+            branchTestPtr(Assembler::NonZero, reg, reg, &ok);
+            assumeUnreachable("Null not possible");
+            bind(&ok);
+#endif
+        }
+        break;
+
+      case JSVAL_TYPE_DOUBLE:
+        // Note: doubles in unboxed objects are not accessed through other
+        // views and do not need canonicalization.
+        if (output.hasValue())
+            loadValue(address, output.valueReg());
+        else
+            loadDouble(address, output.typedReg().fpu());
+        break;
+
+      default:
+        MOZ_CRASH();
+    }
+}
+
+template void
+MacroAssembler::loadUnboxedProperty(Address address, JSValueType type,
+                                    TypedOrValueRegister output);
+
+template void
+MacroAssembler::loadUnboxedProperty(BaseIndex address, JSValueType type,
+                                    TypedOrValueRegister output);
+
+template <typename T>
+void
+MacroAssembler::storeUnboxedProperty(T address, JSValueType type,
+                                     ConstantOrRegister value, Label *failure)
+{
+    switch (type) {
+      case JSVAL_TYPE_BOOLEAN:
+        if (value.constant()) {
+            if (value.value().isBoolean())
+                store8(Imm32(value.value().toBoolean()), address);
+            else
+                jump(failure);
+        } else if (value.reg().hasTyped()) {
+            if (value.reg().type() == MIRType_Boolean)
+                store8(value.reg().typedReg().gpr(), address);
+            else
+                jump(failure);
+        } else {
+            if (failure)
+                branchTestBoolean(Assembler::NotEqual, value.reg().valueReg(), failure);
+            storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ 1);
+        }
+        break;
+
+      case JSVAL_TYPE_INT32:
+        if (value.constant()) {
+            if (value.value().isInt32())
+                store32(Imm32(value.value().toInt32()), address);
+            else
+                jump(failure);
+        } else if (value.reg().hasTyped()) {
+            if (value.reg().type() == MIRType_Int32)
+                store32(value.reg().typedReg().gpr(), address);
+            else
+                jump(failure);
+        } else {
+            if (failure)
+                branchTestInt32(Assembler::NotEqual, value.reg().valueReg(), failure);
+            storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ 4);
+        }
+        break;
+
+      case JSVAL_TYPE_DOUBLE:
+        if (value.constant()) {
+            if (value.value().isNumber()) {
+                loadConstantDouble(value.value().toNumber(), ScratchDoubleReg);
+                storeDouble(ScratchDoubleReg, address);
+            } else {
+                jump(failure);
+            }
+        } else if (value.reg().hasTyped()) {
+            if (value.reg().type() == MIRType_Int32) {
+                convertInt32ToDouble(value.reg().typedReg().gpr(), ScratchDoubleReg);
+                storeDouble(ScratchDoubleReg, address);
+            } else if (value.reg().type() == MIRType_Double) {
+                storeDouble(value.reg().typedReg().fpu(), address);
+            } else {
+                jump(failure);
+            }
+        } else {
+            if (failure)
+                branchTestNumber(Assembler::NotEqual, value.reg().valueReg(), failure);
+            unboxValue(value.reg().valueReg(), AnyRegister(ScratchDoubleReg));
+            storeDouble(ScratchDoubleReg, address);
+        }
+        break;
+
+      case JSVAL_TYPE_OBJECT:
+        if (value.constant()) {
+            if (value.value().isObjectOrNull())
+                storePtr(ImmGCPtr(value.value().toObjectOrNull()), address);
+            else
+                jump(failure);
+        } else if (value.reg().hasTyped()) {
+            MOZ_ASSERT(value.reg().type() != MIRType_Null);
+            if (value.reg().type() == MIRType_Object)
+                storePtr(value.reg().typedReg().gpr(), address);
+            else
+                jump(failure);
+        } else {
+            if (failure) {
+                Label ok;
+                branchTestNull(Assembler::Equal, value.reg().valueReg(), &ok);
+                branchTestObject(Assembler::NotEqual, value.reg().valueReg(), failure);
+                bind(&ok);
+            }
+            storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ sizeof(uintptr_t));
+        }
+        break;
+
+      case JSVAL_TYPE_STRING:
+        if (value.constant()) {
+            if (value.value().isString())
+                storePtr(ImmGCPtr(value.value().toString()), address);
+            else
+                jump(failure);
+        } else if (value.reg().hasTyped()) {
+            if (value.reg().type() == MIRType_String)
+                storePtr(value.reg().typedReg().gpr(), address);
+            else
+                jump(failure);
+        } else {
+            if (failure)
+                branchTestString(Assembler::NotEqual, value.reg().valueReg(), failure);
+            storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ sizeof(uintptr_t));
+        }
+        break;
+
+      default:
+        MOZ_CRASH();
+    }
+}
+
+template void
+MacroAssembler::storeUnboxedProperty(Address address, JSValueType type,
+                                     ConstantOrRegister value, Label *failure);
+
+template void
+MacroAssembler::storeUnboxedProperty(BaseIndex address, JSValueType type,
+                                     ConstantOrRegister value, Label *failure);
+
 // 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);
@@ -775,32 +984,32 @@ MacroAssembler::allocateObject(Register 
     pop(slots);
     jump(&success);
 
     bind(&failAlloc);
     pop(slots);
     callFreeStub(slots);
     jump(fail);
 
-    bind(&success);
+    breakpoint();
 }
 
 void
-MacroAssembler::newGCThing(Register result, Register temp, NativeObject *templateObj,
-                            gc::InitialHeap initialHeap, Label *fail)
+MacroAssembler::newGCThing(Register result, Register temp, JSObject *templateObj,
+                           gc::InitialHeap initialHeap, Label *fail)
 {
     // This method does not initialize the object: if external slots get
     // allocated into |temp|, there is no easy way for us to ensure the caller
     // frees them. Instead just assert this case does not happen.
-    MOZ_ASSERT(!templateObj->numDynamicSlots());
+    MOZ_ASSERT_IF(templateObj->isNative(), !templateObj->as<NativeObject>().numDynamicSlots());
 
     gc::AllocKind allocKind = templateObj->asTenured().getAllocKind();
     MOZ_ASSERT(allocKind >= gc::FINALIZE_OBJECT0 && allocKind <= gc::FINALIZE_OBJECT_LAST);
 
-    allocateObject(result, temp, allocKind, templateObj->numDynamicSlots(), initialHeap, fail);
+    allocateObject(result, temp, allocKind, 0, initialHeap, fail);
 }
 
 void
 MacroAssembler::createGCObject(Register obj, Register temp, JSObject *templateObj,
                                gc::InitialHeap initialHeap, Label *fail, bool initFixedSlots)
 {
     gc::AllocKind allocKind = templateObj->asTenured().getAllocKind();
     MOZ_ASSERT(allocKind >= gc::FINALIZE_OBJECT0 && allocKind <= gc::FINALIZE_OBJECT_LAST);
@@ -1025,28 +1234,47 @@ MacroAssembler::initGCThing(Register obj
 
             if (ntemplate->hasPrivate()) {
                 uint32_t nfixed = ntemplate->numFixedSlots();
                 storePtr(ImmPtr(ntemplate->getPrivate()),
                          Address(obj, NativeObject::getPrivateDataOffset(nfixed)));
             }
         }
     } else if (templateObj->is<InlineTypedObject>()) {
-        InlineTypedObject *ntemplate = &templateObj->as<InlineTypedObject>();
+        size_t nbytes = templateObj->as<InlineTypedObject>().size();
+        const uint8_t *memory = templateObj->as<InlineTypedObject>().inlineTypedMem();
 
         // Memcpy the contents of the template object to the new object.
-        size_t nbytes = ntemplate->size();
         size_t offset = 0;
         while (nbytes) {
-            uintptr_t value = *(uintptr_t *)(ntemplate->inlineTypedMem() + offset);
+            uintptr_t value = *(uintptr_t *)(memory + offset);
             storePtr(ImmWord(value),
                      Address(obj, InlineTypedObject::offsetOfDataStart() + offset));
             nbytes = (nbytes < sizeof(uintptr_t)) ? 0 : nbytes - sizeof(uintptr_t);
             offset += sizeof(uintptr_t);
         }
+    } else if (templateObj->is<UnboxedPlainObject>()) {
+        const UnboxedLayout &layout = templateObj->as<UnboxedPlainObject>().layout();
+
+        // Initialize reference fields of the object, per UnboxedPlainObject::create.
+        if (const int32_t *list = layout.traceList()) {
+            while (*list != -1) {
+                storePtr(ImmGCPtr(GetJitContext()->runtime->names().empty),
+                         Address(obj, UnboxedPlainObject::offsetOfData() + *list));
+                list++;
+            }
+            list++;
+            while (*list != -1) {
+                storePtr(ImmWord(0),
+                         Address(obj, UnboxedPlainObject::offsetOfData() + *list));
+                list++;
+            }
+            // Unboxed objects don't have Values to initialize.
+            MOZ_ASSERT(*(list + 1) == -1);
+        }
     } 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
@@ -689,17 +689,18 @@ class MacroAssembler : public MacroAssem
     void canonicalizeFloat(FloatRegister reg) {
         Label notNaN;
         branchFloat(DoubleOrdered, reg, reg, &notNaN);
         loadConstantFloat32(float(JS::GenericNaN()), reg);
         bind(&notNaN);
     }
 
     template<typename T>
-    void loadFromTypedArray(Scalar::Type arrayType, const T &src, AnyRegister dest, Register temp, Label *fail);
+    void loadFromTypedArray(Scalar::Type arrayType, const T &src, AnyRegister dest, Register temp, Label *fail,
+                            bool canonicalizeDoubles = true);
 
     template<typename T>
     void loadFromTypedArray(Scalar::Type arrayType, const T &src, const ValueOperand &dest, bool allowDouble,
                             Register temp, Label *fail);
 
     template<typename S, typename T>
     void storeToTypedIntArray(Scalar::Type arrayType, const S &value, const T &dest) {
         switch (arrayType) {
@@ -727,16 +728,27 @@ class MacroAssembler : public MacroAssem
 
     template<typename S, typename T>
     void atomicBinopToTypedIntArray(AtomicOp op, Scalar::Type arrayType, const S &value,
                                     const T &mem, Register temp1, Register temp2, AnyRegister output);
 
     void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const BaseIndex &dest);
     void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const Address &dest);
 
+    // Load a property from an UnboxedPlainObject.
+    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);
+
     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;
@@ -799,17 +811,17 @@ class MacroAssembler : public MacroAssem
     void initGCSlots(Register obj, Register temp, NativeObject *templateObj, bool initFixedSlots);
 
   public:
     void callMallocStub(size_t nbytes, Register result, Label *fail);
     void callFreeStub(Register slots);
     void createGCObject(Register result, Register temp, JSObject *templateObj,
                         gc::InitialHeap initialHeap, Label *fail, bool initFixedSlots = true);
 
-    void newGCThing(Register result, Register temp, NativeObject *templateObj,
+    void newGCThing(Register result, Register temp, JSObject *templateObj,
                      gc::InitialHeap initialHeap, Label *fail);
     void initGCThing(Register obj, Register temp, JSObject *templateObj,
                      bool initFixedSlots = true);
 
     void newGCString(Register result, Register temp, Label *fail);
     void newGCFatInlineString(Register result, Register temp, Label *fail);
 
     // Compares two strings for equality based on the JSOP.
--- a/js/src/jit/OptimizationTracking.h
+++ b/js/src/jit/OptimizationTracking.h
@@ -27,31 +27,35 @@ namespace jit {
     _(GetProp_InferredConstant,                         \
       "getprop inferred constant")                      \
     _(GetProp_Constant,                                 \
       "getprop constant")                               \
     _(GetProp_TypedObject,                              \
       "getprop TypedObject")                            \
     _(GetProp_DefiniteSlot,                             \
       "getprop definite slot")                          \
+    _(GetProp_Unboxed,                                  \
+      "getprop unboxed object")                         \
     _(GetProp_CommonGetter,                             \
       "getprop common getter")                          \
     _(GetProp_InlineAccess,                             \
       "getprop inline access")                          \
     _(GetProp_Innerize,                                 \
       "getprop innerize (access on global window)")     \
     _(GetProp_InlineCache,                              \
       "getprop IC")                                     \
                                                         \
     _(SetProp_CommonSetter,                             \
       "setprop common setter")                          \
     _(SetProp_TypedObject,                              \
       "setprop TypedObject")                            \
     _(SetProp_DefiniteSlot,                             \
       "setprop definite slot")                          \
+    _(SetProp_Unboxed,                                  \
+      "setprop unboxed object")                         \
     _(SetProp_InlineAccess,                             \
       "setprop inline access")                          \
                                                         \
     _(GetElem_TypedObject,                              \
       "getprop TypedObject")                            \
     _(GetElem_Dense,                                    \
       "getelem dense")                                  \
     _(GetElem_TypedStatic,                              \
@@ -103,22 +107,30 @@ namespace jit {
     _(UnknownProperties,                                                \
       "unknown properties")                                             \
     _(Singleton,                                                        \
       "is singleton")                                                   \
     _(NotSingleton,                                                     \
       "is not singleton")                                               \
     _(NotFixedSlot,                                                     \
       "property not in fixed slot")                                     \
+    _(InconsistentFixedSlot,                                            \
+      "property not in a consistent fixed slot")                        \
     _(NotObject,                                                        \
       "not definitely an object")                                       \
     _(NotStruct,                                                        \
       "not definitely a TypedObject struct")                            \
+    _(NotUnboxed,                                                       \
+      "not definitely an unboxed object")                               \
     _(StructNoField,                                                    \
       "struct doesn't definitely have field")                           \
+    _(InconsistentFieldType,                                            \
+      "unboxed property does not consistent type")                      \
+    _(InconsistentFieldOffset,                                          \
+      "unboxed property does not consistent offset")                    \
     _(NeedsTypeBarrier,                                                 \
       "needs type barrier")                                             \
     _(InDictionaryMode,                                                 \
       "object in dictionary mode")                                      \
     _(NoProtoFound,                                                     \
       "no proto found")                                                 \
     _(MultiProtoPaths,                                                  \
       "not all paths to property go through same proto")                \
--- a/js/src/jit/ScalarReplacement.cpp
+++ b/js/src/jit/ScalarReplacement.cpp
@@ -107,16 +107,20 @@ IsObjectEscaped(MInstruction *ins, JSObj
         obj = ins->toNewObject()->templateObject();
     else if (ins->isCreateThisWithTemplate())
         obj = ins->toCreateThisWithTemplate()->templateObject();
     else if (ins->isNewCallObject())
         obj = ins->toNewCallObject()->templateObject();
     else
         obj = objDefault;
 
+    // Don't optimize unboxed objects, which aren't handled by MObjectState.
+    if (obj->is<UnboxedPlainObject>())
+        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.
     for (MUseIterator i(ins->usesBegin()); i != ins->usesEnd(); i++) {
         MNode *consumer = (*i)->consumer();
         if (!consumer->isDefinition()) {
             // Cannot optimize if it is observable from fun.arguments or others.
             if (!consumer->toResumePoint()->isRecoverableOperand(*i)) {
--- a/js/src/jit/arm/Lowering-arm.cpp
+++ b/js/src/jit/arm/Lowering-arm.cpp
@@ -388,17 +388,17 @@ LIRGeneratorARM::visitGuardShape(MGuardS
 
 void
 LIRGeneratorARM::visitGuardObjectType(MGuardObjectType *ins)
 {
     MOZ_ASSERT(ins->obj()->type() == MIRType_Object);
 
     LDefinition tempObj = temp(LDefinition::OBJECT);
     LGuardObjectType *guard = new(alloc()) LGuardObjectType(useRegister(ins->obj()), tempObj);
-    assignSnapshot(guard, Bailout_ObjectIdentityOrTypeGuard);
+    assignSnapshot(guard, ins->bailoutKind());
     add(guard, ins);
     redefine(ins, ins->obj());
 }
 
 void
 LIRGeneratorARM::lowerUrshD(MUrsh *mir)
 {
     MDefinition *lhs = mir->lhs();
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -2681,16 +2681,30 @@ MacroAssemblerARMCompat::cmpPtr(const Ad
 
 void
 MacroAssemblerARMCompat::cmpPtr(const Address &lhs, ImmPtr rhs)
 {
     cmpPtr(lhs, ImmWord(uintptr_t(rhs.value)));
 }
 
 void
+MacroAssemblerARMCompat::cmpPtr(const Address &lhs, ImmGCPtr rhs)
+{
+    loadPtr(lhs, secondScratchReg_);
+    ma_cmp(secondScratchReg_, rhs);
+}
+
+void
+MacroAssemblerARMCompat::cmpPtr(const Address &lhs, Imm32 rhs)
+{
+    loadPtr(lhs, secondScratchReg_);
+    ma_cmp(secondScratchReg_, rhs);
+}
+
+void
 MacroAssemblerARMCompat::setStackArg(Register reg, uint32_t arg)
 {
     ma_dataTransferN(IsStore, 32, true, sp, Imm32(arg * sizeof(intptr_t)), reg);
 
 }
 
 void
 MacroAssemblerARMCompat::subPtr(Imm32 imm, const Register dest)
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -1124,16 +1124,29 @@ class MacroAssemblerARMCompat : public M
         else
             load32(address, dest.gpr());
     }
 
     template <typename T>
     void storeUnboxedValue(ConstantOrRegister value, MIRType valueType, const T &dest,
                            MIRType slotType);
 
+    template <typename T>
+    void storeUnboxedPayload(ValueOperand value, T address, size_t nbytes) {
+        switch (nbytes) {
+          case 4:
+            storePtr(value.payloadReg(), address);
+            return;
+          case 1:
+            store8(value.payloadReg(), address);
+            return;
+          default: MOZ_CRASH("Bad payload width");
+        }
+    }
+
     void moveValue(const Value &val, const ValueOperand &dest);
 
     void moveValue(const ValueOperand &src, const ValueOperand &dest) {
         Register s0 = src.typeReg(), d0 = dest.typeReg(),
                  s1 = src.payloadReg(), d1 = dest.payloadReg();
 
         // Either one or both of the source registers could be the same as a
         // destination register.
@@ -1617,24 +1630,26 @@ class MacroAssemblerARMCompat : public M
         add32(Imm32(1), ToPayload(addr));
     }
 
     void cmp32(Register lhs, Imm32 rhs);
     void cmp32(Register lhs, Register rhs);
     void cmp32(const Operand &lhs, Imm32 rhs);
     void cmp32(const Operand &lhs, Register rhs);
 
+    void cmpPtr(Register lhs, Register rhs);
     void cmpPtr(Register lhs, ImmWord rhs);
     void cmpPtr(Register lhs, ImmPtr rhs);
-    void cmpPtr(Register lhs, Register rhs);
     void cmpPtr(Register lhs, ImmGCPtr rhs);
     void cmpPtr(Register lhs, Imm32 rhs);
     void cmpPtr(const Address &lhs, Register rhs);
     void cmpPtr(const Address &lhs, ImmWord rhs);
     void cmpPtr(const Address &lhs, ImmPtr rhs);
+    void cmpPtr(const Address &lhs, ImmGCPtr rhs);
+    void cmpPtr(const Address &lhs, Imm32 rhs);
 
     void subPtr(Imm32 imm, const Register dest);
     void subPtr(const Address &addr, const Register dest);
     void subPtr(Register src, Register dest);
     void subPtr(Register src, const Address &dest);
     void addPtr(Imm32 imm, const Register dest);
     void addPtr(Imm32 imm, const Address &dest);
     void addPtr(ImmWord imm, const Register dest) {
--- a/js/src/jit/mips/Lowering-mips.cpp
+++ b/js/src/jit/mips/Lowering-mips.cpp
@@ -379,17 +379,17 @@ LIRGeneratorMIPS::visitGuardShape(MGuard
 
 void
 LIRGeneratorMIPS::visitGuardObjectType(MGuardObjectType *ins)
 {
     MOZ_ASSERT(ins->obj()->type() == MIRType_Object);
 
     LDefinition tempObj = temp(LDefinition::OBJECT);
     LGuardObjectType *guard = new(alloc()) LGuardObjectType(useRegister(ins->obj()), tempObj);
-    assignSnapshot(guard, Bailout_ObjectIdentityOrTypeGuard);
+    assignSnapshot(guard, ins->bailoutKind());
     add(guard, ins);
     redefine(ins, ins->obj());
 }
 
 void
 LIRGeneratorMIPS::lowerUrshD(MUrsh *mir)
 {
     MDefinition *lhs = mir->lhs();
--- a/js/src/jit/mips/MacroAssembler-mips.h
+++ b/js/src/jit/mips/MacroAssembler-mips.h
@@ -864,16 +864,29 @@ public:
         else
             load32(address, dest.gpr());
     }
 
     template <typename T>
     void storeUnboxedValue(ConstantOrRegister value, MIRType valueType, const T &dest,
                            MIRType slotType);
 
+    template <typename T>
+    void storeUnboxedPayload(ValueOperand value, T address, size_t nbytes) {
+        switch (nbytes) {
+          case 4:
+            storePtr(value.payloadReg(), address);
+            return;
+          case 1:
+            store8(value.payloadReg(), address);
+            return;
+          default: MOZ_CRASH("Bad payload width");
+        }
+    }
+
     void moveValue(const Value &val, const ValueOperand &dest);
 
     void moveValue(const ValueOperand &src, const ValueOperand &dest) {
         Register s0 = src.typeReg(), d0 = dest.typeReg(),
                  s1 = src.payloadReg(), d1 = dest.payloadReg();
 
         // Either one or both of the source registers could be the same as a
         // destination register.
--- a/js/src/jit/none/MacroAssembler-none.h
+++ b/js/src/jit/none/MacroAssembler-none.h
@@ -395,16 +395,17 @@ class MacroAssemblerNone : public Assemb
     Condition testStringTruthy(bool, ValueOperand) { MOZ_CRASH(); }
     void branchTestInt32Truthy(bool, ValueOperand, Label *) { MOZ_CRASH(); }
     void branchTestBooleanTruthy(bool, ValueOperand, Label *) { MOZ_CRASH(); }
     void branchTestStringTruthy(bool, ValueOperand, Label *) { MOZ_CRASH(); }
     void branchTestDoubleTruthy(bool, FloatRegister, Label *) { MOZ_CRASH(); }
 
     template <typename T> void loadUnboxedValue(T, MIRType, AnyRegister) { MOZ_CRASH(); }
     template <typename T> void storeUnboxedValue(ConstantOrRegister, MIRType, T, MIRType) { MOZ_CRASH(); }
+    template <typename T> void storeUnboxedPayload(ValueOperand value, T, size_t) { MOZ_CRASH(); }
 
     void rshiftPtr(Imm32, Register) { MOZ_CRASH(); }
     void rshiftPtrArithmetic(Imm32, Register) { MOZ_CRASH(); }
     void lshiftPtr(Imm32, Register) { MOZ_CRASH(); }
     template <typename T, typename S> void xorPtr(T, S) { MOZ_CRASH(); }
     template <typename T, typename S> void xor32(T, S) { MOZ_CRASH(); }
     template <typename T, typename S> void orPtr(T, S) { MOZ_CRASH(); }
     template <typename T, typename S> void or32(T, S) { MOZ_CRASH(); }
--- a/js/src/jit/shared/Lowering-x86-shared.cpp
+++ b/js/src/jit/shared/Lowering-x86-shared.cpp
@@ -44,17 +44,17 @@ LIRGeneratorX86Shared::visitGuardShape(M
 }
 
 void
 LIRGeneratorX86Shared::visitGuardObjectType(MGuardObjectType *ins)
 {
     MOZ_ASSERT(ins->obj()->type() == MIRType_Object);
 
     LGuardObjectType *guard = new(alloc()) LGuardObjectType(useRegisterAtStart(ins->obj()));
-    assignSnapshot(guard, Bailout_ObjectIdentityOrTypeGuard);
+    assignSnapshot(guard, ins->bailoutKind());
     add(guard, ins);
     redefine(ins, ins->obj());
 }
 
 void
 LIRGeneratorX86Shared::visitPowHalf(MPowHalf *ins)
 {
     MDefinition *input = ins->input();
--- a/js/src/jit/x64/MacroAssembler-x64.h
+++ b/js/src/jit/x64/MacroAssembler-x64.h
@@ -1337,16 +1337,33 @@ class MacroAssemblerX64 : public MacroAs
             movl(Operand(src), dest.gpr());
         else
             unboxNonDouble(Operand(src), dest.gpr());
     }
 
     template <typename T>
     void storeUnboxedValue(ConstantOrRegister value, MIRType valueType, const T &dest, MIRType slotType);
 
+    template <typename T>
+    void storeUnboxedPayload(ValueOperand value, T address, size_t nbytes) {
+        switch (nbytes) {
+          case 8:
+            unboxNonDouble(value, ScratchReg);
+            storePtr(ScratchReg, address);
+            return;
+          case 4:
+            store32(value.valueReg(), address);
+            return;
+          case 1:
+            store8(value.valueReg(), address);
+            return;
+          default: MOZ_CRASH("Bad payload width");
+        }
+    }
+
     void loadInstructionPointerAfterCall(Register dest) {
         loadPtr(Address(StackPointer, 0x0), dest);
     }
 
     void convertUInt32ToDouble(Register src, FloatRegister dest) {
         vcvtsq2sd(src, dest, dest);
     }
 
--- a/js/src/jit/x86/MacroAssembler-x86.h
+++ b/js/src/jit/x86/MacroAssembler-x86.h
@@ -545,16 +545,19 @@ class MacroAssemblerX86 : public MacroAs
         cmp32(lhs, rhs);
     }
     void cmpPtr(const Address &lhs, const ImmWord rhs) {
         cmpPtr(Operand(lhs), rhs);
     }
     void cmpPtr(const Address &lhs, const ImmPtr rhs) {
         cmpPtr(lhs, ImmWord(uintptr_t(rhs.value)));
     }
+    void cmpPtr(const Address &lhs, const ImmGCPtr rhs) {
+        cmpPtr(Operand(lhs), rhs);
+    }
     void cmpPtr(Register lhs, Register rhs) {
         cmp32(lhs, rhs);
     }
     void cmpPtr(const Operand &lhs, ImmMaybeNurseryPtr rhs) {
         cmpl(rhs, lhs);
     }
     void testPtr(Register lhs, Register rhs) {
         test32(lhs, rhs);
@@ -1060,16 +1063,29 @@ class MacroAssemblerX86 : public MacroAs
         else
             movl(Operand(src), dest.gpr());
     }
 
     template <typename T>
     void storeUnboxedValue(ConstantOrRegister value, MIRType valueType, const T &dest,
                            MIRType slotType);
 
+    template <typename T>
+    void storeUnboxedPayload(ValueOperand value, T address, size_t nbytes) {
+        switch (nbytes) {
+          case 4:
+            storePtr(value.payloadReg(), address);
+            return;
+          case 1:
+            store8(value.payloadReg(), address);
+            return;
+          default: MOZ_CRASH("Bad payload width");
+        }
+    }
+
     void rshiftPtr(Imm32 imm, Register dest) {
         shrl(imm, dest);
     }
     void rshiftPtrArithmetic(Imm32 imm, Register dest) {
         sarl(imm, dest);
     }
     void lshiftPtr(Imm32 imm, Register dest) {
         shll(imm, dest);