Bug 1083600 - Use inline data for small transparent typed objects, r=sfink,nmatsakis.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 29 Oct 2014 11:14:53 -0700
changeset 212996 4ec33eddc6fcdcab1c8bc9bc5100bc2d699c17e3
parent 212995 33b93eb7e383198b49a04e026797f7eb08d5bace
child 212997 a9cab8a15d2bee231a85f2b30b70afd2ab59b525
push id27738
push usercbook@mozilla.com
push dateThu, 30 Oct 2014 13:46:07 +0000
treeherdermozilla-central@1aa1b23d799e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink, nmatsakis
bugs1083600
milestone36.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 1083600 - Use inline data for small transparent typed objects, r=sfink,nmatsakis.
js/public/MemoryMetrics.h
js/src/builtin/SIMD.cpp
js/src/builtin/TypedObject.cpp
js/src/builtin/TypedObject.h
js/src/gc/Nursery.cpp
js/src/gc/RootMarking.cpp
js/src/jit-test/tests/TypedObject/inlinetransparent.js
js/src/jit/CodeGenerator.cpp
js/src/jit/CodeGenerator.h
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
js/src/jit/LIR-Common.h
js/src/jit/LOpcodes.h
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MCallOptimize.cpp
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jit/ParallelSafetyAnalysis.cpp
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsinfer.h
js/src/jsobjinlines.h
js/src/vm/ArrayBufferObject.cpp
js/src/vm/ArrayBufferObject.h
js/src/vm/MemoryMetrics.cpp
js/xpconnect/src/XPCJSRuntime.cpp
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -534,16 +534,17 @@ struct CompartmentStats
     macro(Other,   NotLiveGCThing, ionData) \
     macro(Other,   NotLiveGCThing, typeInferenceTypeScripts) \
     macro(Other,   NotLiveGCThing, typeInferenceAllocationSiteTables) \
     macro(Other,   NotLiveGCThing, typeInferenceArrayTypeTables) \
     macro(Other,   NotLiveGCThing, typeInferenceObjectTypeTables) \
     macro(Other,   NotLiveGCThing, compartmentObject) \
     macro(Other,   NotLiveGCThing, compartmentTables) \
     macro(Other,   NotLiveGCThing, innerViewsTable) \
+    macro(Other,   NotLiveGCThing, lazyArrayBuffersTable) \
     macro(Other,   NotLiveGCThing, crossCompartmentWrappersTable) \
     macro(Other,   NotLiveGCThing, regexpCompartment) \
     macro(Other,   NotLiveGCThing, savedStacksSet)
 
     CompartmentStats()
       : FOR_EACH_SIZE(ZERO_SIZE)
         classInfo(),
         extra(),
--- a/js/src/builtin/SIMD.cpp
+++ b/js/src/builtin/SIMD.cpp
@@ -82,17 +82,17 @@ js::ToSimdConstant(JSContext *cx, Handle
 
 template bool js::ToSimdConstant<Int32x4>(JSContext *cx, HandleValue v, jit::SimdConstant *out);
 template bool js::ToSimdConstant<Float32x4>(JSContext *cx, HandleValue v, jit::SimdConstant *out);
 
 template<typename Elem>
 static Elem
 TypedObjectMemory(HandleValue v)
 {
-    OutlineTypedObject &obj = v.toObject().as<OutlineTypedObject>();
+    TypedObject &obj = v.toObject().as<TypedObject>();
     return reinterpret_cast<Elem>(obj.typedMem());
 }
 
 template<typename SimdType, int lane>
 static bool GetSimdLane(JSContext *cx, unsigned argc, Value *vp)
 {
     typedef typename SimdType::Elem Elem;
 
@@ -133,17 +133,17 @@ static bool SignMask(JSContext *cx, unsi
     CallArgs args = CallArgsFromVp(argc, vp);
     if (!args.thisv().isObject() || !args.thisv().toObject().is<TypedObject>()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
                              SimdTypeDescr::class_.name, "signMask",
                              InformalValueTypeName(args.thisv()));
         return false;
     }
 
-    OutlineTypedObject &typedObj = args.thisv().toObject().as<OutlineTypedObject>();
+    TypedObject &typedObj = args.thisv().toObject().as<TypedObject>();
     TypeDescr &descr = typedObj.typeDescr();
     if (descr.kind() != type::Simd || descr.as<SimdTypeDescr>().type() != SimdType::type) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
                              SimdTypeDescr::class_.name, "signMask",
                              InformalValueTypeName(args.thisv()));
         return false;
     }
 
@@ -317,17 +317,17 @@ SimdTypeDescr::call(JSContext *cx, unsig
     }
 
     if (args.length() < LANES) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
                              args.callee().getClass()->name, "3", "s");
         return false;
     }
 
-    Rooted<TypedObject*> result(cx, OutlineTypedObject::createZeroed(cx, descr, 0));
+    Rooted<TypedObject*> result(cx, TypedObject::createZeroed(cx, descr, 0));
     if (!result)
         return false;
 
     switch (descr->type()) {
       case SimdTypeDescr::TYPE_INT32: {
         int32_t *mem = reinterpret_cast<int32_t*>(result->typedMem());
         for (unsigned i = 0; i < 4; i++) {
             if (!ToInt32(cx, args[i], &mem[i]))
@@ -445,17 +445,17 @@ js_InitSIMDClass(JSContext *cx, HandleOb
 template<typename V>
 JSObject *
 js::CreateSimd(JSContext *cx, typename V::Elem *data)
 {
     typedef typename V::Elem Elem;
     Rooted<TypeDescr*> typeDescr(cx, &V::GetTypeDescr(*cx->global()));
     MOZ_ASSERT(typeDescr);
 
-    Rooted<TypedObject *> result(cx, OutlineTypedObject::createZeroed(cx, typeDescr, 0));
+    Rooted<TypedObject *> result(cx, TypedObject::createZeroed(cx, typeDescr, 0));
     if (!result)
         return nullptr;
 
     Elem *resultMem = reinterpret_cast<Elem *>(result->typedMem());
     memcpy(resultMem, data, sizeof(Elem) * V::lanes);
     return result;
 }
 
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -1455,17 +1455,17 @@ js_InitTypedObjectDummy(JSContext *cx, H
 
 /******************************************************************************
  * Typed objects
  */
 
 int32_t
 TypedObject::offset() const
 {
-    if (is<InlineOpaqueTypedObject>())
+    if (is<InlineTypedObject>())
         return 0;
     return typedMem() - typedMemBase();
 }
 
 int32_t
 TypedObject::length() const
 {
     if (typeDescr().is<SizedArrayTypeDescr>())
@@ -1473,64 +1473,82 @@ TypedObject::length() const
     return as<OutlineTypedObject>().unsizedLength();
 }
 
 uint8_t *
 TypedObject::typedMem() const
 {
     MOZ_ASSERT(isAttached());
 
-    if (is<InlineOpaqueTypedObject>())
-        return as<InlineOpaqueTypedObject>().inlineTypedMem();
+    if (is<InlineTypedObject>())
+        return as<InlineTypedObject>().inlineTypedMem();
     return as<OutlineTypedObject>().outOfLineTypedMem();
 }
 
 uint8_t *
 TypedObject::typedMemBase() const
 {
     MOZ_ASSERT(isAttached());
     MOZ_ASSERT(is<OutlineTypedObject>());
 
     JSObject &owner = as<OutlineTypedObject>().owner();
     if (owner.is<ArrayBufferObject>())
         return owner.as<ArrayBufferObject>().dataPointer();
-    return owner.as<InlineOpaqueTypedObject>().inlineTypedMem();
+    return owner.as<InlineTypedObject>().inlineTypedMem();
 }
 
 bool
 TypedObject::isAttached() const
 {
+    if (is<InlineTransparentTypedObject>()) {
+        LazyArrayBufferTable *table = compartment()->lazyArrayBuffers;
+        if (table) {
+            ArrayBufferObject *buffer =
+                table->maybeBuffer(&const_cast<TypedObject *>(this)->as<InlineTransparentTypedObject>());
+            if (buffer)
+                return !buffer->isNeutered();
+        }
+        return true;
+    }
     if (is<InlineOpaqueTypedObject>())
         return true;
     if (!as<OutlineTypedObject>().outOfLineTypedMem())
         return false;
     JSObject &owner = as<OutlineTypedObject>().owner();
     if (owner.is<ArrayBufferObject>() && owner.as<ArrayBufferObject>().isNeutered())
         return false;
     return true;
 }
 
 bool
 TypedObject::maybeForwardedIsAttached() const
 {
-    if (is<InlineOpaqueTypedObject>())
+    if (is<InlineTypedObject>())
         return true;
     if (!as<OutlineTypedObject>().outOfLineTypedMem())
         return false;
     JSObject &owner = *MaybeForwarded(&as<OutlineTypedObject>().owner());
     if (owner.is<ArrayBufferObject>() && owner.as<ArrayBufferObject>().isNeutered())
         return false;
     return true;
 }
 
 /* static */ bool
 TypedObject::GetBuffer(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    args.rval().setObject(args[0].toObject().as<TransparentTypedObject>().owner());
+    JSObject &obj = args[0].toObject();
+    ArrayBufferObject *buffer;
+    if (obj.is<OutlineTransparentTypedObject>())
+        buffer = obj.as<OutlineTransparentTypedObject>().getOrCreateBuffer(cx);
+    else
+        buffer = obj.as<InlineTransparentTypedObject>().getOrCreateBuffer(cx);
+    if (!buffer)
+        MOZ_CRASH();
+    args.rval().setObject(*buffer);
     return true;
 }
 
 /* static */ bool
 TypedObject::GetByteOffset(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setInt32(args[0].toObject().as<TypedObject>().offset());
@@ -1544,17 +1562,17 @@ TypedObject::GetByteOffset(JSContext *cx
 /*static*/ OutlineTypedObject *
 OutlineTypedObject::createUnattached(JSContext *cx,
                                      HandleTypeDescr descr,
                                      int32_t length)
 {
     if (descr->opaque())
         return createUnattachedWithClass(cx, &OutlineOpaqueTypedObject::class_, descr, length);
     else
-        return createUnattachedWithClass(cx, &TransparentTypedObject::class_, descr, length);
+        return createUnattachedWithClass(cx, &OutlineTransparentTypedObject::class_, descr, length);
 }
 
 static JSObject *
 PrototypeForTypeDescr(JSContext *cx, HandleTypeDescr descr)
 {
     if (descr->is<SimpleTypeDescr>()) {
         // FIXME Bug 929651 -- What prototype to use?
         return cx->global()->getOrCreateObjectPrototype(cx);
@@ -1585,17 +1603,17 @@ OutlineTypedObject::setOwnerAndData(JSOb
 }
 
 /*static*/ OutlineTypedObject *
 OutlineTypedObject::createUnattachedWithClass(JSContext *cx,
                                               const Class *clasp,
                                               HandleTypeDescr type,
                                               int32_t length)
 {
-    MOZ_ASSERT(clasp == &TransparentTypedObject::class_ ||
+    MOZ_ASSERT(clasp == &OutlineTransparentTypedObject::class_ ||
                clasp == &OutlineOpaqueTypedObject::class_);
 
     RootedObject proto(cx, PrototypeForTypeDescr(cx, type));
     if (!proto)
         return nullptr;
 
     gc::AllocKind allocKind = allocKindForTypeDescriptor(type);
     JSObject *obj = NewObjectWithClassProto(cx, clasp, proto, nullptr, allocKind);
@@ -1613,16 +1631,19 @@ OutlineTypedObject::createUnattachedWith
 
 void
 OutlineTypedObject::attach(JSContext *cx, ArrayBufferObject &buffer, int32_t offset)
 {
     MOZ_ASSERT(!isAttached());
     MOZ_ASSERT(offset >= 0);
     MOZ_ASSERT((size_t) (offset + size()) <= buffer.byteLength());
 
+    if (typeDescr().is<SizedTypeDescr>())
+        buffer.setHasSizedObjectViews();
+
     if (!buffer.addView(cx, this))
         CrashAtUnhandlableOOM("TypedObject::attach");
 
     setOwnerAndData(&buffer, buffer.dataPointer() + offset);
 }
 
 void
 OutlineTypedObject::attach(JSContext *cx, TypedObject &typedObj, int32_t offset)
@@ -1634,18 +1655,18 @@ OutlineTypedObject::attach(JSContext *cx
     if (typedObj.is<OutlineTypedObject>()) {
         owner = &typedObj.as<OutlineTypedObject>().owner();
         offset += typedObj.offset();
     }
 
     if (owner->is<ArrayBufferObject>()) {
         attach(cx, owner->as<ArrayBufferObject>(), offset);
     } else {
-        MOZ_ASSERT(owner->is<InlineOpaqueTypedObject>());
-        setOwnerAndData(owner, owner->as<InlineOpaqueTypedObject>().inlineTypedMem() + offset);
+        MOZ_ASSERT(owner->is<InlineTypedObject>());
+        setOwnerAndData(owner, owner->as<InlineTypedObject>().inlineTypedMem() + offset);
     }
 }
 
 // Returns a suitable JS_TYPEDOBJ_SLOT_LENGTH value for an instance of
 // the type `type`. `type` must not be an unsized array.
 static int32_t
 TypedObjLengthFromType(TypeDescr &descr)
 {
@@ -1669,37 +1690,36 @@ TypedObjLengthFromType(TypeDescr &descr)
 OutlineTypedObject::createDerived(JSContext *cx, HandleSizedTypeDescr type,
                                   HandleTypedObject typedObj, int32_t offset)
 {
     MOZ_ASSERT(offset <= typedObj->size());
     MOZ_ASSERT(offset + type->size() <= typedObj->size());
 
     int32_t length = TypedObjLengthFromType(*type);
 
-    const js::Class *clasp = typedObj->is<TransparentTypedObject>()
-                             ? &TransparentTypedObject::class_
-                             : &OutlineOpaqueTypedObject::class_;
+    const js::Class *clasp = typedObj->opaque()
+                             ? &OutlineOpaqueTypedObject::class_
+                             : &OutlineTransparentTypedObject::class_;
     Rooted<OutlineTypedObject*> obj(cx);
     obj = createUnattachedWithClass(cx, clasp, type, length);
     if (!obj)
         return nullptr;
 
     obj->attach(cx, *typedObj, offset);
     return obj;
 }
 
 /*static*/ TypedObject *
 TypedObject::createZeroed(JSContext *cx, HandleTypeDescr descr, int32_t length)
 {
     // If possible, create an object with inline data.
-    if (descr->opaque() &&
-        descr->is<SizedTypeDescr>() &&
-        (size_t) descr->as<SizedTypeDescr>().size() <= InlineOpaqueTypedObject::MaximumSize)
+    if (descr->is<SizedTypeDescr>() &&
+        (size_t) descr->as<SizedTypeDescr>().size() <= InlineTypedObject::MaximumSize)
     {
-        InlineOpaqueTypedObject *obj = InlineOpaqueTypedObject::create(cx, descr);
+        InlineTypedObject *obj = InlineTypedObject::create(cx, descr);
         descr->as<SizedTypeDescr>().initInstances(cx->runtime(), obj->inlineTypedMem(), 1);
         return obj;
     }
 
     // Create unattached wrapper object.
     Rooted<OutlineTypedObject*> obj(cx, OutlineTypedObject::createUnattached(cx, descr, length));
     if (!obj)
         return nullptr;
@@ -1786,17 +1806,17 @@ OutlineTypedObject::obj_trace(JSTracer *
     gc::MarkObjectUnbarriered(trc, &typedObj.owner_, "typed object owner");
     JSObject *owner = typedObj.owner_;
 
     uint8_t *mem = typedObj.outOfLineTypedMem();
 
     // Update the data pointer if the owner moved and the owner's data is
     // inline with it.
     if (owner != oldOwner &&
-        (owner->is<InlineOpaqueTypedObject>() ||
+        (owner->is<InlineTypedObject>() ||
          owner->as<ArrayBufferObject>().hasInlineData()))
     {
         mem += reinterpret_cast<uint8_t *>(owner) - reinterpret_cast<uint8_t *>(oldOwner);
         typedObj.setData(mem);
     }
 
     if (!descr.opaque() || !typedObj.maybeForwardedIsAttached())
         return;
@@ -2348,51 +2368,161 @@ OutlineTypedObject::neuter(void *newData
         setUnsizedLength(0);
     setData(reinterpret_cast<uint8_t *>(newData));
 }
 
 /******************************************************************************
  * Inline typed objects
  */
 
-/* static */ InlineOpaqueTypedObject *
-InlineOpaqueTypedObject::create(JSContext *cx, HandleTypeDescr descr)
+/* static */ InlineTypedObject *
+InlineTypedObject::create(JSContext *cx, HandleTypeDescr descr)
 {
     gc::AllocKind allocKind = allocKindForTypeDescriptor(descr);
 
     RootedObject proto(cx, PrototypeForTypeDescr(cx, descr));
     if (!proto)
         return nullptr;
 
-    RootedObject obj(cx, NewObjectWithClassProto(cx, &class_, proto, nullptr, allocKind));
+    const Class *clasp = descr->opaque()
+                         ? &InlineOpaqueTypedObject::class_
+                         : &InlineTransparentTypedObject::class_;
+
+    RootedObject obj(cx, NewObjectWithClassProto(cx, clasp, proto, nullptr, allocKind));
     if (!obj)
         return nullptr;
 
-    return &obj->as<InlineOpaqueTypedObject>();
-}
-
-/* static */
-size_t
-InlineOpaqueTypedObject::offsetOfDataStart()
-{
-    return NativeObject::getFixedSlotOffset(0);
+    return &obj->as<InlineTypedObject>();
 }
 
 /* static */ void
-InlineOpaqueTypedObject::obj_trace(JSTracer *trc, JSObject *object)
+InlineTypedObject::obj_trace(JSTracer *trc, JSObject *object)
 {
-    InlineOpaqueTypedObject &typedObj = object->as<InlineOpaqueTypedObject>();
+    InlineTypedObject &typedObj = object->as<InlineTypedObject>();
 
     // When this is called for compacting GC, the related objects we touch here
     // may not have had their slots updated yet.
     TypeDescr &descr = typedObj.maybeForwardedTypeDescr();
 
     descr.as<SizedTypeDescr>().traceInstances(trc, typedObj.inlineTypedMem(), 1);
 }
 
+ArrayBufferObject *
+InlineTransparentTypedObject::getOrCreateBuffer(JSContext *cx)
+{
+    LazyArrayBufferTable *&table = cx->compartment()->lazyArrayBuffers;
+    if (!table) {
+        table = cx->new_<LazyArrayBufferTable>(cx);
+        if (!table)
+            return nullptr;
+    }
+
+    ArrayBufferObject *buffer = table->maybeBuffer(this);
+    if (buffer)
+        return buffer;
+
+    ArrayBufferObject::BufferContents contents =
+        ArrayBufferObject::BufferContents::createPlain(inlineTypedMem());
+    size_t nbytes = typeDescr().as<SizedTypeDescr>().size();
+
+    // Prevent GC under ArrayBufferObject::create, which might move this object
+    // and its contents.
+    gc::AutoSuppressGC suppress(cx);
+
+    buffer = ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::DoesntOwnData);
+    if (!buffer)
+        return nullptr;
+
+    // The owning object must always be the array buffer's first view. This
+    // both prevents the memory from disappearing out from under the buffer
+    // (the first view is held strongly by the buffer) and is used by the
+    // buffer marking code to detect whether its data pointer needs to be
+    // relocated.
+    JS_ALWAYS_TRUE(buffer->addView(cx, this));
+
+    buffer->setForInlineTypedObject();
+    buffer->setHasSizedObjectViews();
+
+    if (!table->addBuffer(cx, this, buffer))
+        return nullptr;
+
+    return buffer;
+}
+
+ArrayBufferObject *
+OutlineTransparentTypedObject::getOrCreateBuffer(JSContext *cx)
+{
+    if (owner().is<ArrayBufferObject>())
+        return &owner().as<ArrayBufferObject>();
+    return owner().as<InlineTransparentTypedObject>().getOrCreateBuffer(cx);
+}
+
+LazyArrayBufferTable::LazyArrayBufferTable(JSContext *cx)
+ : map(cx)
+{
+    if (!map.init())
+        CrashAtUnhandlableOOM("LazyArrayBufferTable");
+}
+
+LazyArrayBufferTable::~LazyArrayBufferTable()
+{
+    WeakMapBase::removeWeakMapFromList(&map);
+}
+
+ArrayBufferObject *
+LazyArrayBufferTable::maybeBuffer(InlineTransparentTypedObject *obj)
+{
+    if (Map::Ptr p = map.lookup(obj))
+        return &p->value()->as<ArrayBufferObject>();
+    return nullptr;
+}
+
+bool
+LazyArrayBufferTable::addBuffer(JSContext *cx, InlineTransparentTypedObject *obj, ArrayBufferObject *buffer)
+{
+    MOZ_ASSERT(!map.has(obj));
+    if (!map.put(obj, buffer)) {
+        js_ReportOutOfMemory(cx);
+        return false;
+    }
+
+#ifdef JSGC_GENERATIONAL
+    MOZ_ASSERT(!IsInsideNursery(buffer));
+    if (IsInsideNursery(obj)) {
+        // Strip the barriers from the type before inserting into the store
+        // buffer, as is done for DebugScopes::proxiedScopes.
+        Map::Base *baseHashMap = static_cast<Map::Base *>(&map);
+
+        typedef HashMap<JSObject *, JSObject *> UnbarrieredMap;
+        UnbarrieredMap *unbarrieredMap = reinterpret_cast<UnbarrieredMap *>(baseHashMap);
+
+        typedef gc::HashKeyRef<UnbarrieredMap, JSObject *> Ref;
+        cx->runtime()->gc.storeBuffer.putGeneric(Ref(unbarrieredMap, obj));
+
+        // Also make sure the buffer is traced, so that its data pointer is
+        // updated after the typed object moves.
+        cx->runtime()->gc.storeBuffer.putWholeCellFromMainThread(buffer);
+    }
+#endif
+
+    return true;
+}
+
+void
+LazyArrayBufferTable::trace(JSTracer *trc)
+{
+    map.trace(trc);
+}
+
+size_t
+LazyArrayBufferTable::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
+{
+    return mallocSizeOf(this) + map.sizeOfExcludingThis(mallocSizeOf);
+}
+
 /******************************************************************************
  * Typed object classes
  */
 
 #define DEFINE_TYPEDOBJ_CLASS(Name, Trace)        \
     const Class Name::class_ = {                         \
         # Name,                                          \
         Class::NON_NATIVE | JSCLASS_IMPLEMENTS_BARRIERS, \
@@ -2428,24 +2558,20 @@ InlineOpaqueTypedObject::obj_trace(JSTra
             TypedObject::obj_deleteGeneric,              \
             nullptr, nullptr, /* watch/unwatch */        \
             nullptr,   /* slice */                       \
             TypedObject::obj_enumerate,                  \
             nullptr, /* thisObject */                    \
         }                                                \
     }
 
-DEFINE_TYPEDOBJ_CLASS(TransparentTypedObject,
-                      OutlineTypedObject::obj_trace);
-
-DEFINE_TYPEDOBJ_CLASS(OutlineOpaqueTypedObject,
-                      OutlineTypedObject::obj_trace);
-
-DEFINE_TYPEDOBJ_CLASS(InlineOpaqueTypedObject,
-                      InlineOpaqueTypedObject::obj_trace);
+DEFINE_TYPEDOBJ_CLASS(OutlineTransparentTypedObject, OutlineTypedObject::obj_trace);
+DEFINE_TYPEDOBJ_CLASS(OutlineOpaqueTypedObject,      OutlineTypedObject::obj_trace);
+DEFINE_TYPEDOBJ_CLASS(InlineTransparentTypedObject,  InlineTypedObject::obj_trace);
+DEFINE_TYPEDOBJ_CLASS(InlineOpaqueTypedObject,       InlineTypedObject::obj_trace);
 
 static int32_t
 LengthForType(TypeDescr &descr)
 {
     switch (descr.kind()) {
       case type::Scalar:
       case type::Reference:
       case type::Struct:
@@ -2864,30 +2990,32 @@ JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js
                                       ObjectIsTypedObjectJitInfo,
                                       js::ObjectIsTypedObject);
 
 bool
 js::ObjectIsOpaqueTypedObject(ThreadSafeContext *, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 1);
-    args.rval().setBoolean(!args[0].toObject().is<TransparentTypedObject>());
+    JSObject &obj = args[0].toObject();
+    args.rval().setBoolean(obj.is<TypedObject>() && obj.as<TypedObject>().opaque());
     return true;
 }
 
 JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js::ObjectIsOpaqueTypedObjectJitInfo,
                                       ObjectIsOpaqueTypedObjectJitInfo,
                                       js::ObjectIsOpaqueTypedObject);
 
 bool
 js::ObjectIsTransparentTypedObject(ThreadSafeContext *, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 1);
-    args.rval().setBoolean(args[0].toObject().is<TransparentTypedObject>());
+    JSObject &obj = args[0].toObject();
+    args.rval().setBoolean(obj.is<TypedObject>() && !obj.as<TypedObject>().opaque());
     return true;
 }
 
 JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js::ObjectIsTransparentTypedObjectJitInfo,
                                       ObjectIsTransparentTypedObjectJitInfo,
                                       js::ObjectIsTransparentTypedObject);
 
 bool
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -3,16 +3,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef builtin_TypedObject_h
 #define builtin_TypedObject_h
 
 #include "jsobj.h"
+#include "jsweakmap.h"
 
 #include "builtin/TypedObjectConstants.h"
 #include "vm/ArrayBufferObject.h"
 
 /*
  * -------------
  * Typed Objects
  * -------------
@@ -57,29 +58,28 @@
  * sometimes accesses them).
  *
  * - Typed objects:
  *
  * A typed object is an instance of a *type object* (note the past participle).
  * Typed objects can be either transparent or opaque, depending on whether
  * their underlying buffer can be accessed. Transparent and opaque typed
  * objects have different classes, and can have different physical layouts.
- * The following layouts are currently possible:
- *
- * InlineOpaqueTypedObject: Typed objects whose data immediately follows the
- *   object's header are inline typed objects. These do not have an associated
- *   array buffer, so only opaque typed objects can be inline.
+ * The following layouts are possible:
  *
- * OutlineTypedObject: Transparent or opaque typed objects whose data is owned by
- *   another object, which can be either an array buffer or an inline typed
- *   object (opaque objects only). Outline typed objects may be attached or
- *   unattached. An unattached typed object has no memory associated with it.
- *   When first created, objects are always attached, but they can become
- *   unattached if their buffer is neutered (note that this implies that typed
- *   objects of opaque types can't be unattached).
+ * InlineTypedObject: Typed objects whose data immediately follows the object's
+ *   header are inline typed objects. The buffer for these objects is created
+ *   lazily and stored via the compartment's LazyArrayBufferTable, and points
+ *   back into the object's internal data.
+ *
+ * OutlineTypedObject: Typed objects whose data is owned by another object,
+ *   which can be either an array buffer or an inline typed object. Outline
+ *   typed objects may be attached or unattached. An unattached typed object
+ *   has no data associated with it. When first created, objects are always
+ *   attached, but they can become unattached if their buffer is neutered.
  *
  * Note that whether a typed object is opaque is not directly
  * connected to its type. That is, opaque types are *always*
  * represented by opaque typed objects, but you may have opaque typed
  * objects for transparent types too. This can occur for two reasons:
  * (1) a transparent type may be embedded within an opaque type or (2)
  * users can choose to convert transparent typed objects into opaque
  * ones to avoid giving access to the buffer itself.
@@ -666,16 +666,18 @@ class TypedObject : public JSObject
         // == size(), but it can happen when taking the "address of" a
         // 0-sized value. (In other words, we maintain the invariant
         // that `offset + size <= size()` -- this is always checked in
         // the caller's side.)
         MOZ_ASSERT(offset <= (size_t) size());
         return typedMem() + offset;
     }
 
+    inline bool opaque() const;
+
     // Creates a new typed object whose memory is freshly allocated and
     // initialized with zeroes (or, in the case of references, an appropriate
     // default value).
     static TypedObject *createZeroed(JSContext *cx, HandleTypeDescr typeObj, int32_t length);
 
     // User-accessible constructor (`new TypeDescriptor(...)`) used for sized
     // types. Note that the callee here is the type descriptor.
     static bool constructSized(JSContext *cx, unsigned argc, Value *vp);
@@ -779,62 +781,81 @@ class OutlineTypedObject : public TypedO
     void attach(JSContext *cx, TypedObject &typedObj, int32_t offset);
 
     // Invoked when array buffer is transferred elsewhere
     void neuter(void *newData);
 
     static void obj_trace(JSTracer *trace, JSObject *object);
 };
 
-// Class for a transparent typed object, whose owner is an array buffer.
-class TransparentTypedObject : public OutlineTypedObject
+// Class for a transparent typed object whose owner is an array buffer.
+class OutlineTransparentTypedObject : public OutlineTypedObject
 {
   public:
     static const Class class_;
+
+    ArrayBufferObject *getOrCreateBuffer(JSContext *cx);
 };
 
-// Class for an opaque typed object, whose owner may be either an array buffer
+// Class for an opaque typed object whose owner may be either an array buffer
 // or an opaque inlined typed object.
 class OutlineOpaqueTypedObject : public OutlineTypedObject
 {
   public:
     static const Class class_;
 };
 
-// Class for an opaque typed object whose data is allocated inline.
-class InlineOpaqueTypedObject : public TypedObject
+// Class for a typed object whose data is allocated inline.
+class InlineTypedObject : public TypedObject
 {
     // Start of the inline data, which immediately follows the shape and type.
     uint8_t data_[1];
 
   public:
-    static const Class class_;
-
     static const size_t MaximumSize = NativeObject::MAX_FIXED_SLOTS * sizeof(Value);
 
     static gc::AllocKind allocKindForTypeDescriptor(TypeDescr *descr) {
         size_t nbytes = descr->as<SizedTypeDescr>().size();
         MOZ_ASSERT(nbytes <= MaximumSize);
 
         size_t dataSlots = AlignBytes(nbytes, sizeof(Value) / sizeof(Value));
         MOZ_ASSERT(nbytes <= dataSlots * sizeof(Value));
         return gc::GetGCObjectKind(dataSlots);
     }
 
     uint8_t *inlineTypedMem() const {
-        static_assert(offsetof(InlineOpaqueTypedObject, data_) == sizeof(JSObject),
+        static_assert(offsetof(InlineTypedObject, data_) == sizeof(JSObject),
                       "The data for an inline typed object must follow the shape and type.");
         return (uint8_t *) &data_;
     }
 
     static void obj_trace(JSTracer *trace, JSObject *object);
 
-    static size_t offsetOfDataStart();
+    static size_t offsetOfDataStart() {
+        return offsetof(InlineTypedObject, data_);
+    }
+
+    static InlineTypedObject *create(JSContext *cx, HandleTypeDescr descr);
+};
 
-    static InlineOpaqueTypedObject *create(JSContext *cx, HandleTypeDescr descr);
+// Class for a transparent typed object with inline data, which may have a
+// lazily allocated array buffer.
+class InlineTransparentTypedObject : public InlineTypedObject
+{
+  public:
+    static const Class class_;
+
+    ArrayBufferObject *getOrCreateBuffer(JSContext *cx);
+};
+
+// Class for an opaque typed object with inline data and no array buffer.
+class InlineOpaqueTypedObject : public InlineTypedObject
+{
+  public:
+    static const Class class_;
 };
 
 /*
  * Usage: NewOpaqueTypedObject(typeObj)
  *
  * Constructs a new, unattached instance of `Handle`.
  */
 bool NewOpaqueTypedObject(JSContext *cx, unsigned argc, Value *vp);
@@ -1039,17 +1060,18 @@ class LoadReference##T {                
 JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(JS_STORE_SCALAR_CLASS_DEFN)
 JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(JS_LOAD_SCALAR_CLASS_DEFN)
 JS_FOR_EACH_REFERENCE_TYPE_REPR(JS_STORE_REFERENCE_CLASS_DEFN)
 JS_FOR_EACH_REFERENCE_TYPE_REPR(JS_LOAD_REFERENCE_CLASS_DEFN)
 
 inline bool
 IsTypedObjectClass(const Class *class_)
 {
-    return class_ == &TransparentTypedObject::class_ ||
+    return class_ == &OutlineTransparentTypedObject::class_ ||
+           class_ == &InlineTransparentTypedObject::class_ ||
            class_ == &OutlineOpaqueTypedObject::class_ ||
            class_ == &InlineOpaqueTypedObject::class_;
 }
 
 inline bool
 IsSimpleTypeDescrClass(const Class* clasp)
 {
     return clasp == &ScalarTypeDescr::class_ ||
@@ -1073,16 +1095,46 @@ IsSizedTypeDescrClass(const Class* clasp
 
 inline bool
 IsTypeDescrClass(const Class* clasp)
 {
     return IsSizedTypeDescrClass(clasp) ||
            clasp == &UnsizedArrayTypeDescr::class_;
 }
 
+inline bool
+TypedObject::opaque() const
+{
+    return is<OutlineOpaqueTypedObject>() || is<InlineOpaqueTypedObject>();
+}
+
+// Inline transparent typed objects do not initially have an array buffer, but
+// can have that buffer created lazily if it is accessed later. This table
+// manages references from such typed objects to their buffers.
+class LazyArrayBufferTable
+{
+  private:
+    // The map from transparent typed objects to their lazily created buffer.
+    // Keys in this map are InlineTransparentTypedObjects and values are
+    // ArrayBufferObjects, but we don't enforce this in the type system due to
+    // the extra marking code goop that requires.
+    typedef WeakMap<PreBarrieredObject, RelocatablePtrObject> Map;
+    Map map;
+
+  public:
+    LazyArrayBufferTable(JSContext *cx);
+    ~LazyArrayBufferTable();
+
+    ArrayBufferObject *maybeBuffer(InlineTransparentTypedObject *obj);
+    bool addBuffer(JSContext *cx, InlineTransparentTypedObject *obj, ArrayBufferObject *buffer);
+
+    void trace(JSTracer *trc);
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
+};
+
 } // namespace js
 
 JSObject *
 js_InitTypedObjectModuleObject(JSContext *cx, JS::HandleObject obj);
 
 template <>
 inline bool
 JSObject::is<js::SimpleTypeDescr>() const
@@ -1117,20 +1169,28 @@ JSObject::is<js::TypedObject>() const
 {
     return IsTypedObjectClass(getClass());
 }
 
 template <>
 inline bool
 JSObject::is<js::OutlineTypedObject>() const
 {
-    return getClass() == &js::TransparentTypedObject::class_ ||
+    return getClass() == &js::OutlineTransparentTypedObject::class_ ||
            getClass() == &js::OutlineOpaqueTypedObject::class_;
 }
 
+template <>
+inline bool
+JSObject::is<js::InlineTypedObject>() const
+{
+    return getClass() == &js::InlineTransparentTypedObject::class_ ||
+           getClass() == &js::InlineOpaqueTypedObject::class_;
+}
+
 inline void
 js::TypedProto::initTypeDescrSlot(TypeDescr &descr)
 {
     initReservedSlot(JS_TYPROTO_SLOT_DESCR, ObjectValue(descr));
 }
 
 inline js::type::Kind
 js::TypedProto::kind() const {
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -390,22 +390,23 @@ GetObjectAllocKindForCopy(const Nursery 
         return GetBackgroundAllocKind(TypedArrayObject::AllocKindForLazyBuffer(nbytes));
     }
 
     // Proxies have finalizers and are not nursery allocated.
     MOZ_ASSERT(!IsProxy(obj));
 
     // Inlined opaque typed objects are followed by their data, so make sure we
     // copy it all over to the new object.
-    if (obj->is<InlineOpaqueTypedObject>()) {
+    if (obj->is<InlineTypedObject>()) {
         // Figure out the size of this object, from the prototype's TypeDescr.
         // The objects we are traversing here are all tenured, so we don't need
         // to check forwarding pointers.
-        TypeDescr *descr = &obj->as<InlineOpaqueTypedObject>().typeDescr();
-        return InlineOpaqueTypedObject::allocKindForTypeDescriptor(descr);
+        TypeDescr *descr = &obj->as<InlineTypedObject>().typeDescr();
+        MOZ_ASSERT(!IsInsideNursery(descr));
+        return InlineTypedObject::allocKindForTypeDescriptor(descr);
     }
 
     // Outline typed objects have special requirements for their allocation kind.
     if (obj->is<OutlineTypedObject>()) {
         TypeDescr *descr = &obj->as<OutlineTypedObject>().typeDescr();
         return OutlineTypedObject::allocKindForTypeDescriptor(descr);
     }
 
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -531,16 +531,19 @@ js::gc::GCRuntime::markRuntime(JSTracer 
         if (traceOrMark == TraceRuntime) {
             if (c->watchpointMap)
                 c->watchpointMap->markAll(trc);
         }
 
         /* Mark debug scopes, if present */
         if (c->debugScopes)
             c->debugScopes->mark(trc);
+
+        if (c->lazyArrayBuffers)
+            c->lazyArrayBuffers->trace(trc);
     }
 
     MarkInterpreterActivations(&rt->mainThread, trc);
 
     jit::MarkJitActivations(&rt->mainThread, trc);
 
     if (!isHeapMinorCollecting()) {
         /*
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObject/inlinetransparent.js
@@ -0,0 +1,35 @@
+if (!this.hasOwnProperty("TypedObject"))
+    quit();
+
+var TO = TypedObject;
+
+var PointType = new TO.StructType({x: TO.int32, y: TO.int32});
+var LineType = new TO.StructType({from: PointType, to: PointType});
+
+function testBasic(how) {
+    var line = new LineType();
+    var from = line.from;
+    var to = line.to;
+    TO.storage(to).buffer.expando = "hello";
+    var dataview = new DataView(TO.storage(from).buffer);
+    line.from.x = 12;
+    line.to.x = 3;
+    if (how == 1)
+        minorgc();
+    else if (how == 2)
+	gc();
+    assertEq(from.x, 12);
+    assertEq(from.y, 0);
+    assertEq(to.x, 3);
+    assertEq(to.y, 0);
+    assertEq(TO.storage(to).byteOffset, 8);
+    dataview.setInt32(8, 10, true);
+    assertEq(to.x, 10);
+    assertEq(TO.storage(line).buffer.expando, "hello");
+}
+for (var i = 0; i < 5; i++)
+    testBasic(0);
+for (var i = 0; i < 5; i++)
+    testBasic(1);
+for (var i = 0; i < 5; i++)
+    testBasic(2);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -5003,38 +5003,16 @@ CodeGenerator::visitTypedArrayElements(L
 {
     Register obj = ToRegister(lir->object());
     Register out = ToRegister(lir->output());
     masm.loadPtr(Address(obj, TypedArrayLayout::dataOffset()), out);
     return true;
 }
 
 bool
-CodeGenerator::visitNeuterCheck(LNeuterCheck *lir)
-{
-    Register obj = ToRegister(lir->object());
-    Register temp = ToRegister(lir->temp());
-
-    Label inlineObject;
-    masm.loadObjClass(obj, temp);
-    masm.branchPtr(Assembler::Equal, temp, ImmPtr(&InlineOpaqueTypedObject::class_), &inlineObject);
-
-    masm.loadPtr(Address(obj, OutlineTypedObject::offsetOfOwner()), temp);
-    masm.unboxInt32(Address(temp, ArrayBufferObject::offsetOfFlagsSlot()), temp);
-
-    Imm32 flag(ArrayBufferObject::neuteredFlag());
-    if (!bailoutTest32(Assembler::NonZero, temp, flag, lir->snapshot()))
-        return false;
-
-    masm.bind(&inlineObject);
-
-    return true;
-}
-
-bool
 CodeGenerator::visitTypedObjectProto(LTypedObjectProto *lir)
 {
     Register obj = ToRegister(lir->object());
     MOZ_ASSERT(ToRegister(lir->output()) == ReturnReg);
 
     // Eventually we ought to inline this helper function for
     // efficiency, but it's mildly non-trivial since we must reach
     // into the type object and so on.
@@ -5060,22 +5038,23 @@ bool
 CodeGenerator::visitTypedObjectElements(LTypedObjectElements *lir)
 {
     Register obj = ToRegister(lir->object());
     Register out = ToRegister(lir->output());
 
     Label inlineObject, done;
     masm.loadObjClass(obj, out);
     masm.branchPtr(Assembler::Equal, out, ImmPtr(&InlineOpaqueTypedObject::class_), &inlineObject);
+    masm.branchPtr(Assembler::Equal, out, ImmPtr(&InlineTransparentTypedObject::class_), &inlineObject);
 
     masm.loadPtr(Address(obj, OutlineTypedObject::offsetOfData()), out);
     masm.jump(&done);
 
     masm.bind(&inlineObject);
-    masm.computeEffectiveAddress(Address(obj, InlineOpaqueTypedObject::offsetOfDataStart()), out);
+    masm.computeEffectiveAddress(Address(obj, InlineTypedObject::offsetOfDataStart()), out);
     masm.bind(&done);
 
     return true;
 }
 
 bool
 CodeGenerator::visitSetTypedObjectOffset(LSetTypedObjectOffset *lir)
 {
@@ -5085,22 +5064,23 @@ CodeGenerator::visitSetTypedObjectOffset
     Register temp1 = ToRegister(lir->temp1());
 
     // Compute the base pointer for the typed object's owner.
     masm.loadPtr(Address(object, OutlineTypedObject::offsetOfOwner()), temp0);
 
     Label inlineObject, done;
     masm.loadObjClass(temp0, temp1);
     masm.branchPtr(Assembler::Equal, temp1, ImmPtr(&InlineOpaqueTypedObject::class_), &inlineObject);
+    masm.branchPtr(Assembler::Equal, temp1, ImmPtr(&InlineTransparentTypedObject::class_), &inlineObject);
 
     masm.loadPrivate(Address(temp0, ArrayBufferObject::offsetOfDataSlot()), temp0);
     masm.jump(&done);
 
     masm.bind(&inlineObject);
-    masm.addPtr(ImmWord(InlineOpaqueTypedObject::offsetOfDataStart()), temp0);
+    masm.addPtr(ImmWord(InlineTypedObject::offsetOfDataStart()), temp0);
 
     masm.bind(&done);
 
     // Compute the new data pointer and set it in the object.
     masm.addPtr(offset, temp0);
     masm.storePtr(temp0, Address(object, OutlineTypedObject::offsetOfData()));
 
     return true;
--- a/js/src/jit/CodeGenerator.h
+++ b/js/src/jit/CodeGenerator.h
@@ -176,17 +176,16 @@ class CodeGenerator : public CodeGenerat
     bool visitSetArgumentsObjectArg(LSetArgumentsObjectArg *lir);
     bool visitReturnFromCtor(LReturnFromCtor *lir);
     bool visitComputeThis(LComputeThis *lir);
     bool visitLoadArrowThis(LLoadArrowThis *lir);
     bool visitArrayLength(LArrayLength *lir);
     bool visitSetArrayLength(LSetArrayLength *lir);
     bool visitTypedArrayLength(LTypedArrayLength *lir);
     bool visitTypedArrayElements(LTypedArrayElements *lir);
-    bool visitNeuterCheck(LNeuterCheck *lir);
     bool visitTypedObjectElements(LTypedObjectElements *lir);
     bool visitSetTypedObjectOffset(LSetTypedObjectOffset *lir);
     bool visitTypedObjectProto(LTypedObjectProto *ins);
     bool visitTypedObjectUnsizedLength(LTypedObjectUnsizedLength *ins);
     bool visitStringLength(LStringLength *lir);
     bool visitInitializedLength(LInitializedLength *lir);
     bool visitSetInitializedLength(LSetInitializedLength *lir);
     bool visitNotO(LNotO *ins);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -7242,43 +7242,42 @@ IonBuilder::getElemTryTypedObject(bool *
 static MIRType
 MIRTypeForTypedArrayRead(Scalar::Type arrayType, bool observedDouble);
 
 bool
 IonBuilder::checkTypedObjectIndexInBounds(int32_t elemSize,
                                           MDefinition *obj,
                                           MDefinition *index,
                                           TypedObjectPrediction objPrediction,
-                                          MDefinition **indexAsByteOffset,
-                                          bool *canBeNeutered)
+                                          MDefinition **indexAsByteOffset)
 {
     // Ensure index is an integer.
     MInstruction *idInt32 = MToInt32::New(alloc(), index);
     current->add(idInt32);
 
     // If we know the length statically from the type, just embed it.
     // Otherwise, load it from the appropriate reserved slot on the
     // typed object.  We know it's an int32, so we can convert from
     // Value to int32 using truncation.
     int32_t lenOfAll;
     MDefinition *length;
     if (objPrediction.hasKnownArrayLength(&lenOfAll)) {
         length = constantInt(lenOfAll);
 
-        // If we are not loading the length from the object itself,
-        // then we still need to check if the object was neutered.
-        *canBeNeutered = true;
+        // If we are not loading the length from the object itself, only
+        // optimize if the array buffer can't have been neutered.
+        types::TypeObjectKey *globalType = types::TypeObjectKey::get(&script()->global());
+        if (globalType->hasFlags(constraints(), types::OBJECT_FLAG_SIZED_OBJECT_NEUTERED))
+            return false;
     } else if (objPrediction.kind() == type::UnsizedArray) {
+        // Note: unsized arrays will have their length set to zero if they are
+        // neutered, so we don't need to make sure that no neutering has
+        // occurred which affects this object.
         length = MTypedObjectUnsizedLength::New(alloc(), obj);
         current->add(length->toInstruction());
-
-        // If we are loading the length from the object itself,
-        // then we do not need an extra neuter check, because the length
-        // will have been set to 0 when the object was neutered.
-        *canBeNeutered = false;
     } else {
         return false;
     }
 
     index = addBoundsCheck(idInt32, length);
 
     // Since we passed the bounds check, it is impossible for the
     // result of multiplication to overflow; so enable imul path.
@@ -7299,41 +7298,35 @@ IonBuilder::getElemTryScalarElemOfTypedO
                                               int32_t elemSize)
 {
     MOZ_ASSERT(objPrediction.ofArrayKind());
 
     // Must always be loading the same scalar type
     ScalarTypeDescr::Type elemType = elemPrediction.scalarType();
     MOZ_ASSERT(elemSize == ScalarTypeDescr::alignment(elemType));
 
-    bool canBeNeutered;
     MDefinition *indexAsByteOffset;
-    if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction,
-                                       &indexAsByteOffset, &canBeNeutered))
-    {
-        return true;
-    }
-
-    return pushScalarLoadFromTypedObject(emitted, obj, indexAsByteOffset, elemType, canBeNeutered);
+    if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset))
+        return true;
+
+    return pushScalarLoadFromTypedObject(emitted, obj, indexAsByteOffset, elemType);
 }
 
 bool
 IonBuilder::pushScalarLoadFromTypedObject(bool *emitted,
                                           MDefinition *obj,
                                           MDefinition *offset,
-                                          ScalarTypeDescr::Type elemType,
-                                          bool canBeNeutered)
+                                          ScalarTypeDescr::Type elemType)
 {
     int32_t size = ScalarTypeDescr::size(elemType);
     MOZ_ASSERT(size == ScalarTypeDescr::alignment(elemType));
 
     // Find location within the owner object.
     MDefinition *elements, *scaledOffset;
-    loadTypedObjectElements(obj, offset, size, canBeNeutered,
-                            &elements, &scaledOffset);
+    loadTypedObjectElements(obj, offset, size, &elements, &scaledOffset);
 
     // Load the element.
     MLoadTypedArrayElement *load = MLoadTypedArrayElement::New(alloc(), elements, scaledOffset, elemType);
     current->add(load);
     current->push(load);
 
     // If we are reading in-bounds elements, we can use knowledge about
     // the array type to determine the result type, even if the opcode has
@@ -7364,39 +7357,34 @@ IonBuilder::getElemTryComplexElemOfTyped
                                                TypedObjectPrediction elemPrediction,
                                                int32_t elemSize)
 {
     MOZ_ASSERT(objPrediction.ofArrayKind());
 
     MDefinition *type = loadTypedObjectType(obj);
     MDefinition *elemTypeObj = typeObjectForElementFromArrayStructType(type);
 
-    bool canBeNeutered;
     MDefinition *indexAsByteOffset;
-    if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction,
-                                       &indexAsByteOffset, &canBeNeutered))
-    {
-        return true;
-    }
+    if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset))
+        return true;
 
     return pushDerivedTypedObject(emitted, obj, indexAsByteOffset,
-                                  elemPrediction, elemTypeObj, canBeNeutered);
+                                  elemPrediction, elemTypeObj);
 }
 
 bool
 IonBuilder::pushDerivedTypedObject(bool *emitted,
                                    MDefinition *obj,
                                    MDefinition *offset,
                                    TypedObjectPrediction derivedPrediction,
-                                   MDefinition *derivedTypeObj,
-                                   bool canBeNeutered)
+                                   MDefinition *derivedTypeObj)
 {
     // Find location within the owner object.
     MDefinition *owner, *ownerOffset;
-    loadTypedObjectData(obj, offset, canBeNeutered, &owner, &ownerOffset);
+    loadTypedObjectData(obj, offset, &owner, &ownerOffset);
 
     // Create the derived typed object.
     MInstruction *derivedTypedObj = MNewDerivedTypedObject::New(alloc(),
                                                                 derivedPrediction,
                                                                 derivedTypeObj,
                                                                 owner,
                                                                 ownerOffset);
     current->add(derivedTypedObj);
@@ -8154,26 +8142,22 @@ IonBuilder::setElemTryScalarElemOfTypedO
                                               MDefinition *value,
                                               TypedObjectPrediction elemPrediction,
                                               int32_t elemSize)
 {
     // Must always be loading the same scalar type
     ScalarTypeDescr::Type elemType = elemPrediction.scalarType();
     MOZ_ASSERT(elemSize == ScalarTypeDescr::alignment(elemType));
 
-    bool canBeNeutered;
     MDefinition *indexAsByteOffset;
-    if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction,
-                                       &indexAsByteOffset, &canBeNeutered))
-    {
-        return true;
-    }
+    if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset))
+        return true;
 
     // Store the element
-    if (!storeScalarTypedObjectValue(obj, indexAsByteOffset, elemType, canBeNeutered, false, value))
+    if (!storeScalarTypedObjectValue(obj, indexAsByteOffset, elemType, false, value))
         return false;
 
     current->push(value);
 
     *emitted = true;
     return true;
 }
 
@@ -8538,17 +8522,17 @@ IonBuilder::jsop_setelem_typed_object(Sc
     MInstruction *int_index = MToInt32::New(alloc(), index);
     current->add(int_index);
 
     size_t elemSize = ScalarTypeDescr::alignment(arrayType);
     MMul *byteOffset = MMul::New(alloc(), int_index, constantInt(elemSize),
                                         MIRType_Int32, MMul::Integer);
     current->add(byteOffset);
 
-    if (!storeScalarTypedObjectValue(object, byteOffset, arrayType, false, racy, value))
+    if (!storeScalarTypedObjectValue(object, byteOffset, arrayType, racy, value))
         return false;
 
     return true;
 }
 
 bool
 IonBuilder::jsop_length()
 {
@@ -9320,37 +9304,46 @@ bool
 IonBuilder::getPropTryScalarPropOfTypedObject(bool *emitted, MDefinition *typedObj,
                                               int32_t fieldOffset,
                                               TypedObjectPrediction fieldPrediction,
                                               types::TemporaryTypeSet *resultTypes)
 {
     // Must always be loading the same scalar type
     Scalar::Type fieldType = fieldPrediction.scalarType();
 
+    // Don't optimize if the typed object might be neutered.
+    types::TypeObjectKey *globalType = types::TypeObjectKey::get(&script()->global());
+    if (globalType->hasFlags(constraints(), types::OBJECT_FLAG_SIZED_OBJECT_NEUTERED))
+        return true;
+
     // OK, perform the optimization.
-    return pushScalarLoadFromTypedObject(emitted, typedObj, constantInt(fieldOffset),
-                                         fieldType, true);
+    return pushScalarLoadFromTypedObject(emitted, typedObj, constantInt(fieldOffset), fieldType);
 }
 
 bool
 IonBuilder::getPropTryComplexPropOfTypedObject(bool *emitted,
                                                MDefinition *typedObj,
                                                int32_t fieldOffset,
                                                TypedObjectPrediction fieldPrediction,
                                                size_t fieldIndex,
                                                types::TemporaryTypeSet *resultTypes)
 {
+    // Don't optimize if the typed object might be neutered.
+    types::TypeObjectKey *globalType = types::TypeObjectKey::get(&script()->global());
+    if (globalType->hasFlags(constraints(), types::OBJECT_FLAG_SIZED_OBJECT_NEUTERED))
+        return true;
+
     // OK, perform the optimization
 
     // Identify the type object for the field.
     MDefinition *type = loadTypedObjectType(typedObj);
     MDefinition *fieldTypeObj = typeObjectForFieldFromStructType(type, fieldIndex);
 
     return pushDerivedTypedObject(emitted, typedObj, constantInt(fieldOffset),
-                                  fieldPrediction, fieldTypeObj, true);
+                                  fieldPrediction, fieldTypeObj);
 }
 
 bool
 IonBuilder::getPropTryDefiniteSlot(bool *emitted, MDefinition *obj, PropertyName *name,
                                    BarrierKind barrier, types::TemporaryTypeSet *types)
 {
     MOZ_ASSERT(*emitted == false);
     uint32_t slot = getDefiniteSlot(obj->resultTypeSet(), name);
@@ -9987,19 +9980,24 @@ IonBuilder::setPropTryScalarPropOfTypedO
                                               MDefinition *obj,
                                               int32_t fieldOffset,
                                               MDefinition *value,
                                               TypedObjectPrediction fieldPrediction)
 {
     // Must always be loading the same scalar type
     Scalar::Type fieldType = fieldPrediction.scalarType();
 
+    // Don't optimize if the typed object might be neutered.
+    types::TypeObjectKey *globalType = types::TypeObjectKey::get(&script()->global());
+    if (globalType->hasFlags(constraints(), types::OBJECT_FLAG_SIZED_OBJECT_NEUTERED))
+        return true;
+
     // OK! Perform the optimization.
 
-    if (!storeScalarTypedObjectValue(obj, constantInt(fieldOffset), fieldType, true, false, value))
+    if (!storeScalarTypedObjectValue(obj, constantInt(fieldOffset), fieldType, false, value))
         return false;
 
     current->push(value);
 
     *emitted = true;
     return true;
 }
 
@@ -10953,17 +10951,16 @@ IonBuilder::loadTypedObjectType(MDefinit
 // where the data can be found. Often, these returned values are the
 // same as the inputs, but in cases where intermediate derived type
 // objects have been created, the return values will remove
 // intermediate layers (often rendering those derived type objects
 // into dead code).
 void
 IonBuilder::loadTypedObjectData(MDefinition *typedObj,
                                 MDefinition *offset,
-                                bool canBeNeutered,
                                 MDefinition **owner,
                                 MDefinition **ownerOffset)
 {
     MOZ_ASSERT(typedObj->type() == MIRType_Object);
     MOZ_ASSERT(offset->type() == MIRType_Int32);
 
     // Shortcircuit derived type objects, meaning the intermediate
     // objects created to represent `a.b` in an expression like
@@ -10980,41 +10977,34 @@ IonBuilder::loadTypedObjectData(MDefinit
         MAdd *offsetAdd = MAdd::NewAsmJS(alloc(), ins->offset(), offset, MIRType_Int32);
         current->add(offsetAdd);
 
         *owner = ins->owner();
         *ownerOffset = offsetAdd;
         return;
     }
 
-    if (canBeNeutered) {
-        MNeuterCheck *chk = MNeuterCheck::New(alloc(), typedObj);
-        current->add(chk);
-        typedObj = chk;
-    }
-
     *owner = typedObj;
     *ownerOffset = offset;
 }
 
 // Takes as input a typed object, an offset into that typed object's
 // memory, and the type repr of the data found at that offset. Returns
 // the elements pointer and a scaled offset. The scaled offset is
 // expressed in units of `unit`; when working with typed array MIR,
 // this is typically the alignment.
 void
 IonBuilder::loadTypedObjectElements(MDefinition *typedObj,
                                     MDefinition *offset,
                                     int32_t unit,
-                                    bool canBeNeutered,
                                     MDefinition **ownerElements,
                                     MDefinition **ownerScaledOffset)
 {
     MDefinition *owner, *ownerOffset;
-    loadTypedObjectData(typedObj, offset, canBeNeutered, &owner, &ownerOffset);
+    loadTypedObjectData(typedObj, offset, &owner, &ownerOffset);
 
     // Load the element data.
     MTypedObjectElements *elements = MTypedObjectElements::New(alloc(), owner);
     current->add(elements);
 
     // Scale to a different unit for compat with typed array MIRs.
     if (unit != 1) {
         MDiv *scaledOffset = MDiv::NewAsmJS(alloc(), ownerOffset, constantInt(unit), MIRType_Int32,
@@ -11091,25 +11081,23 @@ IonBuilder::typeObjectForFieldFromStruct
 
     return unboxFieldType;
 }
 
 bool
 IonBuilder::storeScalarTypedObjectValue(MDefinition *typedObj,
                                         MDefinition *byteOffset,
                                         ScalarTypeDescr::Type type,
-                                        bool canBeNeutered,
                                         bool racy,
                                         MDefinition *value)
 {
     // Find location within the owner object.
     MDefinition *elements, *scaledOffset;
     size_t alignment = ScalarTypeDescr::alignment(type);
-    loadTypedObjectElements(typedObj, byteOffset, alignment, canBeNeutered,
-                            &elements, &scaledOffset);
+    loadTypedObjectElements(typedObj, byteOffset, alignment, &elements, &scaledOffset);
 
     // Clamp value to [0, 255] when type is Uint8Clamped
     MDefinition *toWrite = value;
     if (type == Scalar::Uint8Clamped) {
         toWrite = MClampToUint8::New(alloc(), value);
         current->add(toWrite->toInstruction());
     }
 
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -466,51 +466,45 @@ class IonBuilder
     bool typedObjectHasField(MDefinition *typedObj,
                              PropertyName *name,
                              size_t *fieldOffset,
                              TypedObjectPrediction *fieldTypeReprs,
                              size_t *fieldIndex);
     MDefinition *loadTypedObjectType(MDefinition *value);
     void loadTypedObjectData(MDefinition *typedObj,
                              MDefinition *offset,
-                             bool canBeNeutered,
                              MDefinition **owner,
                              MDefinition **ownerOffset);
     void loadTypedObjectElements(MDefinition *typedObj,
                                  MDefinition *offset,
                                  int32_t unit,
-                                 bool canBeNeutered,
                                  MDefinition **ownerElements,
                                  MDefinition **ownerScaledOffset);
     MDefinition *typeObjectForElementFromArrayStructType(MDefinition *typedObj);
     MDefinition *typeObjectForFieldFromStructType(MDefinition *type,
                                                   size_t fieldIndex);
     bool storeScalarTypedObjectValue(MDefinition *typedObj,
                                      MDefinition *offset,
                                      ScalarTypeDescr::Type type,
-                                     bool canBeNeutered,
                                      bool racy,
                                      MDefinition *value);
     bool checkTypedObjectIndexInBounds(int32_t elemSize,
                                        MDefinition *obj,
                                        MDefinition *index,
                                        TypedObjectPrediction objTypeDescrs,
-                                       MDefinition **indexAsByteOffset,
-                                       bool *canBeNeutered);
+                                       MDefinition **indexAsByteOffset);
     bool pushDerivedTypedObject(bool *emitted,
                                 MDefinition *obj,
                                 MDefinition *offset,
                                 TypedObjectPrediction derivedTypeDescrs,
-                                MDefinition *derivedTypeObj,
-                                bool canBeNeutered);
+                                MDefinition *derivedTypeObj);
     bool pushScalarLoadFromTypedObject(bool *emitted,
                                        MDefinition *obj,
                                        MDefinition *offset,
-                                       ScalarTypeDescr::Type type,
-                                       bool canBeNeutered);
+                                       ScalarTypeDescr::Type type);
     MDefinition *neuterCheck(MDefinition *obj);
 
     // jsop_setelem() helpers.
     bool setElemTryTypedArray(bool *emitted, MDefinition *object,
                          MDefinition *index, MDefinition *value);
     bool setElemTryTypedObject(bool *emitted, MDefinition *obj,
                                MDefinition *index, MDefinition *value);
     bool setElemTryTypedStatic(bool *emitted, MDefinition *object,
@@ -763,17 +757,18 @@ class IonBuilder
     InliningStatus inlineIsObject(CallInfo &callInfo);
     InliningStatus inlineHaveSameClass(CallInfo &callInfo);
     InliningStatus inlineToObject(CallInfo &callInfo);
     InliningStatus inlineToInteger(CallInfo &callInfo);
     InliningStatus inlineToString(CallInfo &callInfo);
     InliningStatus inlineDump(CallInfo &callInfo);
     InliningStatus inlineHasClass(CallInfo &callInfo, const Class *clasp,
                                   const Class *clasp2 = nullptr,
-                                  const Class *clasp3 = nullptr);
+                                  const Class *clasp3 = nullptr,
+                                  const Class *clasp4 = nullptr);
     InliningStatus inlineIsConstructing(CallInfo &callInfo);
 
     // Testing functions.
     InliningStatus inlineForceSequentialOrInParallelSection(CallInfo &callInfo);
     InliningStatus inlineBailout(CallInfo &callInfo);
     InliningStatus inlineAssertFloat32(CallInfo &callInfo);
 
     // Bind function.
--- a/js/src/jit/LIR-Common.h
+++ b/js/src/jit/LIR-Common.h
@@ -4253,34 +4253,16 @@ class LSetTypedObjectOffset : public LIn
     const LDefinition *temp0() {
         return getTemp(0);
     }
     const LDefinition *temp1() {
         return getTemp(1);
     }
 };
 
-// Check whether a typed object has a neutered owner buffer.
-class LNeuterCheck : public LInstructionHelper<0, 1, 1>
-{
-  public:
-    LIR_HEADER(NeuterCheck)
-
-    LNeuterCheck(const LAllocation &object, const LDefinition &temp) {
-        setOperand(0, object);
-        setTemp(0, temp);
-    }
-    const LAllocation *object() {
-        return getOperand(0);
-    }
-    const LDefinition *temp() {
-        return getTemp(0);
-    }
-};
-
 // Bailout if index >= length.
 class LBoundsCheck : public LInstructionHelper<0, 2, 0>
 {
   public:
     LIR_HEADER(BoundsCheck)
 
     LBoundsCheck(const LAllocation &index, const LAllocation &length) {
         setOperand(0, index);
--- a/js/src/jit/LOpcodes.h
+++ b/js/src/jit/LOpcodes.h
@@ -206,17 +206,16 @@
     _(GuardThreadExclusive)         \
     _(TypeBarrierV)                 \
     _(TypeBarrierO)                 \
     _(MonitorTypes)                 \
     _(PostWriteBarrierO)            \
     _(PostWriteBarrierV)            \
     _(InitializedLength)            \
     _(SetInitializedLength)         \
-    _(NeuterCheck)                  \
     _(BoundsCheck)                  \
     _(BoundsCheckRange)             \
     _(BoundsCheckLower)             \
     _(LoadElementV)                 \
     _(LoadElementT)                 \
     _(LoadElementHole)              \
     _(StoreElementV)                \
     _(StoreElementT)                \
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2586,26 +2586,16 @@ LIRGenerator::visitNot(MNot *ins)
       }
 
       default:
         MOZ_CRASH("Unexpected MIRType.");
     }
 }
 
 bool
-LIRGenerator::visitNeuterCheck(MNeuterCheck *ins)
-{
-    LNeuterCheck *chk = new(alloc()) LNeuterCheck(useRegister(ins->object()),
-                                                  temp());
-    if (!assignSnapshot(chk, Bailout_Neutered))
-        return false;
-    return redefine(ins, ins->input()) && add(chk, ins);
-}
-
-bool
 LIRGenerator::visitBoundsCheck(MBoundsCheck *ins)
 {
     LInstruction *check;
     if (ins->minimum() || ins->maximum()) {
         check = new(alloc()) LBoundsCheckRange(useRegisterOrConstant(ins->index()),
                                                useAny(ins->length()),
                                                temp());
     } else {
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -181,17 +181,16 @@ class LIRGenerator : public LIRGenerator
     bool visitFilterTypeSet(MFilterTypeSet *ins);
     bool visitTypeBarrier(MTypeBarrier *ins);
     bool visitMonitorTypes(MMonitorTypes *ins);
     bool visitPostWriteBarrier(MPostWriteBarrier *ins);
     bool visitArrayLength(MArrayLength *ins);
     bool visitSetArrayLength(MSetArrayLength *ins);
     bool visitTypedArrayLength(MTypedArrayLength *ins);
     bool visitTypedArrayElements(MTypedArrayElements *ins);
-    bool visitNeuterCheck(MNeuterCheck *lir);
     bool visitTypedObjectElements(MTypedObjectElements *ins);
     bool visitSetTypedObjectOffset(MSetTypedObjectOffset *ins);
     bool visitTypedObjectProto(MTypedObjectProto *ins);
     bool visitTypedObjectUnsizedLength(MTypedObjectUnsizedLength *ins);
     bool visitInitializedLength(MInitializedLength *ins);
     bool visitSetInitializedLength(MSetInitializedLength *ins);
     bool visitNot(MNot *ins);
     bool visitBoundsCheck(MBoundsCheck *ins);
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -196,21 +196,24 @@ IonBuilder::inlineNativeCall(CallInfo &c
     if (native == intrinsic_ToString)
         return inlineToString(callInfo);
     if (native == intrinsic_IsConstructing)
         return inlineIsConstructing(callInfo);
 
     // TypedObject intrinsics.
     if (native == intrinsic_ObjectIsTypedObject)
         return inlineHasClass(callInfo,
-                              &TransparentTypedObject::class_,
+                              &OutlineTransparentTypedObject::class_,
                               &OutlineOpaqueTypedObject::class_,
+                              &InlineTransparentTypedObject::class_,
                               &InlineOpaqueTypedObject::class_);
     if (native == intrinsic_ObjectIsTransparentTypedObject)
-        return inlineHasClass(callInfo, &TransparentTypedObject::class_);
+        return inlineHasClass(callInfo,
+                              &OutlineTransparentTypedObject::class_,
+                              &InlineTransparentTypedObject::class_);
     if (native == intrinsic_ObjectIsOpaqueTypedObject)
         return inlineHasClass(callInfo,
                               &OutlineOpaqueTypedObject::class_,
                               &InlineOpaqueTypedObject::class_);
     if (native == intrinsic_ObjectIsTypeDescr)
         return inlineObjectIsTypeDescr(callInfo);
     if (native == intrinsic_TypeDescrIsSimpleType)
         return inlineHasClass(callInfo,
@@ -1822,38 +1825,43 @@ IonBuilder::inlineNewDenseArrayForParall
                                                           &templateObject->as<ArrayObject>());
     current->add(newObject);
     current->push(newObject);
 
     return InliningStatus_Inlined;
 }
 
 IonBuilder::InliningStatus
-IonBuilder::inlineHasClass(CallInfo &callInfo, const Class *clasp1, const Class *clasp2, const Class *clasp3)
+IonBuilder::inlineHasClass(CallInfo &callInfo,
+                           const Class *clasp1, const Class *clasp2,
+                           const Class *clasp3, const Class *clasp4)
 {
     if (callInfo.constructing() || callInfo.argc() != 1)
         return InliningStatus_NotInlined;
 
     if (callInfo.getArg(0)->type() != MIRType_Object)
         return InliningStatus_NotInlined;
     if (getInlineReturnType() != MIRType_Boolean)
         return InliningStatus_NotInlined;
 
     types::TemporaryTypeSet *types = callInfo.getArg(0)->resultTypeSet();
     const Class *knownClass = types ? types->getKnownClass() : nullptr;
     if (knownClass) {
-        pushConstant(BooleanValue(knownClass == clasp1 || knownClass == clasp2 || knownClass == clasp3));
+        pushConstant(BooleanValue(knownClass == clasp1 ||
+                                  knownClass == clasp2 ||
+                                  knownClass == clasp3 ||
+                                  knownClass == clasp4));
     } else {
         MHasClass *hasClass1 = MHasClass::New(alloc(), callInfo.getArg(0), clasp1);
         current->add(hasClass1);
 
-        if (!clasp2 && !clasp3) {
+        if (!clasp2 && !clasp3 && !clasp4) {
             current->push(hasClass1);
         } else {
-            const Class *remaining[] = { clasp2, clasp3 };
+            const Class *remaining[] = { clasp2, clasp3, clasp4 };
             MDefinition *last = hasClass1;
             for (size_t i = 0; i < ArrayLength(remaining); i++) {
                 MHasClass *hasClass = MHasClass::New(alloc(), callInfo.getArg(0), remaining[i]);
                 current->add(hasClass);
                 MBitOr *either = MBitOr::New(alloc(), last, hasClass);
                 either->infer(inspector, pc);
                 current->add(either);
                 last = either;
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -7346,52 +7346,16 @@ class MTypedArrayElements
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 
     ALLOW_CLONE(MTypedArrayElements)
 };
 
-// Checks whether a typed object is neutered.
-class MNeuterCheck
-  : public MUnaryInstruction,
-    public SingleObjectPolicy::Data
-{
-  private:
-    explicit MNeuterCheck(MDefinition *object)
-      : MUnaryInstruction(object)
-    {
-        MOZ_ASSERT(object->type() == MIRType_Object);
-        setResultType(MIRType_Object);
-        setResultTypeSet(object->resultTypeSet());
-        setGuard();
-        setMovable();
-    }
-
-  public:
-    INSTRUCTION_HEADER(NeuterCheck)
-
-    static MNeuterCheck *New(TempAllocator &alloc, MDefinition *object) {
-        return new(alloc) MNeuterCheck(object);
-    }
-
-    MDefinition *object() const {
-        return getOperand(0);
-    }
-
-    bool congruentTo(const MDefinition *ins) const {
-        return congruentIfOperandsEqual(ins);
-    }
-
-    AliasSet getAliasSet() const {
-        return AliasSet::Load(AliasSet::ObjectFields);
-    }
-};
-
 // Load a binary data object's "elements", which is just its opaque
 // binary data space. Eventually this should probably be
 // unified with `MTypedArrayElements`.
 class MTypedObjectElements
   : public MUnaryInstruction,
     public SingleObjectPolicy::Data
 {
   private:
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -161,17 +161,16 @@ namespace jit {
     _(TypedArrayElements)                                                   \
     _(TypedObjectProto)                                                     \
     _(TypedObjectUnsizedLength)                                             \
     _(TypedObjectElements)                                                  \
     _(SetTypedObjectOffset)                                                 \
     _(InitializedLength)                                                    \
     _(SetInitializedLength)                                                 \
     _(Not)                                                                  \
-    _(NeuterCheck)                                                          \
     _(BoundsCheck)                                                          \
     _(BoundsCheckLower)                                                     \
     _(InArray)                                                              \
     _(LoadElement)                                                          \
     _(LoadElementHole)                                                      \
     _(StoreElement)                                                         \
     _(StoreElementHole)                                                     \
     _(ArrayPopShift)                                                        \
--- a/js/src/jit/ParallelSafetyAnalysis.cpp
+++ b/js/src/jit/ParallelSafetyAnalysis.cpp
@@ -247,17 +247,16 @@ class ParallelSafetyVisitor : public MDe
     SAFE_OP(TypedArrayElements)
     SAFE_OP(TypedObjectProto)
     SAFE_OP(TypedObjectUnsizedLength)
     SAFE_OP(TypedObjectElements)
     SAFE_OP(SetTypedObjectOffset)
     SAFE_OP(InitializedLength)
     WRITE_GUARDED_OP(SetInitializedLength, elements)
     SAFE_OP(Not)
-    SAFE_OP(NeuterCheck)
     SAFE_OP(BoundsCheck)
     SAFE_OP(BoundsCheckLower)
     SAFE_OP(LoadElement)
     SAFE_OP(LoadElementHole)
     MAYBE_WRITE_GUARDED_OP(StoreElement, elements)
     WRITE_GUARDED_OP(StoreElementHole, elements)
     UNSAFE_OP(ArrayPopShift)
     UNSAFE_OP(ArrayPush)
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -52,16 +52,17 @@ JSCompartment::JSCompartment(Zone *zone,
     enterCompartmentDepth(0),
     data(nullptr),
     objectMetadataCallback(nullptr),
     lastAnimationTime(0),
     regExps(runtime_),
     globalWriteBarriered(false),
     propertyTree(thisForCtor()),
     selfHostingScriptSource(nullptr),
+    lazyArrayBuffers(nullptr),
     gcIncomingGrayPointers(nullptr),
     gcWeakMapList(nullptr),
     gcPreserveJitCode(options.preserveJitCode()),
     debugModeBits(0),
     rngState(0),
     watchpointMap(nullptr),
     scriptCountsMap(nullptr),
     debugScriptMap(nullptr),
@@ -78,16 +79,17 @@ JSCompartment::JSCompartment(Zone *zone,
 
 JSCompartment::~JSCompartment()
 {
     js_delete(jitCompartment_);
     js_delete(watchpointMap);
     js_delete(scriptCountsMap);
     js_delete(debugScriptMap);
     js_delete(debugScopes);
+    js_delete(lazyArrayBuffers);
     js_free(enumerators);
 
     runtime_->numCompartments--;
 }
 
 bool
 JSCompartment::init(JSContext *cx)
 {
@@ -864,28 +866,31 @@ JSCompartment::clearBreakpointsIn(FreeOp
 void
 JSCompartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                       size_t *tiAllocationSiteTables,
                                       size_t *tiArrayTypeTables,
                                       size_t *tiObjectTypeTables,
                                       size_t *compartmentObject,
                                       size_t *compartmentTables,
                                       size_t *innerViewsArg,
+                                      size_t *lazyArrayBuffersArg,
                                       size_t *crossCompartmentWrappersArg,
                                       size_t *regexpCompartment,
                                       size_t *savedStacksSet)
 {
     *compartmentObject += mallocSizeOf(this);
     types.addSizeOfExcludingThis(mallocSizeOf, tiAllocationSiteTables,
                                  tiArrayTypeTables, tiObjectTypeTables);
     *compartmentTables += baseShapes.sizeOfExcludingThis(mallocSizeOf)
                         + initialShapes.sizeOfExcludingThis(mallocSizeOf)
                         + newTypeObjects.sizeOfExcludingThis(mallocSizeOf)
                         + lazyTypeObjects.sizeOfExcludingThis(mallocSizeOf);
     *innerViewsArg += innerViews.sizeOfExcludingThis(mallocSizeOf);
+    if (lazyArrayBuffers)
+        *lazyArrayBuffersArg += lazyArrayBuffers->sizeOfIncludingThis(mallocSizeOf);
     *crossCompartmentWrappersArg += crossCompartmentWrappers.sizeOfExcludingThis(mallocSizeOf);
     *regexpCompartment += regExps.sizeOfExcludingThis(mallocSizeOf);
     *savedStacksSet += savedStacks_.sizeOfExcludingThis(mallocSizeOf);
 }
 
 void
 JSCompartment::adoptWorkerAllocator(Allocator *workerAllocator)
 {
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -127,16 +127,17 @@ typedef HashMap<CrossCompartmentKey, Rea
 
 namespace JS {
 struct TypeInferenceSizes;
 }
 
 namespace js {
 class AutoDebugModeInvalidation;
 class DebugScopes;
+class LazyArrayBufferTable;
 class WeakMapBase;
 }
 
 struct JSCompartment
 {
     JS::CompartmentOptions       options_;
 
   private:
@@ -244,16 +245,17 @@ struct JSCompartment
   public:
     void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                 size_t *tiAllocationSiteTables,
                                 size_t *tiArrayTypeTables,
                                 size_t *tiObjectTypeTables,
                                 size_t *compartmentObject,
                                 size_t *compartmentTables,
                                 size_t *innerViews,
+                                size_t *lazyArrayBuffers,
                                 size_t *crossCompartmentWrappers,
                                 size_t *regexpCompartment,
                                 size_t *savedStacksSet);
 
     /*
      * Shared scope property tree, and arena-pool for allocating its nodes.
      */
     js::PropertyTree             propertyTree;
@@ -287,20 +289,22 @@ struct JSCompartment
     void sweepCallsiteClones();
 
     /*
      * Lazily initialized script source object to use for scripts cloned
      * from the self-hosting global.
      */
     js::ReadBarrieredScriptSourceObject selfHostingScriptSource;
 
-    // Information mapping objects which own their own storage to other objects
-    // sharing that storage.
+    // Map from array buffers to views sharing that storage.
     js::InnerViewTable innerViews;
 
+    // Map from typed objects to array buffers lazily created for them.
+    js::LazyArrayBufferTable *lazyArrayBuffers;
+
     /* During GC, stores the index of this compartment in rt->compartments. */
     unsigned                     gcIndex;
 
     /*
      * During GC, stores the head of a list of incoming pointers from gray cells.
      *
      * The objects in the list are either cross-compartment wrappers, or
      * debugger wrapper objects.  The list link is either in the second extra
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -478,42 +478,49 @@ enum MOZ_ENUM_TYPE(uint32_t) {
 
     /*
      * For the function on a run-once script, whether the function has actually
      * run multiple times.
      */
     OBJECT_FLAG_RUNONCE_INVALIDATED   = 0x00200000,
 
     /*
+     * For a global object, whether any array buffers in this compartment with
+     * sized typed object views have been neutered. Sized typed objects have
+     * different neutering checks from other array buffer views.
+     */
+    OBJECT_FLAG_SIZED_OBJECT_NEUTERED = 0x00400000,
+
+    /*
      * Whether objects with this type should be allocated directly in the
      * tenured heap.
      */
-    OBJECT_FLAG_PRE_TENURE            = 0x00400000,
+    OBJECT_FLAG_PRE_TENURE            = 0x00800000,
 
     /* Whether objects with this type might have copy on write elements. */
-    OBJECT_FLAG_COPY_ON_WRITE         = 0x00800000,
+    OBJECT_FLAG_COPY_ON_WRITE         = 0x01000000,
 
     /*
      * Whether all properties of this object are considered unknown.
      * If set, all other flags in DYNAMIC_MASK will also be set.
      */
-    OBJECT_FLAG_UNKNOWN_PROPERTIES    = 0x01000000,
+    OBJECT_FLAG_UNKNOWN_PROPERTIES    = 0x02000000,
 
     /* Flags which indicate dynamic properties of represented objects. */
-    OBJECT_FLAG_DYNAMIC_MASK          = 0x01ff0000,
+    OBJECT_FLAG_DYNAMIC_MASK          = 0x03ff0000,
 
     /* Mask for objects created with unknown properties. */
     OBJECT_FLAG_UNKNOWN_MASK =
         OBJECT_FLAG_DYNAMIC_MASK
       | OBJECT_FLAG_SETS_MARKED_UNKNOWN,
 
     // Mask/shift for this type object's generation. If out of sync with the
     // TypeZone's generation, this TypeObject hasn't been swept yet.
-    OBJECT_FLAG_GENERATION_MASK       = 0x02000000,
-    OBJECT_FLAG_GENERATION_SHIFT      = 25,
+    OBJECT_FLAG_GENERATION_MASK       = 0x04000000,
+    OBJECT_FLAG_GENERATION_SHIFT      = 26,
 };
 typedef uint32_t TypeObjectFlags;
 
 class StackTypeSet;
 class HeapTypeSet;
 class TemporaryTypeSet;
 
 /*
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -278,38 +278,41 @@ JSObject::isQualifiedVarObj()
 inline bool
 JSObject::isUnqualifiedVarObj()
 {
     if (is<js::DebugScopeObject>())
         return as<js::DebugScopeObject>().scope().isUnqualifiedVarObj();
     return lastProperty()->hasObjectFlag(js::BaseShape::UNQUALIFIED_VAROBJ);
 }
 
+namespace js {
+
 inline bool
-ClassCanHaveFixedData(const js::Class *clasp)
+ClassCanHaveFixedData(const Class *clasp)
 {
     // Normally, the number of fixed slots given an object is the maximum
     // permitted for its size class. For array buffers and non-shared typed
     // arrays we only use enough to cover the class reserved slots, so that
     // the remaining space in the object's allocation is available for the
     // buffer's data.
     return !clasp->isNative()
         || clasp == &js::ArrayBufferObject::class_
-        || clasp == &js::InlineOpaqueTypedObject::class_
         || js::IsTypedArrayClass(clasp);
 }
 
+} // namespace js
+
 /* static */ inline JSObject *
 JSObject::create(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
                  js::HandleShape shape, js::HandleTypeObject type)
 {
     MOZ_ASSERT(shape && type);
     MOZ_ASSERT(type->clasp() == shape->getObjectClass());
     MOZ_ASSERT(type->clasp() != &js::ArrayObject::class_);
-    MOZ_ASSERT_IF(!ClassCanHaveFixedData(type->clasp()),
+    MOZ_ASSERT_IF(!js::ClassCanHaveFixedData(type->clasp()),
                   js::gc::GetGCKindSlots(kind, type->clasp()) == shape->numFixedSlots());
     MOZ_ASSERT_IF(type->clasp()->flags & JSCLASS_BACKGROUND_FINALIZE, IsBackgroundFinalized(kind));
     MOZ_ASSERT_IF(type->clasp()->finalize, heap == js::gc::TenuredHeap);
 
     // Non-native classes cannot have reserved slots or private data, and the
     // objects can't have any fixed slots, for compatibility with
     // GetReservedOrProxyPrivateSlot.
     MOZ_ASSERT_IF(!type->clasp()->isNative(), JSCLASS_RESERVED_SLOTS(type->clasp()) == 0);
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -116,17 +116,17 @@ const Class ArrayBufferObject::class_ = 
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     ArrayBufferObject::finalize,
     nullptr,        /* call        */
     nullptr,        /* hasInstance */
     nullptr,        /* construct   */
-    nullptr,        /* trace       */
+    ArrayBufferObject::trace,
     JS_NULL_CLASS_SPEC,
     {
         nullptr,    /* outerObject */
         nullptr,    /* innerObject */
         nullptr,    /* iteratorObject */
         false,      /* isWrappedNative */
         nullptr,    /* weakmapKeyDelegateOp */
         ArrayBufferObject::objectMoved
@@ -509,27 +509,52 @@ ArrayBufferObject::neuterView(JSContext 
 
 /* static */ bool
 ArrayBufferObject::neuter(JSContext *cx, Handle<ArrayBufferObject*> buffer,
                           BufferContents newContents)
 {
     if (buffer->isAsmJS() && !OnDetachAsmJSArrayBuffer(cx, buffer))
         return false;
 
+    // When neutering buffers where we don't know all views, the new data must
+    // match the old data. All missing views are sized typed objects, which do
+    // not have a length property to mutate when neutering.
+    MOZ_ASSERT_IF(buffer->forInlineTypedObject(),
+                  newContents.data() == buffer->dataPointer());
+
+    // When neutering a buffer with sized typed object views, any jitcode which
+    // accesses such views needs to be deoptimized so that neuter checks are
+    // performed. This is done by setting a compartment wide flag indicating
+    // that buffers with sized object views have been neutered.
+    if (buffer->hasSizedObjectViews()) {
+        // Make sure the global object's type has been instantiated, so the
+        // flag change will be observed.
+        if (!cx->global()->getType(cx))
+            CrashAtUnhandlableOOM("ArrayBufferObject::neuter");
+        types::MarkTypeObjectFlags(cx, cx->global(), types::OBJECT_FLAG_SIZED_OBJECT_NEUTERED);
+    }
+
     // Neuter all views on the buffer, clear out the list of views and the
     // buffer's data.
 
     if (InnerViewTable::ViewVector *views = cx->compartment()->innerViews.maybeViewsUnbarriered(buffer)) {
         for (size_t i = 0; i < views->length(); i++)
             buffer->neuterView(cx, (*views)[i], newContents);
         cx->compartment()->innerViews.removeViews(buffer);
     }
-    if (buffer->firstView())
-        buffer->neuterView(cx, buffer->firstView(), newContents);
-    buffer->setFirstView(nullptr);
+    if (buffer->firstView()) {
+        if (buffer->forInlineTypedObject()) {
+            // The buffer points to inline data in its first view, so to keep
+            // this pointer alive we don't clear out the first view.
+            MOZ_ASSERT(buffer->firstView()->is<InlineTransparentTypedObject>());
+        } else {
+            buffer->neuterView(cx, buffer->firstView(), newContents);
+            buffer->setFirstView(nullptr);
+        }
+    }
 
     if (newContents.data() != buffer->dataPointer())
         buffer->setNewOwnedData(cx->runtime()->defaultFreeOp(), newContents);
 
     buffer->setByteLength(0);
     buffer->setIsNeutered();
     return true;
 }
@@ -562,32 +587,39 @@ ArrayBufferObject::changeViewContents(JS
 
     // Notify compiled jit code that the base pointer has moved.
     MarkObjectStateChange(cx, view);
 }
 
 void
 ArrayBufferObject::changeContents(JSContext *cx, BufferContents newContents)
 {
+    MOZ_ASSERT(!forInlineTypedObject());
+
     // Change buffer contents.
     uint8_t* oldDataPointer = dataPointer();
     setNewOwnedData(cx->runtime()->defaultFreeOp(), newContents);
 
     // Update all views.
     if (InnerViewTable::ViewVector *views = cx->compartment()->innerViews.maybeViewsUnbarriered(this)) {
         for (size_t i = 0; i < views->length(); i++)
             changeViewContents(cx, (*views)[i], oldDataPointer, newContents);
     }
     if (firstView())
         changeViewContents(cx, firstView(), oldDataPointer, newContents);
 }
 
 /* static */ bool
 ArrayBufferObject::prepareForAsmJSNoSignals(JSContext *cx, Handle<ArrayBufferObject*> buffer)
 {
+    if (buffer->forInlineTypedObject()) {
+        JS_ReportError(cx, "ArrayBuffer can't be used by asm.js");
+        return false;
+    }
+
     if (!buffer->ownsData()) {
         BufferContents contents = AllocateArrayBufferContents(cx, buffer->byteLength());
         if (!contents)
             return false;
         memcpy(contents.data(), buffer->dataPointer(), buffer->byteLength());
         buffer->changeContents(cx, contents);
     }
 
@@ -608,16 +640,21 @@ ArrayBufferObject::prepareForAsmJS(JSCon
         return true;
 
     // This can't happen except via the shell toggling signals.enabled.
     if (buffer->isAsmJSMalloced()) {
         JS_ReportError(cx, "can't access same buffer with and without signals enabled");
         return false;
     }
 
+    if (buffer->forInlineTypedObject()) {
+        JS_ReportError(cx, "ArrayBuffer can't be used by asm.js");
+        return false;
+    }
+
     // Get the entire reserved region (with all pages inaccessible).
     void *data;
 # ifdef XP_WIN
     data = VirtualAlloc(nullptr, AsmJSMappedSize, MEM_RESERVE, PAGE_NOACCESS);
     if (!data)
         return false;
 # else
     data = MozTaggedAnonymousMmap(nullptr, AsmJSMappedSize, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0, "asm-js-reserved");
@@ -735,41 +772,45 @@ ArrayBufferObject::flags() const
 void
 ArrayBufferObject::setFlags(uint32_t flags)
 {
     setSlot(FLAGS_SLOT, Int32Value(flags));
 }
 
 ArrayBufferObject *
 ArrayBufferObject::create(JSContext *cx, uint32_t nbytes, BufferContents contents,
+                          OwnsState ownsState /* = OwnsData */,
                           NewObjectKind newKind /* = GenericObject */)
 {
     MOZ_ASSERT_IF(contents.kind() == MAPPED, contents);
 
     // If we need to allocate data, try to use a larger object size class so
     // that the array buffer's data can be allocated inline with the object.
     // The extra space will be left unused by the object's fixed slots and
     // available for the buffer's data, see NewObject().
     size_t reservedSlots = JSCLASS_RESERVED_SLOTS(&class_);
 
     size_t nslots = reservedSlots;
     bool allocated = false;
     if (contents) {
-        // The ABO is taking ownership, so account the bytes against the zone.
-        size_t nAllocated = nbytes;
-        if (contents.kind() == MAPPED)
-            nAllocated = JS_ROUNDUP(nbytes, js::gc::SystemPageSize());
-        cx->zone()->updateMallocCounter(nAllocated);
+        if (ownsState == OwnsData) {
+            // The ABO is taking ownership, so account the bytes against the zone.
+            size_t nAllocated = nbytes;
+            if (contents.kind() == MAPPED)
+                nAllocated = JS_ROUNDUP(nbytes, js::gc::SystemPageSize());
+            cx->zone()->updateMallocCounter(nAllocated);
+        }
     } else {
+        MOZ_ASSERT(ownsState == OwnsData);
         size_t usableSlots = NativeObject::MAX_FIXED_SLOTS - reservedSlots;
         if (nbytes <= usableSlots * sizeof(Value)) {
             int newSlots = (nbytes - 1) / sizeof(Value) + 1;
             MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value)));
             nslots = reservedSlots + newSlots;
-            contents = BufferContents::createUnowned(nullptr);
+            contents = BufferContents::createPlain(nullptr);
         } else {
             contents = AllocateArrayBufferContents(cx, nbytes);
             if (!contents)
                 return nullptr;
             allocated = true;
         }
     }
 
@@ -784,29 +825,29 @@ ArrayBufferObject::create(JSContext *cx,
     }
 
     MOZ_ASSERT(obj->getClass() == &class_);
     MOZ_ASSERT(!gc::IsInsideNursery(obj));
 
     if (!contents) {
         void *data = obj->inlineDataPointer();
         memset(data, 0, nbytes);
-        obj->initialize(nbytes, BufferContents::createUnowned(data), DoesntOwnData);
+        obj->initialize(nbytes, BufferContents::createPlain(data), DoesntOwnData);
     } else {
-        obj->initialize(nbytes, contents, OwnsData);
+        obj->initialize(nbytes, contents, ownsState);
     }
 
     return obj;
 }
 
 ArrayBufferObject *
 ArrayBufferObject::create(JSContext *cx, uint32_t nbytes,
                           NewObjectKind newKind /* = GenericObject */)
 {
-    return create(cx, nbytes, BufferContents::createUnowned(nullptr));
+    return create(cx, nbytes, BufferContents::createPlain(nullptr));
 }
 
 JSObject *
 ArrayBufferObject::createSlice(JSContext *cx, Handle<ArrayBufferObject*> arrayBuffer,
                                uint32_t begin, uint32_t end)
 {
     uint32_t bufLength = arrayBuffer->byteLength();
     if (begin > bufLength || end > bufLength || begin > end) {
@@ -861,36 +902,36 @@ ArrayBufferObject::createDataViewForThis
 ArrayBufferObject::stealContents(JSContext *cx, Handle<ArrayBufferObject*> buffer,
                                  bool hasStealableContents)
 {
     MOZ_ASSERT_IF(hasStealableContents, buffer->hasStealableContents());
 
     BufferContents oldContents(buffer->dataPointer(), buffer->bufferKind());
     BufferContents newContents = AllocateArrayBufferContents(cx, buffer->byteLength());
     if (!newContents)
-        return BufferContents::createUnowned(nullptr);
+        return BufferContents::createPlain(nullptr);
 
     if (hasStealableContents) {
         // Return the old contents and give the neutered buffer a pointer to
         // freshly allocated memory that we will never write to and should
         // never get committed.
         buffer->setOwnsData(DoesntOwnData);
         if (!ArrayBufferObject::neuter(cx, buffer, newContents)) {
             js_free(newContents.data());
-            return BufferContents::createUnowned(nullptr);
+            return BufferContents::createPlain(nullptr);
         }
         return oldContents;
     }
 
     // Create a new chunk of memory to return since we cannot steal the
     // existing contents away from the buffer.
     memcpy(newContents.data(), oldContents.data(), buffer->byteLength());
     if (!ArrayBufferObject::neuter(cx, buffer, oldContents)) {
         js_free(newContents.data());
-        return BufferContents::createUnowned(nullptr);
+        return BufferContents::createPlain(nullptr);
     }
     return newContents;
 }
 
 /* static */ void
 ArrayBufferObject::addSizeOfExcludingThis(JSObject *obj, mozilla::MallocSizeOf mallocSizeOf,
                                           JS::ClassInfo *info)
 {
@@ -920,16 +961,33 @@ ArrayBufferObject::finalize(FreeOp *fop,
 {
     ArrayBufferObject &buffer = obj->as<ArrayBufferObject>();
 
     if (buffer.ownsData())
         buffer.releaseData(fop);
 }
 
 /* static */ void
+ArrayBufferObject::trace(JSTracer *trc, JSObject *obj)
+{
+    // If this buffer is associated with an inline typed object,
+    // fix up the data pointer if the typed object was moved.
+    ArrayBufferObject &buf = obj->as<ArrayBufferObject>();
+
+    if (!buf.forInlineTypedObject())
+        return;
+
+    JSObject *view = buf.firstView();
+    MOZ_ASSERT(view && view->is<InlineTransparentTypedObject>());
+
+    gc::MarkObjectUnbarriered(trc, &view, "array buffer inline typed object owner");
+    buf.setSlot(DATA_SLOT, PrivateValue(view->as<InlineTransparentTypedObject>().inlineTypedMem()));
+}
+
+/* static */ void
 ArrayBufferObject::objectMoved(JSObject *obj, const JSObject *old)
 {
     ArrayBufferObject &dst = obj->as<ArrayBufferObject>();
     const ArrayBufferObject &src = old->as<ArrayBufferObject>();
 
     // Fix up possible inline data pointer.
     if (src.hasInlineData())
         dst.setSlot(DATA_SLOT, PrivateValue(dst.inlineDataPointer()));
@@ -1274,17 +1332,17 @@ JS_NewArrayBuffer(JSContext *cx, uint32_
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_NewArrayBufferWithContents(JSContext *cx, size_t nbytes, void *data)
 {
     MOZ_ASSERT_IF(!data, nbytes == 0);
     ArrayBufferObject::BufferContents contents =
         ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(data);
-    return ArrayBufferObject::create(cx, nbytes, contents, TenuredObject);
+    return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::OwnsData, TenuredObject);
 }
 
 JS_FRIEND_API(bool)
 JS_IsArrayBufferObject(JSObject *obj)
 {
     obj = CheckedUnwrap(obj);
     return obj && obj->is<ArrayBufferObject>();
 }
@@ -1331,17 +1389,17 @@ JS_StealArrayBufferContents(JSContext *c
 }
 
 JS_PUBLIC_API(JSObject *)
 JS_NewMappedArrayBufferWithContents(JSContext *cx, size_t nbytes, void *data)
 {
     MOZ_ASSERT(data);
     ArrayBufferObject::BufferContents contents =
         ArrayBufferObject::BufferContents::create<ArrayBufferObject::MAPPED>(data);
-    return ArrayBufferObject::create(cx, nbytes, contents, TenuredObject);
+    return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::OwnsData, TenuredObject);
 }
 
 JS_PUBLIC_API(void *)
 JS_CreateMappedArrayBufferContents(int fd, size_t offset, size_t length)
 {
     return ArrayBufferObject::createMappedContents(fd, offset, length).data();
 }
 
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -76,18 +76,18 @@ class ArrayBufferObjectMaybeShared : pub
 };
 
 /*
  * ArrayBufferObject
  *
  * This class holds the underlying raw buffer that the various
  * ArrayBufferViewObject subclasses (DataViewObject and the TypedArrays)
  * access. It can be created explicitly and passed to an ArrayBufferViewObject
- * subclass, or can be created implicitly by constructing a TypedArrayObject
- * with a size.
+ * subclass, or can be created lazily when it is first accessed for a
+ * TypedArrayObject or TypedObject that doesn't have an explicit buffer.
  *
  * ArrayBufferObject (or really the underlying memory) /is not racy/: the
  * memory is private to a single worker.
  */
 class ArrayBufferObject : public ArrayBufferObjectMaybeShared
 {
     static bool byteLengthGetterImpl(JSContext *cx, CallArgs args);
     static bool fun_slice_impl(JSContext *cx, CallArgs args);
@@ -99,16 +99,21 @@ class ArrayBufferObject : public ArrayBu
     static const uint8_t FLAGS_SLOT = 3;
 
     static const uint8_t RESERVED_SLOTS = 4;
 
     static const size_t ARRAY_BUFFER_ALIGNMENT = 8;
 
   public:
 
+    enum OwnsState {
+        DoesntOwnData = 0,
+        OwnsData = 1,
+    };
+
     enum BufferKind {
         PLAIN               = 0, // malloced or inline data
         ASMJS_MALLOCED      = 1,
         ASMJS_MAPPED        = 2,
         MAPPED              = 3,
 
         KIND_MASK           = 0x3
     };
@@ -120,17 +125,31 @@ class ArrayBufferObject : public ArrayBu
         BUFFER_KIND_MASK    = BufferKind::KIND_MASK,
 
         NEUTERED            = 0x4,
 
         // The dataPointer() is owned by this buffer and should be released
         // when no longer in use. Releasing the pointer may be done by either
         // freeing or unmapping it, and how to do this is determined by the
         // buffer's other flags.
+        //
+        // Array buffers which do not own their data include buffers that
+        // allocate their data inline, and buffers that are created lazily for
+        // typed objects with inline storage, in which case the buffer points
+        // directly to the typed object's storage.
         OWNS_DATA           = 0x8,
+
+        // This array buffer was created lazily for a typed object with inline
+        // data. This implies both that the typed object owns the buffer's data
+        // and that the list of views sharing this buffer's data might be
+        // incomplete. Any missing views will be sized typed objects.
+        FOR_INLINE_TYPED_OBJECT = 0x10,
+
+        // Views of this buffer might include sized typed objects.
+        SIZED_OBJECT_VIEWS  = 0x20
     };
 
   public:
 
     class BufferContents {
         uint8_t *data_;
         BufferKind kind_;
 
@@ -146,17 +165,17 @@ class ArrayBufferObject : public ArrayBu
       public:
 
         template<BufferKind Kind>
         static BufferContents create(void *data)
         {
             return BufferContents(static_cast<uint8_t*>(data), Kind);
         }
 
-        static BufferContents createUnowned(void *data)
+        static BufferContents createPlain(void *data)
         {
             return BufferContents(static_cast<uint8_t*>(data), PLAIN);
         }
 
         uint8_t *data() const { return data_; }
         BufferKind kind() const { return kind_; }
 
         operator ConvertibleToBool() const { return data_ ? &BufferContents::nonNull : nullptr; }
@@ -176,32 +195,34 @@ class ArrayBufferObject : public ArrayBu
 #ifdef NIGHTLY_BUILD
     static bool fun_transfer(JSContext *cx, unsigned argc, Value *vp);
 #endif
 
     static bool class_constructor(JSContext *cx, unsigned argc, Value *vp);
 
     static ArrayBufferObject *create(JSContext *cx, uint32_t nbytes,
                                      BufferContents contents,
+                                     OwnsState ownsState = OwnsData,
                                      NewObjectKind newKind = GenericObject);
     static ArrayBufferObject *create(JSContext *cx, uint32_t nbytes,
                                      NewObjectKind newKind = GenericObject);
 
     static JSObject *createSlice(JSContext *cx, Handle<ArrayBufferObject*> arrayBuffer,
                                  uint32_t begin, uint32_t end);
 
     static bool createDataViewForThisImpl(JSContext *cx, CallArgs args);
     static bool createDataViewForThis(JSContext *cx, unsigned argc, Value *vp);
 
     template<typename T>
     static bool createTypedArrayFromBufferImpl(JSContext *cx, CallArgs args);
 
     template<typename T>
     static bool createTypedArrayFromBuffer(JSContext *cx, unsigned argc, Value *vp);
 
+    static void trace(JSTracer *trc, JSObject *obj);
     static void objectMoved(JSObject *obj, const JSObject *old);
 
     static BufferContents stealContents(JSContext *cx,
                                         Handle<ArrayBufferObject*> buffer,
                                         bool hasStealableContents);
 
     bool hasStealableContents() const {
         // Inline elements strictly adhere to the corresponding buffer.
@@ -222,19 +243,19 @@ class ArrayBufferObject : public ArrayBu
         return (ownsData() && isPlain()) || isAsmJSMalloced();
     }
 
     static void addSizeOfExcludingThis(JSObject *obj, mozilla::MallocSizeOf mallocSizeOf,
                                        JS::ClassInfo *info);
 
     // ArrayBufferObjects (strongly) store the first view added to them, while
     // later views are (weakly) stored in the compartment's InnerViewTable
-    // below. Buffers typically have at least one view, so this slot optimizes
-    // for the common case. Avoid entries in the InnerViewTable saves memory
-    // and non-incrementalized sweep time.
+    // below. Buffers usually only have one view, so this slot optimizes for
+    // the common case. Avoiding entries in the InnerViewTable saves memory and
+    // non-incrementalized sweep time.
     ArrayBufferViewObject *firstView();
 
     bool addView(JSContext *cx, JSObject *view);
 
     void setNewOwnedData(FreeOp* fop, BufferContents newContents);
     void changeContents(JSContext *cx, BufferContents newContents);
 
     /*
@@ -296,33 +317,38 @@ class ArrayBufferObject : public ArrayBu
         return getFixedSlotOffset(FLAGS_SLOT);
     }
     static size_t offsetOfDataSlot() {
         return getFixedSlotOffset(DATA_SLOT);
     }
 
     static uint32_t neuteredFlag() { return NEUTERED; }
 
+    void setForInlineTypedObject() {
+        setFlags(flags() | FOR_INLINE_TYPED_OBJECT);
+    }
+    void setHasSizedObjectViews() {
+        setFlags(flags() | SIZED_OBJECT_VIEWS);
+    }
+
   protected:
-    enum OwnsState {
-        DoesntOwnData = 0,
-        OwnsData = 1,
-    };
-
     void setDataPointer(BufferContents contents, OwnsState ownsState);
     void setByteLength(size_t length);
 
     uint32_t flags() const;
     void setFlags(uint32_t flags);
 
     bool ownsData() const { return flags() & OWNS_DATA; }
     void setOwnsData(OwnsState owns) {
         setFlags(owns ? (flags() | OWNS_DATA) : (flags() & ~OWNS_DATA));
     }
 
+    bool forInlineTypedObject() const { return flags() & FOR_INLINE_TYPED_OBJECT; }
+    bool hasSizedObjectViews() const { return flags() & SIZED_OBJECT_VIEWS; }
+
     void setIsAsmJSMalloced() { setFlags((flags() & ~KIND_MASK) | ASMJS_MALLOCED); }
     void setIsNeutered() { setFlags(flags() | NEUTERED); }
 
     void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState) {
         setByteLength(byteLength);
         setFlags(0);
         setFirstView(nullptr);
         setDataPointer(contents, ownsState);
--- a/js/src/vm/MemoryMetrics.cpp
+++ b/js/src/vm/MemoryMetrics.cpp
@@ -332,16 +332,17 @@ StatsCompartmentCallback(JSRuntime *rt, 
     // Measure the compartment object itself, and things hanging off it.
     compartment->addSizeOfIncludingThis(rtStats->mallocSizeOf_,
                                         &cStats.typeInferenceAllocationSiteTables,
                                         &cStats.typeInferenceArrayTypeTables,
                                         &cStats.typeInferenceObjectTypeTables,
                                         &cStats.compartmentObject,
                                         &cStats.compartmentTables,
                                         &cStats.innerViewsTable,
+                                        &cStats.lazyArrayBuffersTable,
                                         &cStats.crossCompartmentWrappersTable,
                                         &cStats.regexpCompartment,
                                         &cStats.savedStacksSet);
 }
 
 static void
 StatsArenaCallback(JSRuntime *rt, void *data, gc::Arena *arena,
                    JSGCTraceKind traceKind, size_t thingSize)
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2235,16 +2235,20 @@ ReportCompartmentStats(const JS::Compart
     ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("compartment-tables"),
         cStats.compartmentTables,
         "Compartment-wide tables storing shape and type object information.");
 
     ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("inner-views"),
         cStats.innerViewsTable,
         "The table for array buffer inner views.");
 
+    ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("lazy-array-buffers"),
+        cStats.lazyArrayBuffersTable,
+        "The table for typed object lazy array buffers.");
+
     ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("cross-compartment-wrapper-table"),
         cStats.crossCompartmentWrappersTable,
         "The cross-compartment wrapper table.");
 
     ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("regexp-compartment"),
         cStats.regexpCompartment,
         "The regexp compartment and regexp data.");