Bug 1061404 - Move ArrayBuffer view list into per compartment tables, r=sfink.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 17 Sep 2014 11:13:17 -0700
changeset 229110 e3da4ca374cf921b5de1f641e19326dc5e2696bf
parent 229109 7b3ba487c01b6c55a4d290b1f1ab826f42c37eec
child 229111 3774cabb49f23b15d4be7f5649b4f6fb59955385
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink
bugs1061404
milestone35.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 1061404 - Move ArrayBuffer view list into per compartment tables, r=sfink.
js/public/MemoryMetrics.h
js/src/builtin/TypedObject.cpp
js/src/builtin/TypedObject.h
js/src/builtin/TypedObjectConstants.h
js/src/gc/Nursery.cpp
js/src/gc/Statistics.cpp
js/src/gc/Statistics.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsgc.cpp
js/src/vm/ArrayBufferObject.cpp
js/src/vm/ArrayBufferObject.h
js/src/vm/MemoryMetrics.cpp
js/src/vm/TypedArrayObject.cpp
js/src/vm/TypedArrayObject.h
js/xpconnect/src/XPCJSRuntime.cpp
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -533,16 +533,17 @@ struct CompartmentStats
     macro(Other,   NotLiveGCThing, baselineStubsFallback) \
     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, crossCompartmentWrappersTable) \
     macro(Other,   NotLiveGCThing, regexpCompartment) \
     macro(Other,   NotLiveGCThing, savedStacksSet)
 
     CompartmentStats()
       : FOR_EACH_SIZE(ZERO_SIZE)
         classInfo(),
         extra(),
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -1503,40 +1503,41 @@ TypedObject::createUnattachedWithClass(J
     RootedObject obj(cx, NewObjectWithClassProto(cx, clasp, &*proto, nullptr));
     if (!obj)
         return nullptr;
 
     obj->initPrivate(nullptr);
     obj->initReservedSlot(JS_BUFVIEW_SLOT_BYTEOFFSET, Int32Value(0));
     obj->initReservedSlot(JS_BUFVIEW_SLOT_LENGTH, Int32Value(length));
     obj->initReservedSlot(JS_BUFVIEW_SLOT_OWNER, NullValue());
-    obj->initReservedSlot(JS_BUFVIEW_SLOT_NEXT_VIEW, PrivateValue(nullptr));
 
     return static_cast<TypedObject*>(&*obj);
 }
 
 void
-TypedObject::attach(ArrayBufferObject &buffer, int32_t offset)
+TypedObject::attach(JSContext *cx, ArrayBufferObject &buffer, int32_t offset)
 {
     JS_ASSERT(offset >= 0);
     JS_ASSERT((size_t) (offset + size()) <= buffer.byteLength());
 
-    buffer.addView(this);
+    if (!buffer.addView(cx, this))
+        CrashAtUnhandlableOOM("TypedObject::attach");
+
     InitArrayBufferViewDataPointer(this, &buffer, offset);
     setReservedSlot(JS_BUFVIEW_SLOT_BYTEOFFSET, Int32Value(offset));
     setReservedSlot(JS_BUFVIEW_SLOT_OWNER, ObjectValue(buffer));
 }
 
 void
-TypedObject::attach(TypedObject &typedObj, int32_t offset)
+TypedObject::attach(JSContext *cx, TypedObject &typedObj, int32_t offset)
 {
     JS_ASSERT(!typedObj.owner().isNeutered());
     JS_ASSERT(typedObj.typedMem() != NULL);
 
-    attach(typedObj.owner(), typedObj.offset() + offset);
+    attach(cx, typedObj.owner(), typedObj.offset() + 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)
 {
     switch (descr.kind()) {
@@ -1567,17 +1568,17 @@ TypedObject::createDerived(JSContext *cx
     int32_t length = TypedObjLengthFromType(*type);
 
     const js::Class *clasp = typedObj->getClass();
     Rooted<TypedObject*> obj(cx);
     obj = createUnattachedWithClass(cx, clasp, type, length);
     if (!obj)
         return nullptr;
 
-    obj->attach(*typedObj, offset);
+    obj->attach(cx, *typedObj, offset);
     return obj;
 }
 
 /*static*/ TypedObject *
 TypedObject::createZeroed(JSContext *cx,
                           HandleTypeDescr descr,
                           int32_t length)
 {
@@ -1596,17 +1597,17 @@ TypedObject::createZeroed(JSContext *cx,
       case type::SizedArray:
       {
         size_t totalSize = descr->as<SizedTypeDescr>().size();
         Rooted<ArrayBufferObject*> buffer(cx);
         buffer = ArrayBufferObject::create(cx, totalSize);
         if (!buffer)
             return nullptr;
         descr->as<SizedTypeDescr>().initInstances(cx->runtime(), buffer->dataPointer(), 1);
-        obj->attach(*buffer, 0);
+        obj->attach(cx, *buffer, 0);
         return obj;
       }
 
       case type::UnsizedArray:
       {
         Rooted<SizedTypeDescr*> elementTypeRepr(cx);
         elementTypeRepr = &descr->as<UnsizedArrayTypeDescr>().elementType();
 
@@ -1619,17 +1620,17 @@ TypedObject::createZeroed(JSContext *cx,
 
         Rooted<ArrayBufferObject*> buffer(cx);
         buffer = ArrayBufferObject::create(cx, totalSize.value());
         if (!buffer)
             return nullptr;
 
         if (length)
             elementTypeRepr->initInstances(cx->runtime(), buffer->dataPointer(), length);
-        obj->attach(*buffer, 0);
+        obj->attach(cx, *buffer, 0);
         return obj;
       }
     }
 
     MOZ_CRASH("Bad TypeRepresentation Kind");
 }
 
 static bool
@@ -1647,21 +1648,21 @@ ReportTypedObjTypeError(JSContext *cx,
 
     JS_free(cx, (void *) typeReprStr);
     return false;
 }
 
 /*static*/ void
 TypedObject::obj_trace(JSTracer *trace, JSObject *object)
 {
-    ArrayBufferViewObject::trace(trace, object);
-
     JS_ASSERT(object->is<TypedObject>());
     TypedObject &typedObj = object->as<TypedObject>();
 
+    gc::MarkSlot(trace, &typedObj.getFixedSlotRef(JS_BUFVIEW_SLOT_OWNER), "typed object owner");
+
     // 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();
 
     if (descr.opaque()) {
         uint8_t *mem = typedObj.typedMem();
         if (!mem)
             return; // partially constructed
@@ -2400,17 +2401,17 @@ TypedObject::constructSized(JSContext *c
             return false;
         }
 
         Rooted<TypedObject*> obj(cx);
         obj = TypedObject::createUnattached(cx, callee, LengthForType(*callee));
         if (!obj)
             return false;
 
-        obj->attach(*buffer, offset);
+        obj->attach(cx, *buffer, offset);
         args.rval().setObject(*obj);
         return true;
     }
 
     // Data constructor.
     if (args[0].isObject()) {
         // Create the typed object.
         int32_t length = LengthForType(*callee);
@@ -2538,17 +2539,17 @@ TypedObject::constructUnsized(JSContext 
             return false;
         }
 
         Rooted<TypedObject*> obj(cx);
         obj = TypedObject::createUnattached(cx, callee, length);
         if (!obj)
             return false;
 
-        obj->attach(args[0].toObject().as<ArrayBufferObject>(), offset);
+        obj->attach(cx, args[0].toObject().as<ArrayBufferObject>(), offset);
     }
 
     // Data constructor for unsized values
     if (args[0].isObject()) {
         // Read length out of the object.
         RootedObject arg(cx, &args[0].toObject());
         RootedValue lengthVal(cx);
         if (!JSObject::getProperty(cx, arg, arg, cx->names().length, &lengthVal))
@@ -2681,29 +2682,35 @@ js::NewDerivedTypedObject(JSContext *cx,
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
 bool
-js::AttachTypedObject(ThreadSafeContext *, unsigned argc, Value *vp)
+js::AttachTypedObject(ThreadSafeContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     JS_ASSERT(args.length() == 3);
     JS_ASSERT(args[0].isObject() && args[0].toObject().is<TypedObject>());
     JS_ASSERT(args[1].isObject() && args[1].toObject().is<TypedObject>());
     JS_ASSERT(args[2].isInt32());
 
     TypedObject &handle = args[0].toObject().as<TypedObject>();
     TypedObject &target = args[1].toObject().as<TypedObject>();
     JS_ASSERT(handle.typedMem() == nullptr); // must not be attached already
     size_t offset = args[2].toInt32();
-    handle.attach(target, offset);
+
+    if (cx->isForkJoinContext()) {
+        LockedJSContext ncx(cx->asForkJoinContext());
+        handle.attach(ncx, target, offset);
+    } else {
+        handle.attach(cx->asJSContext(), target, offset);
+    }
     return true;
 }
 
 JS_JITINFO_NATIVE_PARALLEL_THREADSAFE(js::AttachTypedObjectJitInfo,
                                       AttachTypedObjectJitInfo,
                                       js::AttachTypedObject);
 
 bool
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -665,20 +665,20 @@ class TypedObject : public ArrayBufferVi
     // used for sized types. Note that the callee here is the *type descriptor*,
     // not the typedObj.
     static bool constructSized(JSContext *cx, unsigned argc, Value *vp);
 
     // As `constructSized`, but for unsized array types.
     static bool constructUnsized(JSContext *cx, unsigned argc, Value *vp);
 
     // Use this method when `buffer` is the owner of the memory.
-    void attach(ArrayBufferObject &buffer, int32_t offset);
+    void attach(JSContext *cx, ArrayBufferObject &buffer, int32_t offset);
 
     // Otherwise, use this to attach to memory referenced by another typedObj.
-    void attach(TypedObject &typedObj, int32_t offset);
+    void attach(JSContext *cx, TypedObject &typedObj, int32_t offset);
 
     // Invoked when array buffer is transferred elsewhere
     void neuter(void *newData);
 
     int32_t offset() const {
         return getReservedSlot(JS_BUFVIEW_SLOT_BYTEOFFSET).toInt32();
     }
 
--- a/js/src/builtin/TypedObjectConstants.h
+++ b/js/src/builtin/TypedObjectConstants.h
@@ -90,30 +90,29 @@
 ///////////////////////////////////////////////////////////////////////////
 // Slots for typed objects
 
 
 // Common to data view, typed arrays, and typed objects:
 #define JS_BUFVIEW_SLOT_BYTEOFFSET       0
 #define JS_BUFVIEW_SLOT_LENGTH           1 // see (*) below
 #define JS_BUFVIEW_SLOT_OWNER            2
-#define JS_BUFVIEW_SLOT_NEXT_VIEW        3
 
 // Specific to data view:
-#define JS_DATAVIEW_SLOT_DATA            7 // see (**) below
-#define JS_DATAVIEW_SLOTS                4 // Number of slots for data views
+#define JS_DATAVIEW_SLOT_DATA            3 // see (**) below
+#define JS_DATAVIEW_SLOTS                3 // Number of slots for data views
 
 // Specific to typed arrays:
-#define JS_TYPEDARR_SLOT_TYPE            4 // A ScalarTypeDescr::Type constant
+#define JS_TYPEDARR_SLOT_TYPE            3 // A ScalarTypeDescr::Type constant
 #define JS_TYPEDARR_SLOT_DATA            7 // see (**) below
-#define JS_TYPEDARR_SLOTS                5 // Number of slots for typed arrays
+#define JS_TYPEDARR_SLOTS                4 // Number of slots for typed arrays
 
 // Specific to typed objects:
-#define JS_TYPEDOBJ_SLOT_DATA            7
-#define JS_TYPEDOBJ_SLOTS                4 // Number of slots for typed objs
+#define JS_TYPEDOBJ_SLOT_DATA            3
+#define JS_TYPEDOBJ_SLOTS                3 // Number of slots for typed objs
 
 // (*) The interpretation of the JS_BUFVIEW_SLOT_LENGTH slot depends on
 // the kind of view:
 // - DataView: stores the length in bytes
 // - TypedArray: stores the array length
 // - TypedObject: for arrays, stores the array length, else 0
 
 // (**) This is the index of the slot that will be used for private data.
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -318,17 +318,16 @@ class MinorCollectionTracer : public JST
      */
     RelocationOverlay *head;
     RelocationOverlay **tail;
 
     /* Save and restore all of the runtime state we use during MinorGC. */
     bool savedRuntimeNeedBarrier;
     AutoDisableProxyCheck disableStrictProxyChecking;
     AutoEnterOOMUnsafeRegion oomUnsafeRegion;
-    ArrayBufferVector liveArrayBuffers;
 
     /* Insert the given relocation entry into the list of things to visit. */
     MOZ_ALWAYS_INLINE void insertIntoFixupList(RelocationOverlay *entry) {
         *tail = entry;
         tail = &entry->next_;
         *tail = nullptr;
     }
 
@@ -349,35 +348,20 @@ class MinorCollectionTracer : public JST
          * pre-barriers do not fire on objects that have been relocated. The
          * pre-barrier's call to obj->zone() will try to look through shape_,
          * which is now the relocation magic and will crash. However,
          * zone->needsIncrementalBarrier() must still be set correctly so that
          * allocations we make in minor GCs between incremental slices will
          * allocate their objects marked.
          */
         rt->setNeedsIncrementalBarrier(false);
-
-        /*
-         * We use the live array buffer lists to track traced buffers so we can
-         * sweep their dead views. Incremental collection also use these lists,
-         * so we may need to save and restore their contents here.
-         */
-        if (rt->gc.state() != NO_INCREMENTAL) {
-            for (GCCompartmentsIter c(rt); !c.done(); c.next()) {
-                if (!ArrayBufferObject::saveArrayBufferList(c, liveArrayBuffers))
-                    CrashAtUnhandlableOOM("OOM while saving live array buffers");
-                ArrayBufferObject::resetArrayBufferList(c);
-            }
-        }
     }
 
     ~MinorCollectionTracer() {
         runtime()->setNeedsIncrementalBarrier(savedRuntimeNeedBarrier);
-        if (runtime()->gc.state() != NO_INCREMENTAL)
-            ArrayBufferObject::restoreArrayBufferLists(liveArrayBuffers);
     }
 };
 
 } /* namespace gc */
 } /* namespace js */
 
 static AllocKind
 GetObjectAllocKindForCopy(const Nursery &nursery, JSObject *obj)
@@ -821,18 +805,18 @@ js::Nursery::collect(JSRuntime *rt, JS::
     TIME_START(collectToFP);
     TenureCountCache tenureCounts;
     collectToFixedPoint(&trc, tenureCounts);
     TIME_END(collectToFP);
 
     // Update the array buffer object's view lists.
     TIME_START(sweepArrayBufferViewList);
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
-        if (!c->gcLiveArrayBuffers.empty())
-            ArrayBufferObject::sweep(c);
+        if (c->innerViews.needsSweepAfterMinorGC())
+            c->innerViews.sweepAfterMinorGC(rt);
     }
     TIME_END(sweepArrayBufferViewList);
 
     // Update any slot or element pointers whose destination has been tenured.
     TIME_START(updateJitActivations);
     js::jit::UpdateJitActivationsForMinorGC<Nursery>(&rt->mainThread, &trc);
     TIME_END(updateJitActivations);
 
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -287,16 +287,17 @@ static const PhaseInfo phases[] = {
     { PHASE_SWEEP_MARK_GRAY, "Mark Gray", PHASE_SWEEP_MARK },
     { PHASE_SWEEP_MARK_GRAY_WEAK, "Mark Gray and Weak", PHASE_SWEEP_MARK },
     { PHASE_FINALIZE_START, "Finalize Start Callback", PHASE_SWEEP },
     { PHASE_SWEEP_ATOMS, "Sweep Atoms", PHASE_SWEEP },
     { PHASE_SWEEP_SYMBOL_REGISTRY, "Sweep Symbol Registry", PHASE_SWEEP },
     { PHASE_SWEEP_COMPARTMENTS, "Sweep Compartments", PHASE_SWEEP },
     { PHASE_SWEEP_DISCARD_CODE, "Sweep Discard Code", PHASE_SWEEP_COMPARTMENTS },
     { PHASE_SWEEP_TABLES, "Sweep Tables", PHASE_SWEEP_COMPARTMENTS },
+    { PHASE_SWEEP_TABLES_INNER_VIEWS, "Sweep Inner Views", PHASE_SWEEP_TABLES },
     { PHASE_SWEEP_TABLES_WRAPPER, "Sweep Cross Compartment Wrappers", PHASE_SWEEP_TABLES },
     { PHASE_SWEEP_TABLES_BASE_SHAPE, "Sweep Base Shapes", PHASE_SWEEP_TABLES },
     { PHASE_SWEEP_TABLES_INITIAL_SHAPE, "Sweep Initial Shapes", PHASE_SWEEP_TABLES },
     { PHASE_SWEEP_TABLES_TYPE_OBJECT, "Sweep Type Objects", PHASE_SWEEP_TABLES },
     { PHASE_SWEEP_TABLES_BREAKPOINT, "Sweep Breakpoints", PHASE_SWEEP_TABLES },
     { PHASE_SWEEP_TABLES_REGEXP, "Sweep Regexps", PHASE_SWEEP_TABLES },
     { PHASE_DISCARD_ANALYSIS, "Discard Analysis", PHASE_SWEEP_COMPARTMENTS },
     { PHASE_DISCARD_TI, "Discard TI", PHASE_DISCARD_ANALYSIS },
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -38,16 +38,17 @@ enum Phase {
     PHASE_SWEEP_MARK_GRAY,
     PHASE_SWEEP_MARK_GRAY_WEAK,
     PHASE_FINALIZE_START,
     PHASE_SWEEP_ATOMS,
     PHASE_SWEEP_SYMBOL_REGISTRY,
     PHASE_SWEEP_COMPARTMENTS,
     PHASE_SWEEP_DISCARD_CODE,
     PHASE_SWEEP_TABLES,
+    PHASE_SWEEP_TABLES_INNER_VIEWS,
     PHASE_SWEEP_TABLES_WRAPPER,
     PHASE_SWEEP_TABLES_BASE_SHAPE,
     PHASE_SWEEP_TABLES_INITIAL_SHAPE,
     PHASE_SWEEP_TABLES_TYPE_OBJECT,
     PHASE_SWEEP_TABLES_BREAKPOINT,
     PHASE_SWEEP_TABLES_REGEXP,
     PHASE_DISCARD_ANALYSIS,
     PHASE_DISCARD_TI,
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -573,16 +573,22 @@ JSCompartment::markRoots(JSTracer *trc)
 void
 JSCompartment::sweep(FreeOp *fop, bool releaseTypes)
 {
     JS_ASSERT(!activeAnalysis);
     JSRuntime *rt = runtimeFromMainThread();
 
     {
         gcstats::MaybeAutoPhase ap(rt->gc.stats, !rt->isHeapCompacting(),
+                                   gcstats::PHASE_SWEEP_TABLES_INNER_VIEWS);
+        innerViews.sweep(rt);
+    }
+
+    {
+        gcstats::MaybeAutoPhase ap(rt->gc.stats, !rt->isHeapCompacting(),
                                    gcstats::PHASE_SWEEP_TABLES_WRAPPER);
         sweepCrossCompartmentWrappers();
     }
 
     /* Remove dead references held weakly by the compartment. */
 
     sweepBaseShapeTable();
     sweepInitialShapeTable();
@@ -907,27 +913,29 @@ 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 *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);
     *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
@@ -227,16 +227,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 *crossCompartmentWrappers,
                                 size_t *regexpCompartment,
                                 size_t *savedStacksSet);
 
     /*
      * Shared scope property tree, and arena-pool for allocating its nodes.
      */
     js::PropertyTree             propertyTree;
@@ -269,31 +270,32 @@ 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.
+    js::InnerViewTable innerViews;
+
     /* 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
      * slot for the former, or a special slot for the latter.
      */
     JSObject                     *gcIncomingGrayPointers;
 
-    /* During GC, list of live array buffers with >1 view accumulated during tracing. */
-    js::ArrayBufferVector        gcLiveArrayBuffers;
-
     /* Linked list of live weakmaps in this compartment. */
     js::WeakMapBase              *gcWeakMapList;
 
   private:
     enum {
         DebugMode = 1 << 0,
         DebugNeedDelazification = 1 << 1
     };
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3680,17 +3680,16 @@ GCRuntime::beginMarkPhase(JS::gcreason::
         } else {
             isFull = false;
         }
 
         zone->setPreservingCode(false);
     }
 
     for (CompartmentsIter c(rt, WithAtoms); !c.done(); c.next()) {
-        JS_ASSERT(c->gcLiveArrayBuffers.empty());
         c->marked = false;
         c->scheduledForDestruction = false;
         c->maybeAlive = false;
         if (shouldPreserveJITCode(c, currentTime, reason))
             c->zone()->setPreservingCode(true);
     }
 
     if (!rt->gc.cleanUpEverything) {
@@ -3974,43 +3973,37 @@ js::gc::MarkingValidator::nonIncremental
             return;
 
         memcpy((void *)entry->bitmap, (void *)bitmap->bitmap, sizeof(bitmap->bitmap));
         if (!map.putNew(r.front(), entry))
             return;
     }
 
     /*
-     * Temporarily clear the weakmaps' mark flags and the lists of live array
-     * buffers for the compartments we are collecting.
+     * Temporarily clear the weakmaps' mark flags for the compartments we are
+     * collecting.
      */
 
     WeakMapSet markedWeakMaps;
     if (!markedWeakMaps.init())
         return;
 
-    ArrayBufferVector arrayBuffers;
     for (GCCompartmentsIter c(runtime); !c.done(); c.next()) {
-        if (!WeakMapBase::saveCompartmentMarkedWeakMaps(c, markedWeakMaps) ||
-            !ArrayBufferObject::saveArrayBufferList(c, arrayBuffers))
-        {
+        if (!WeakMapBase::saveCompartmentMarkedWeakMaps(c, markedWeakMaps))
             return;
-        }
     }
 
     /*
      * After this point, the function should run to completion, so we shouldn't
      * do anything fallible.
      */
     initialized = true;
 
-    for (GCCompartmentsIter c(runtime); !c.done(); c.next()) {
+    for (GCCompartmentsIter c(runtime); !c.done(); c.next())
         WeakMapBase::unmarkCompartment(c);
-        ArrayBufferObject::resetArrayBufferList(c);
-    }
 
     /* Re-do all the marking, but non-incrementally. */
     js::gc::State state = gc->incrementalState;
     gc->incrementalState = MARK_ROOTS;
 
     JS_ASSERT(gcmarker->isDrained());
     gcmarker->reset();
 
@@ -4058,22 +4051,19 @@ js::gc::MarkingValidator::nonIncremental
     /* Take a copy of the non-incremental mark state and restore the original. */
     for (GCChunkSet::Range r(gc->chunkSet.all()); !r.empty(); r.popFront()) {
         Chunk *chunk = r.front();
         ChunkBitmap *bitmap = &chunk->bitmap;
         ChunkBitmap *entry = map.lookup(chunk)->value();
         Swap(*entry, *bitmap);
     }
 
-    for (GCCompartmentsIter c(runtime); !c.done(); c.next()) {
+    for (GCCompartmentsIter c(runtime); !c.done(); c.next())
         WeakMapBase::unmarkCompartment(c);
-        ArrayBufferObject::resetArrayBufferList(c);
-    }
     WeakMapBase::restoreCompartmentMarkedWeakMaps(markedWeakMaps);
-    ArrayBufferObject::restoreArrayBufferLists(arrayBuffers);
 
     gc->incrementalState = state;
 }
 
 void
 js::gc::MarkingValidator::validate()
 {
     /*
@@ -4331,20 +4321,18 @@ GCRuntime::getNextZoneGroup()
             JS_ASSERT(zone->isGCMarking());
             zone->setNeedsIncrementalBarrier(false, Zone::UpdateJit);
             zone->setGCState(Zone::NoGC);
             zone->gcGrayRoots.clearAndFree();
         }
         rt->setNeedsIncrementalBarrier(false);
         AssertNeedsBarrierFlagsConsistent(rt);
 
-        for (GCCompartmentGroupIter comp(rt); !comp.done(); comp.next()) {
-            ArrayBufferObject::resetArrayBufferList(comp);
+        for (GCCompartmentGroupIter comp(rt); !comp.done(); comp.next())
             ResetGrayList(comp);
-        }
 
         abortSweepAfterCurrentGroup = false;
         currentZoneGroup = nullptr;
     }
 }
 
 /*
  * Gray marking:
@@ -4657,20 +4645,16 @@ GCRuntime::beginSweepingZoneGroup()
             rt->sweepAtoms();
         }
         {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_SYMBOL_REGISTRY);
             rt->symbolRegistry().sweep();
         }
     }
 
-    /* Prune out dead views from ArrayBuffer's view lists. */
-    for (GCCompartmentGroupIter c(rt); !c.done(); c.next())
-        ArrayBufferObject::sweep(c);
-
     /* Collect watch points associated with unreachable objects. */
     WatchpointMap::sweepAll(rt);
 
     /* Detach unreachable debuggers and global objects from each other. */
     Debugger::sweepAll(&fop);
 
     {
         gcstats::AutoPhase ap(stats, gcstats::PHASE_SWEEP_COMPARTMENTS);
@@ -5014,17 +4998,16 @@ GCRuntime::endSweepPhase(bool lastGC)
             JS_ASSERT_IF(!IsBackgroundFinalized(AllocKind(i)) ||
                          !sweepOnBackgroundThread,
                          !zone->allocator.arenas.arenaListsToSweep[i]);
         }
     }
 
     for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         JS_ASSERT(!c->gcIncomingGrayPointers);
-        JS_ASSERT(c->gcLiveArrayBuffers.empty());
 
         for (JSCompartment::WrapperEnum e(c); !e.empty(); e.popFront()) {
             if (e.front().key().kind != CrossCompartmentKey::StringWrapper)
                 AssertNotOnGrayList(&e.front().value().unbarrieredGet().toObject());
         }
     }
 #endif
 }
@@ -5160,20 +5143,18 @@ GCRuntime::resetIncrementalGC(const char
 
       case MARK: {
         /* Cancel any ongoing marking. */
         AutoCopyFreeListToArenasForGC copy(rt);
 
         marker.reset();
         marker.stop();
 
-        for (GCCompartmentsIter c(rt); !c.done(); c.next()) {
-            ArrayBufferObject::resetArrayBufferList(c);
+        for (GCCompartmentsIter c(rt); !c.done(); c.next())
             ResetGrayList(c);
-        }
 
         for (GCZonesIter zone(rt); !zone.done(); zone.next()) {
             JS_ASSERT(zone->isGCMarking());
             zone->setNeedsIncrementalBarrier(false, Zone::UpdateJit);
             zone->setGCState(Zone::NoGC);
         }
         rt->setNeedsIncrementalBarrier(false);
         AssertNeedsBarrierFlagsConsistent(rt);
@@ -5203,19 +5184,16 @@ GCRuntime::resetIncrementalGC(const char
 
       default:
         MOZ_CRASH("Invalid incremental GC state");
     }
 
     stats.reset(reason);
 
 #ifdef DEBUG
-    for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next())
-        JS_ASSERT(c->gcLiveArrayBuffers.empty());
-
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         JS_ASSERT(!zone->needsIncrementalBarrier());
         for (unsigned i = 0; i < FINALIZE_LIMIT; ++i)
             JS_ASSERT(!zone->allocator.arenas.arenaListsToSweep[i]);
     }
 #endif
 }
 
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -114,17 +114,17 @@ const Class ArrayBufferObject::class_ = 
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     ArrayBufferObject::finalize,
     nullptr,        /* call        */
     nullptr,        /* hasInstance */
     nullptr,        /* construct   */
-    ArrayBufferObject::obj_trace,
+    nullptr,        /* trace       */
     JS_NULL_CLASS_SPEC,
     {
         nullptr,    /* outerObject */
         nullptr,    /* innerObject */
         nullptr,    /* iteratorObject */
         false,      /* isWrappedNative */
         nullptr,    /* weakmapKeyDelegateOp */
         ArrayBufferObject::objectMoved
@@ -271,126 +271,108 @@ AllocateArrayBufferContents(JSContext *c
 {
     uint8_t *p = cx->runtime()->pod_callocCanGC<uint8_t>(nbytes);
     if (!p)
         js_ReportOutOfMemory(cx);
 
     return ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN_BUFFER>(p);
 }
 
-ArrayBufferViewObject *
-ArrayBufferObject::viewList() const
-{
-    return reinterpret_cast<ArrayBufferViewObject *>(getSlot(VIEW_LIST_SLOT).toPrivate());
-}
-
-void
-ArrayBufferObject::setViewListNoBarrier(ArrayBufferViewObject *viewsHead)
-{
-    setSlot(VIEW_LIST_SLOT, PrivateValue(viewsHead));
-}
-
-void
-ArrayBufferObject::setViewList(ArrayBufferViewObject *viewsHead)
-{
-    if (ArrayBufferViewObject *oldHead = viewList())
-        ArrayBufferViewObject::writeBarrierPre(oldHead);
-    setViewListNoBarrier(viewsHead);
-    PostBarrierTypedArrayObject(this);
-}
-
 bool
 ArrayBufferObject::canNeuter(JSContext *cx)
 {
     if (isAsmJSArrayBuffer()) {
         if (!ArrayBufferObject::canNeuterAsmJSArrayBuffer(cx, *this))
             return false;
     }
 
     return true;
 }
 
+void
+ArrayBufferObject::neuterView(JSContext *cx, ArrayBufferViewObject *view,
+                              BufferContents newContents)
+{
+    view->neuter(newContents.data());
+
+    // Notify compiled jit code that the base pointer has moved.
+    MarkObjectStateChange(cx, view);
+}
+
 /* static */ void
 ArrayBufferObject::neuter(JSContext *cx, Handle<ArrayBufferObject*> buffer,
                           BufferContents newContents)
 {
     JS_ASSERT(buffer->canNeuter(cx));
 
     // Neuter all views on the buffer, clear out the list of views and the
     // buffer's data.
 
-    for (ArrayBufferViewObject *view = buffer->viewList(); view; view = view->nextView()) {
-        view->neuter(newContents.data());
-
-        // Notify compiled jit code that the base pointer has moved.
-        MarkObjectStateChange(cx, view);
+    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 (newContents.data() != buffer->dataPointer())
         buffer->setNewOwnedData(cx->runtime()->defaultFreeOp(), newContents);
 
     buffer->setByteLength(0);
-    buffer->setViewList(nullptr);
     buffer->setIsNeutered();
-
-    // If this is happening during an incremental GC, remove the buffer from
-    // the list of live buffers with multiple views if necessary.
-    if (buffer->inLiveList()) {
-        ArrayBufferVector &gcLiveArrayBuffers = cx->compartment()->gcLiveArrayBuffers;
-        DebugOnly<bool> found = false;
-        for (size_t i = 0; i < gcLiveArrayBuffers.length(); i++) {
-            if (buffer == gcLiveArrayBuffers[i]) {
-                found = true;
-                gcLiveArrayBuffers[i] = gcLiveArrayBuffers.back();
-                gcLiveArrayBuffers.popBack();
-                break;
-            }
-        }
-        JS_ASSERT(found);
-        buffer->setInLiveList(false);
-    }
 }
 
 void
 ArrayBufferObject::setNewOwnedData(FreeOp* fop, BufferContents newContents)
 {
     JS_ASSERT(!isAsmJSArrayBuffer());
 
     if (ownsData()) {
         JS_ASSERT(newContents.data() != dataPointer());
         releaseData(fop);
     }
 
     setDataPointer(newContents, OwnsData);
 }
 
 void
+ArrayBufferObject::changeViewContents(JSContext *cx, ArrayBufferViewObject *view,
+                                      uint8_t *oldDataPointer, BufferContents newContents)
+{
+    // Watch out for NULL data pointers in views. This means that the view
+    // is not fully initialized (in which case it'll be initialized later
+    // with the correct pointer).
+    uint8_t *viewDataPointer = view->dataPointer();
+    if (viewDataPointer) {
+        JS_ASSERT(newContents);
+        ptrdiff_t offset = viewDataPointer - oldDataPointer;
+        viewDataPointer = static_cast<uint8_t *>(newContents.data()) + offset;
+        view->setPrivate(viewDataPointer);
+    }
+
+    // Notify compiled jit code that the base pointer has moved.
+    MarkObjectStateChange(cx, view);
+}
+
+void
 ArrayBufferObject::changeContents(JSContext *cx, BufferContents newContents)
 {
     // Change buffer contents.
     uint8_t* oldDataPointer = dataPointer();
     setNewOwnedData(cx->runtime()->defaultFreeOp(), newContents);
 
     // Update all views.
-    ArrayBufferViewObject *viewListHead = viewList();
-    for (ArrayBufferViewObject *view = viewListHead; view; view = view->nextView()) {
-        // Watch out for NULL data pointers in views. This means that the view
-        // is not fully initialized (in which case it'll be initialized later
-        // with the correct pointer).
-        uint8_t *viewDataPointer = view->dataPointer();
-        if (viewDataPointer) {
-            JS_ASSERT(newContents);
-            ptrdiff_t offset = viewDataPointer - oldDataPointer;
-            viewDataPointer = static_cast<uint8_t *>(newContents.data()) + offset;
-            view->setPrivate(viewDataPointer);
-        }
-
-        // Notify compiled jit code that the base pointer has moved.
-        MarkObjectStateChange(cx, view);
+    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->isAsmJSArrayBuffer())
         return true;
 
@@ -532,36 +514,16 @@ void
 ArrayBufferObject::releaseMappedArray()
 {
     if(!isMappedArrayBuffer() || isNeutered())
         return;
 
     DeallocateMappedContent(dataPointer(), byteLength());
 }
 
-void
-ArrayBufferObject::addView(ArrayBufferViewObject *view)
-{
-    // Note that pre-barriers are not needed here because either the list was
-    // previously empty, in which case no pointer is being overwritten, or the
-    // list was nonempty and will be made weak during this call (and weak
-    // pointers cannot violate the snapshot-at-the-beginning invariant.)
-
-    ArrayBufferViewObject *viewsHead = viewList();
-    if (viewsHead == nullptr) {
-        // This ArrayBufferObject will have a single view at this point, so it
-        // is a strong pointer (it will be marked during tracing.)
-        JS_ASSERT(view->nextView() == nullptr);
-    } else {
-        view->setNextView(viewsHead);
-    }
-
-    setViewList(view);
-}
-
 uint8_t *
 ArrayBufferObject::dataPointer() const
 {
     return static_cast<uint8_t *>(getSlot(DATA_SLOT).toPrivate());
 }
 
 void
 ArrayBufferObject::releaseData(FreeOp *fop)
@@ -799,204 +761,209 @@ ArrayBufferObject::finalize(FreeOp *fop,
 {
     ArrayBufferObject &buffer = obj->as<ArrayBufferObject>();
 
     if (buffer.ownsData())
         buffer.releaseData(fop);
 }
 
 /* static */ void
-ArrayBufferObject::obj_trace(JSTracer *trc, JSObject *obj)
-{
-    JSRuntime *rt = trc->runtime();
-    if (!IS_GC_MARKING_TRACER(trc) && !rt->isHeapMinorCollecting() && !rt->isHeapCompacting()
-#ifdef JSGC_FJGENERATIONAL
-        && !rt->isFJMinorCollecting()
-#endif
-        )
-    {
-        return;
-    }
-
-    // ArrayBufferObjects need to maintain a list of possibly-weak pointers to
-    // their views. The straightforward way to update the weak pointers would
-    // be in the views' finalizers, but giving views finalizers means they
-    // cannot be swept in the background. This results in a very high
-    // performance cost.  Instead, ArrayBufferObjects with a single view hold a
-    // strong pointer to the view. This can entrain garbage when the single
-    // view becomes otherwise unreachable while the buffer is still live, but
-    // this is expected to be rare. ArrayBufferObjects with 0-1 views are
-    // expected to be by far the most common cases. ArrayBufferObjects with
-    // multiple views are collected into a linked list during collection, and
-    // then swept to prune out their dead views.
-
-    ArrayBufferObject &buffer = AsArrayBuffer(obj);
-    ArrayBufferViewObject *viewsHead = buffer.viewList();
-    if (!viewsHead)
-        return;
-
-    ArrayBufferViewObject *tmp = viewsHead;
-    buffer.setViewList(UpdateObjectIfRelocated(rt, &tmp));
-
-    if (tmp->nextView() == nullptr) {
-        // Single view: mark it, but only if we're actually doing a GC pass
-        // right now. Otherwise, the tracing pass for barrier verification will
-        // fail if we add another view and the pointer becomes weak.
-        MarkObjectUnbarriered(trc, &viewsHead, "arraybuffer.singleview");
-        buffer.setViewListNoBarrier(viewsHead);
-    } else if (!rt->isHeapCompacting()) {
-        // Multiple views: do not mark, but append buffer to list.
-        ArrayBufferVector &gcLiveArrayBuffers = buffer.compartment()->gcLiveArrayBuffers;
-
-        // obj_trace may be called multiple times before sweep(), so avoid
-        // adding this buffer to the list multiple times.
-        if (buffer.inLiveList()) {
-#ifdef DEBUG
-            bool found = false;
-            for (size_t i = 0; i < gcLiveArrayBuffers.length(); i++)
-                found |= gcLiveArrayBuffers[i] == &buffer;
-            JS_ASSERT(found);
-#endif
-        } else if (gcLiveArrayBuffers.append(&buffer)) {
-            buffer.setInLiveList(true);
-        } else {
-            CrashAtUnhandlableOOM("OOM while updating live array buffers");
-        }
-    } else {
-        // If we're fixing up pointers after compacting then trace everything.
-        ArrayBufferViewObject *prev = nullptr;
-        ArrayBufferViewObject *view = viewsHead;
-        while (view) {
-            JS_ASSERT(buffer.compartment() == MaybeForwarded(view)->compartment());
-            MarkObjectUnbarriered(trc, &view, "arraybuffer.singleview");
-            if (prev)
-                prev->setNextView(view);
-            else
-                buffer.setViewListNoBarrier(view);
-            view = view->nextView();
-        }
-    }
-}
-
-/* static */ void
-ArrayBufferObject::sweep(JSCompartment *compartment)
-{
-    JSRuntime *rt = compartment->runtimeFromMainThread();
-    ArrayBufferVector &gcLiveArrayBuffers = compartment->gcLiveArrayBuffers;
-
-    for (size_t i = 0; i < gcLiveArrayBuffers.length(); i++) {
-        ArrayBufferObject *buffer = gcLiveArrayBuffers[i];
-
-        JS_ASSERT(buffer->inLiveList());
-        buffer->setInLiveList(false);
-
-        ArrayBufferViewObject *viewsHead = buffer->viewList();
-        JS_ASSERT(viewsHead);
-        buffer->setViewList(UpdateObjectIfRelocated(rt, &viewsHead));
-
-        // Rebuild the list of views of the ArrayBufferObject, discarding dead
-        // views.  If there is only one view, it will have already been marked.
-        ArrayBufferViewObject *prevLiveView = nullptr;
-        ArrayBufferViewObject *view = viewsHead;
-        while (view) {
-            JS_ASSERT(buffer->compartment() == view->compartment());
-            ArrayBufferViewObject *nextView = view->nextView();
-            if (!IsObjectAboutToBeFinalized(&view)) {
-                view->setNextView(prevLiveView);
-                prevLiveView = view;
-            }
-            view = UpdateObjectIfRelocated(rt, &nextView);
-        }
-
-        buffer->setViewList(prevLiveView);
-    }
-
-    gcLiveArrayBuffers.clear();
-}
-
-/* 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.
     const size_t reservedSlots = JSCLASS_RESERVED_SLOTS(&ArrayBufferObject::class_);
     if (src.dataPointer() == src.fixedData(reservedSlots))
         dst.setSlot(DATA_SLOT, PrivateValue(dst.fixedData(reservedSlots)));
 }
 
+ArrayBufferViewObject *
+ArrayBufferObject::firstView()
+{
+    return getSlot(FIRST_VIEW_SLOT).isObject()
+           ? &getSlot(FIRST_VIEW_SLOT).toObject().as<ArrayBufferViewObject>()
+           : nullptr;
+}
+
 void
-ArrayBufferObject::resetArrayBufferList(JSCompartment *comp)
+ArrayBufferObject::setFirstView(ArrayBufferViewObject *view)
+{
+    setSlot(FIRST_VIEW_SLOT, ObjectOrNullValue(view));
+}
+
+bool
+ArrayBufferObject::addView(JSContext *cx, ArrayBufferViewObject *view)
 {
-    ArrayBufferVector &gcLiveArrayBuffers = comp->gcLiveArrayBuffers;
+    if (!firstView()) {
+        setFirstView(view);
+        return true;
+    }
+    return cx->compartment()->innerViews.addView(cx, this, view);
+}
+
+/*
+ * InnerViewTable
+ */
+
+static size_t VIEW_LIST_MAX_LENGTH = 500;
 
-    for (size_t i = 0; i < gcLiveArrayBuffers.length(); i++) {
-        ArrayBufferObject *buffer = gcLiveArrayBuffers[i];
+bool
+InnerViewTable::addView(JSContext *cx, ArrayBufferObject *obj, ArrayBufferViewObject *view)
+{
+    // ArrayBufferObject entries are only added when there are multiple views.
+    JS_ASSERT(obj->firstView());
+
+    if (!map.initialized() && !map.init())
+        return false;
+
+    Map::AddPtr p = map.lookupForAdd(obj);
+
+    JS_ASSERT(!gc::IsInsideNursery(obj));
+    bool addToNursery = nurseryKeysValid && gc::IsInsideNursery(view);
+
+    if (p) {
+        ViewVector &views = p->value();
+        JS_ASSERT(!views.empty());
 
-        JS_ASSERT(buffer->inLiveList());
-        buffer->setInLiveList(false);
+        if (addToNursery) {
+            // Only add the entry to |nurseryKeys| if it isn't already there.
+            if (views.length() >= VIEW_LIST_MAX_LENGTH) {
+                // To avoid quadratic blowup, skip the loop below if we end up
+                // adding enormous numbers of views for the same object.
+                nurseryKeysValid = false;
+            } else {
+                for (size_t i = 0; i < views.length(); i++) {
+                    if (gc::IsInsideNursery(views[i]))
+                        addToNursery = false;
+                }
+            }
+        }
+
+        if (!views.append(view))
+            return false;
+    } else {
+        if (!map.add(p, obj, ViewVector()))
+            return false;
+        JS_ALWAYS_TRUE(p->value().append(view));
     }
 
-    gcLiveArrayBuffers.clear();
-}
-
-/* static */ bool
-ArrayBufferObject::saveArrayBufferList(JSCompartment *comp, ArrayBufferVector &vector)
-{
-    const ArrayBufferVector &gcLiveArrayBuffers = comp->gcLiveArrayBuffers;
-
-    for (size_t i = 0; i < gcLiveArrayBuffers.length(); i++) {
-        if (!vector.append(gcLiveArrayBuffers[i]))
-            return false;
-    }
+    if (addToNursery && !nurseryKeys.append(obj))
+        nurseryKeysValid = false;
 
     return true;
 }
 
-/* static */ void
-ArrayBufferObject::restoreArrayBufferLists(ArrayBufferVector &vector)
+InnerViewTable::ViewVector *
+InnerViewTable::maybeViewsUnbarriered(ArrayBufferObject *obj)
+{
+    if (!map.initialized())
+        return nullptr;
+
+    Map::Ptr p = map.lookup(obj);
+    if (p)
+        return &p->value();
+    return nullptr;
+}
+
+void
+InnerViewTable::removeViews(ArrayBufferObject *obj)
 {
-    for (size_t i = 0; i < vector.length(); i++) {
-        ArrayBufferObject *buffer = vector[i];
+    Map::Ptr p = map.lookup(obj);
+    JS_ASSERT(p);
+
+    map.remove(p);
+}
+
+bool
+InnerViewTable::sweepEntry(JSObject **pkey, ViewVector &views)
+{
+    if (IsObjectAboutToBeFinalized(pkey))
+        return true;
+
+    JS_ASSERT(!views.empty());
+    for (size_t i = 0; i < views.length(); i++) {
+        if (IsObjectAboutToBeFinalized(&views[i])) {
+            views[i--] = views.back();
+            views.popBack();
+        }
+    }
+
+    return views.empty();
+}
+
+void
+InnerViewTable::sweep(JSRuntime *rt)
+{
+    JS_ASSERT(nurseryKeys.empty());
+
+    if (!map.initialized())
+        return;
 
-        JS_ASSERT(!buffer->inLiveList());
-        buffer->setInLiveList(true);
+    for (Map::Enum e(map); !e.empty(); e.popFront()) {
+        JSObject *key = e.front().key();
+        if (sweepEntry(&key, e.front().value()))
+            e.removeFront();
+        else if (key != e.front().key())
+            e.rekeyFront(key);
+    }
+}
+
+void
+InnerViewTable::sweepAfterMinorGC(JSRuntime *rt)
+{
+    JS_ASSERT(!nurseryKeys.empty());
+
+    if (nurseryKeysValid) {
+        for (size_t i = 0; i < nurseryKeys.length(); i++) {
+            JSObject *key = nurseryKeys[i];
+            Map::Ptr p = map.lookup(key);
+            if (!p)
+                continue;
 
-        buffer->compartment()->gcLiveArrayBuffers.infallibleAppend(buffer);
+            if (sweepEntry(&key, p->value()))
+                map.remove(nurseryKeys[i]);
+            else
+                map.rekeyIfMoved(nurseryKeys[i], key);
+        }
+        nurseryKeys.clear();
+    } else {
+        // Do the required sweeping by looking at every map entry.
+        nurseryKeys.clear();
+        sweep(rt);
+
+        nurseryKeysValid = true;
     }
 }
 
+size_t
+InnerViewTable::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
+{
+    if (!map.initialized())
+        return 0;
+
+    size_t vectorSize = 0;
+    for (Map::Enum e(map); !e.empty(); e.popFront())
+        vectorSize += e.front().value().sizeOfExcludingThis(mallocSizeOf);
+
+    return vectorSize
+         + map.sizeOfExcludingThis(mallocSizeOf)
+         + nurseryKeys.sizeOfExcludingThis(mallocSizeOf);
+}
+
 /*
  * ArrayBufferViewObject
  */
 
-/*
- * This method is used to trace TypedArrayObjects and DataViewObjects. We need
- * a custom tracer because some of an ArrayBufferViewObject's reserved slots
- * are weak references, and some need to be updated specially during moving
- * GCs.
- */
-/* static */ void
-ArrayBufferViewObject::trace(JSTracer *trc, JSObject *obj)
+template <>
+bool
+JSObject::is<js::ArrayBufferViewObject>() const
 {
-    HeapSlot &bufSlot = obj->getReservedSlotRef(BUFFER_SLOT);
-    MarkSlot(trc, &bufSlot, "typedarray.buffer");
-
-    // Update obj's data pointer if the array buffer moved. Note that during
-    // initialization, bufSlot may still contain |undefined|.
-    if (bufSlot.isObject()) {
-        ArrayBufferObject &buf = AsArrayBuffer(MaybeForwarded(&bufSlot.toObject()));
-        int32_t offset = obj->getReservedSlot(BYTEOFFSET_SLOT).toInt32();
-        MOZ_ASSERT(buf.dataPointer() != nullptr);
-        obj->initPrivate(buf.dataPointer() + offset);
-    }
-
-    /* Update NEXT_VIEW_SLOT, if the view moved. */
-    IsSlotMarked(&obj->getReservedSlotRef(NEXT_VIEW_SLOT));
+    return is<DataViewObject>() || is<TypedArrayObject>() || is<TypedObject>();
 }
 
 void
 ArrayBufferViewObject::neuter(void *newData)
 {
     MOZ_ASSERT(newData != nullptr);
     if (is<DataViewObject>())
         as<DataViewObject>().neuter(newData);
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -52,18 +52,16 @@ class ArrayBufferViewObject;
 //
 // Most APIs will only accept ArrayBufferObject.  ArrayBufferObjectMaybeShared exists
 // as a join point to allow APIs that can take or use either, notably AsmJS.
 //
 // As ArrayBufferObject and SharedArrayBufferObject are separated, so are the
 // TypedArray hierarchies below the two.  However, the TypedArrays have the
 // same layout (see TypedArrayObject.h), so there is little code duplication.
 
-typedef Vector<ArrayBufferObject *, 0, SystemAllocPolicy> ArrayBufferVector;
-
 class ArrayBufferObjectMaybeShared;
 
 uint32_t AnyArrayBufferByteLength(const ArrayBufferObjectMaybeShared *buf);
 uint8_t *AnyArrayBufferDataPointer(const ArrayBufferObjectMaybeShared *buf);
 ArrayBufferObjectMaybeShared &AsAnyArrayBuffer(HandleValue val);
 
 class ArrayBufferObjectMaybeShared : public JSObject
 {
@@ -92,17 +90,17 @@ class ArrayBufferObjectMaybeShared : pub
 class ArrayBufferObject : public ArrayBufferObjectMaybeShared
 {
     static bool byteLengthGetterImpl(JSContext *cx, CallArgs args);
     static bool fun_slice_impl(JSContext *cx, CallArgs args);
 
   public:
     static const uint8_t DATA_SLOT = 0;
     static const uint8_t BYTE_LENGTH_SLOT = 1;
-    static const uint8_t VIEW_LIST_SLOT = 2;
+    static const uint8_t FIRST_VIEW_SLOT = 2;
     static const uint8_t FLAGS_SLOT = 3;
 
     static const uint8_t RESERVED_SLOTS = 4;
 
     static const size_t ARRAY_BUFFER_ALIGNMENT = 8;
 
   public:
 
@@ -114,26 +112,23 @@ class ArrayBufferObject : public ArrayBu
     };
 
   protected:
 
     enum ArrayBufferFlags {
         // The flags also store the BufferKind
         BUFFER_KIND_MASK    = BufferKind::KIND_MASK,
 
-        NEUTERED_BUFFER     = 0x8,
-
-        // In the gcLiveArrayBuffers list.
-        IN_LIVE_LIST        =  0x10,
+        NEUTERED_BUFFER     = 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.
-        OWNS_DATA           =  0x20,
+        OWNS_DATA           = 0x8,
     };
 
   public:
 
     class BufferContents {
         uint8_t *data_;
         BufferKind kind_;
 
@@ -192,26 +187,18 @@ class ArrayBufferObject : public ArrayBu
     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 obj_trace(JSTracer *trc, JSObject *obj);
-
-    static void sweep(JSCompartment *rt);
-
     static void objectMoved(JSObject *obj, const JSObject *old);
 
-    static void resetArrayBufferList(JSCompartment *rt);
-    static bool saveArrayBufferList(JSCompartment *c, ArrayBufferVector &vector);
-    static void restoreArrayBufferLists(ArrayBufferVector &vector);
-
     static BufferContents stealContents(JSContext *cx, Handle<ArrayBufferObject*> buffer);
 
     bool hasStealableContents() const {
         // Inline elements strictly adhere to the corresponding buffer.
         if (!ownsData())
             return false;
 
         // asm.js buffer contents are transferred by copying, just like inline
@@ -225,32 +212,47 @@ class ArrayBufferObject : public ArrayBu
         // allocate new ones based on the current byteLength, which is 0 for a
         // neutered array -- not the original byteLength.
         return !isNeutered();
     }
 
     static void addSizeOfExcludingThis(JSObject *obj, mozilla::MallocSizeOf mallocSizeOf,
                                        JS::ClassInfo *info);
 
-    void addView(ArrayBufferViewObject *view);
+    // 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.
+    ArrayBufferViewObject *firstView();
+
+    bool addView(JSContext *cx, ArrayBufferViewObject *view);
 
     void setNewOwnedData(FreeOp* fop, BufferContents newContents);
     void changeContents(JSContext *cx, BufferContents newContents);
 
     /*
      * Ensure data is not stored inline in the object. Used when handing back a
      * GC-safe pointer.
      */
     static bool ensureNonInline(JSContext *cx, Handle<ArrayBufferObject*> buffer);
 
     bool canNeuter(JSContext *cx);
 
     /* Neuter this buffer and all its views. */
     static void neuter(JSContext *cx, Handle<ArrayBufferObject*> buffer, BufferContents newContents);
 
+  private:
+    void neuterView(JSContext *cx, ArrayBufferViewObject *view,
+                    BufferContents newContents);
+    void changeViewContents(JSContext *cx, ArrayBufferViewObject *view,
+                            uint8_t *oldDataPointer, BufferContents newContents);
+    void setFirstView(ArrayBufferViewObject *view);
+
+  public:
     uint8_t *dataPointer() const;
     size_t byteLength() const;
     BufferContents contents() const {
         return BufferContents(dataPointer(), bufferKind());
     }
 
     void releaseData(FreeOp *fop);
 
@@ -286,41 +288,32 @@ class ArrayBufferObject : public ArrayBu
     enum OwnsState {
         DoesntOwnData = 0,
         OwnsData = 1,
     };
 
     void setDataPointer(BufferContents contents, OwnsState ownsState);
     void setByteLength(size_t length);
 
-    ArrayBufferViewObject *viewList() const;
-    void setViewList(ArrayBufferViewObject *viewsHead);
-    void setViewListNoBarrier(ArrayBufferViewObject *viewsHead);
-
     uint32_t flags() const;
     void setFlags(uint32_t flags);
 
-    bool inLiveList() const { return flags() & IN_LIVE_LIST; }
-    void setInLiveList(bool value) {
-        setFlags(value ? (flags() | IN_LIVE_LIST) : (flags() & ~IN_LIVE_LIST));
-    }
-
     bool ownsData() const { return flags() & OWNS_DATA; }
     void setOwnsData(OwnsState owns) {
         setFlags(owns ? (flags() | OWNS_DATA) : (flags() & ~OWNS_DATA));
     }
 
     void setIsAsmJSArrayBuffer() { setFlags(flags() | ASMJS_BUFFER); }
     void setIsMappedArrayBuffer() { setFlags(flags() | MAPPED_BUFFER); }
     void setIsNeutered() { setFlags(flags() | NEUTERED_BUFFER); }
 
     void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState) {
         setByteLength(byteLength);
         setFlags(0);
-        setViewListNoBarrier(nullptr);
+        setFirstView(nullptr);
         setDataPointer(contents, ownsState);
     }
 
     void releaseAsmJSArray(FreeOp *fop);
     void releaseAsmJSArrayNoSignals(FreeOp *fop);
     void releaseMappedArray();
 };
 
@@ -337,32 +330,21 @@ class ArrayBufferViewObject : public JSO
     static const size_t BYTEOFFSET_SLOT  = JS_BUFVIEW_SLOT_BYTEOFFSET;
 
     /* Byte length of view */
     static const size_t LENGTH_SLOT      = JS_BUFVIEW_SLOT_LENGTH;
 
     /* Underlying ArrayBufferObject */
     static const size_t BUFFER_SLOT      = JS_BUFVIEW_SLOT_OWNER;
 
-    /* ArrayBufferObjects point to a linked list of views, chained through this slot */
-    static const size_t NEXT_VIEW_SLOT   = JS_BUFVIEW_SLOT_NEXT_VIEW;
-
   public:
     static ArrayBufferObject *bufferObject(JSContext *cx, Handle<ArrayBufferViewObject *> obj);
 
-    ArrayBufferViewObject *nextView() const {
-        return static_cast<ArrayBufferViewObject*>(getFixedSlot(NEXT_VIEW_SLOT).toPrivate());
-    }
-
-    inline void setNextView(ArrayBufferViewObject *view);
-
     void neuter(void *newData);
 
-    static void trace(JSTracer *trc, JSObject *obj);
-
     uint8_t *dataPointer() {
         return static_cast<uint8_t *>(getPrivate());
     }
 };
 
 bool
 ToClampedIndex(JSContext *cx, HandleValue v, uint32_t length, uint32_t *out);
 
@@ -395,23 +377,16 @@ InitArrayBufferViewDataPointer(ArrayBuff
  * Tests for ArrayBufferObject, like obj->is<ArrayBufferObject>().
  */
 bool IsArrayBuffer(HandleValue v);
 bool IsArrayBuffer(HandleObject obj);
 bool IsArrayBuffer(JSObject *obj);
 ArrayBufferObject &AsArrayBuffer(HandleObject obj);
 ArrayBufferObject &AsArrayBuffer(JSObject *obj);
 
-inline void
-ArrayBufferViewObject::setNextView(ArrayBufferViewObject *view)
-{
-    setFixedSlot(NEXT_VIEW_SLOT, PrivateValue(view));
-    PostBarrierTypedArrayObject(this);
-}
-
 extern uint32_t JS_FASTCALL
 ClampDoubleToUint8(const double x);
 
 struct uint8_clamped {
     uint8_t val;
 
     uint8_clamped() { }
     uint8_clamped(const uint8_clamped& other) : val(other.val) { }
@@ -488,11 +463,65 @@ template<typename T> inline bool TypeIsF
 template<> inline bool TypeIsFloatingPoint<float>() { return true; }
 template<> inline bool TypeIsFloatingPoint<double>() { return true; }
 
 template<typename T> inline bool TypeIsUnsigned() { return false; }
 template<> inline bool TypeIsUnsigned<uint8_t>() { return true; }
 template<> inline bool TypeIsUnsigned<uint16_t>() { return true; }
 template<> inline bool TypeIsUnsigned<uint32_t>() { return true; }
 
+// Per-compartment table that manages the relationship between array buffers
+// and the views that use their storage.
+class InnerViewTable
+{
+  public:
+    typedef Vector<ArrayBufferViewObject *, 1, SystemAllocPolicy> ViewVector;
+
+    friend class ArrayBufferObject;
+
+  private:
+    typedef HashMap<JSObject *,
+                    ViewVector,
+                    DefaultHasher<JSObject *>,
+                    SystemAllocPolicy> Map;
+
+    // For all objects sharing their storage with some other view, this maps
+    // the object to the list of such views. All entries in this map are weak.
+    Map map;
+
+    // List of keys from innerViews where either the source or at least one
+    // target is in the nursery.
+    Vector<JSObject *, 0, SystemAllocPolicy> nurseryKeys;
+
+    // Whether nurseryKeys is a complete list.
+    bool nurseryKeysValid;
+
+    // Sweep an entry during GC, returning whether the entry should be removed.
+    bool sweepEntry(JSObject **pkey, ViewVector &views);
+
+    bool addView(JSContext *cx, ArrayBufferObject *obj, ArrayBufferViewObject *view);
+    ViewVector *maybeViewsUnbarriered(ArrayBufferObject *obj);
+    void removeViews(ArrayBufferObject *obj);
+
+  public:
+    InnerViewTable()
+      : nurseryKeysValid(true)
+    {}
+
+    // Remove references to dead objects in the table and update table entries
+    // to reflect moved objects.
+    void sweep(JSRuntime *rt);
+    void sweepAfterMinorGC(JSRuntime *rt);
+
+    bool needsSweepAfterMinorGC() {
+        return !nurseryKeys.empty() || !nurseryKeysValid;
+    }
+
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
+};
+
 } // namespace js
 
+template <>
+bool
+JSObject::is<js::ArrayBufferViewObject>() const;
+
 #endif // vm_ArrayBufferObject_h
--- a/js/src/vm/MemoryMetrics.cpp
+++ b/js/src/vm/MemoryMetrics.cpp
@@ -331,16 +331,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.crossCompartmentWrappersTable,
                                         &cStats.regexpCompartment,
                                         &cStats.savedStacksSet);
 }
 
 static void
 StatsArenaCallback(JSRuntime *rt, void *data, gc::Arena *arena,
                    JSGCTraceKind traceKind, size_t thingSize)
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -102,17 +102,18 @@ TypedArrayObject::ensureHasBuffer(JSCont
 {
     if (tarray->buffer())
         return true;
 
     Rooted<ArrayBufferObject *> buffer(cx, ArrayBufferObject::create(cx, tarray->byteLength()));
     if (!buffer)
         return false;
 
-    buffer->addView(tarray);
+    if (!buffer->addView(cx, tarray))
+        return false;
 
     memcpy(buffer->dataPointer(), tarray->viewData(), tarray->byteLength());
     InitArrayBufferViewDataPointer(tarray, buffer, 0);
 
     tarray->setSlot(BUFFER_SLOT, ObjectValue(*buffer));
     return true;
 }
 
@@ -317,34 +318,35 @@ class TypedArrayObjectTemplate : public 
         } else {
             void *data = obj->fixedData(FIXED_DATA_START);
             obj->initPrivate(data);
             memset(data, 0, len * sizeof(NativeType));
         }
 
         obj->setSlot(LENGTH_SLOT, Int32Value(len));
         obj->setSlot(BYTEOFFSET_SLOT, Int32Value(byteOffset));
-        obj->setSlot(NEXT_VIEW_SLOT, PrivateValue(nullptr));
 
 #ifdef DEBUG
         if (buffer) {
             uint32_t arrayByteLength = obj->byteLength();
             uint32_t arrayByteOffset = obj->byteOffset();
             uint32_t bufferByteLength = buffer->byteLength();
             JS_ASSERT_IF(!buffer->isNeutered(), buffer->dataPointer() <= obj->viewData());
             JS_ASSERT(bufferByteLength - arrayByteOffset >= arrayByteLength);
             JS_ASSERT(arrayByteOffset <= bufferByteLength);
         }
 
         // Verify that the private slot is at the expected place
         JS_ASSERT(obj->numFixedSlots() == DATA_SLOT);
 #endif
 
-        if (buffer)
-            buffer->addView(obj);
+        if (buffer) {
+            if (!buffer->addView(cx, obj))
+                return nullptr;
+        }
 
         return obj;
     }
 
     static JSObject *
     makeInstance(JSContext *cx, Handle<ArrayBufferObject *> bufobj, uint32_t byteOffset, uint32_t len)
     {
         RootedObject nullproto(cx, nullptr);
@@ -897,24 +899,24 @@ DataViewObject::create(JSContext *cx, ui
                 return nullptr;
         }
     }
 
     DataViewObject &dvobj = obj->as<DataViewObject>();
     dvobj.setFixedSlot(BYTEOFFSET_SLOT, Int32Value(byteOffset));
     dvobj.setFixedSlot(LENGTH_SLOT, Int32Value(byteLength));
     dvobj.setFixedSlot(BUFFER_SLOT, ObjectValue(*arrayBuffer));
-    dvobj.setFixedSlot(NEXT_VIEW_SLOT, PrivateValue(nullptr));
     InitArrayBufferViewDataPointer(&dvobj, arrayBuffer, byteOffset);
     JS_ASSERT(byteOffset + byteLength <= arrayBuffer->byteLength());
 
     // Verify that the private slot is at the expected place
     JS_ASSERT(dvobj.numFixedSlots() == DATA_SLOT);
 
-    arrayBuffer->addView(&dvobj);
+    if (!arrayBuffer->addView(cx, &dvobj))
+        return nullptr;
 
     return &dvobj;
 }
 
 bool
 DataViewObject::construct(JSContext *cx, JSObject *bufobj, const CallArgs &args, HandleObject proto)
 {
     if (!IsArrayBuffer(bufobj)) {
@@ -1742,17 +1744,17 @@ IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Flo
     JS_StrictPropertyStub,   /* setProperty */                                 \
     JS_EnumerateStub,                                                          \
     JS_ResolveStub,                                                            \
     JS_ConvertStub,                                                            \
     nullptr,                 /* finalize    */                                 \
     nullptr,                 /* call        */                                 \
     nullptr,                 /* hasInstance */                                 \
     nullptr,                 /* construct   */                                 \
-    ArrayBufferViewObject::trace, /* trace  */                                 \
+    nullptr,                 /* trace  */                                      \
     TYPED_ARRAY_CLASS_SPEC(_typedArray),                                       \
     {                                                                          \
         nullptr,             /* outerObject */                                 \
         nullptr,             /* innerObject */                                 \
         nullptr,             /* iteratorObject */                              \
         false,               /* isWrappedNative */                             \
         nullptr,             /* weakmapKeyDelegateOp */                        \
         TypedArrayObject::ObjectMoved                                          \
@@ -1924,17 +1926,17 @@ const Class DataViewObject::class_ = {
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub,
     nullptr,                 /* finalize */
     nullptr,                 /* call        */
     nullptr,                 /* hasInstance */
     nullptr,                 /* construct   */
-    ArrayBufferViewObject::trace, /* trace  */
+    nullptr,                 /* trace       */
 };
 
 const JSFunctionSpec DataViewObject::jsfuncs[] = {
     JS_FN("getInt8",    DataViewObject::fun_getInt8,      1,0),
     JS_FN("getUint8",   DataViewObject::fun_getUint8,     1,0),
     JS_FN("getInt16",   DataViewObject::fun_getInt16,     2,0),
     JS_FN("getUint16",  DataViewObject::fun_getUint16,    2,0),
     JS_FN("getInt32",   DataViewObject::fun_getInt32,     2,0),
--- a/js/src/vm/TypedArrayObject.h
+++ b/js/src/vm/TypedArrayObject.h
@@ -406,16 +406,9 @@ ClampIntForUint8Array(int32_t x)
 
 template <>
 inline bool
 JSObject::is<js::TypedArrayObject>() const
 {
     return js::IsTypedArrayClass(getClass());
 }
 
-template <>
-inline bool
-JSObject::is<js::ArrayBufferViewObject>() const
-{
-    return is<js::DataViewObject>() || is<js::TypedArrayObject>();
-}
-
 #endif /* vm_TypedArrayObject_h */
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2224,16 +2224,20 @@ ReportCompartmentStats(const JS::Compart
     ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("compartment-object"),
         cStats.compartmentObject,
         "The JSCompartment object itself.");
 
     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("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.");