Bug 987508 - Create array buffers lazily for small typed arrays, r=sfink.
☠☠ backed out by 8b87a6adad14 ☠ ☠
authorBrian Hackett <bhackett1024@gmail.com>
Mon, 07 Apr 2014 11:46:54 -0700
changeset 177356 e35851f07b6703bee6830b4ebcd2990f41629238
parent 177355 14b5fbfa2163b634fcdad1249441e0fb789dc0ef
child 177357 aff859f63f703e6e450e81f539c8c70e18bdfa4f
push id6188
push userryanvm@gmail.com
push dateTue, 08 Apr 2014 02:36:53 +0000
treeherderfx-team@80ed52d37cfe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssfink
bugs987508
milestone31.0a1
Bug 987508 - Create array buffers lazily for small typed arrays, r=sfink.
content/media/webaudio/AudioBuffer.cpp
js/src/gc/Nursery.cpp
js/src/jsapi-tests/testTypedArrays.cpp
js/src/jsfriendapi.h
js/src/jsobj.cpp
js/src/jsobjinlines.h
js/src/vm/ArrayBufferObject.cpp
js/src/vm/ArrayBufferObject.h
js/src/vm/ObjectImpl-inl.h
js/src/vm/ObjectImpl.h
js/src/vm/StructuredClone.cpp
js/src/vm/TypedArrayObject.cpp
js/src/vm/TypedArrayObject.h
--- a/content/media/webaudio/AudioBuffer.cpp
+++ b/content/media/webaudio/AudioBuffer.cpp
@@ -192,17 +192,17 @@ AudioBuffer::GetChannelData(JSContext* a
 static already_AddRefed<ThreadSharedFloatArrayBufferList>
 StealJSArrayDataIntoThreadSharedFloatArrayBufferList(JSContext* aJSContext,
                                                      const nsTArray<JSObject*>& aJSArrays)
 {
   nsRefPtr<ThreadSharedFloatArrayBufferList> result =
     new ThreadSharedFloatArrayBufferList(aJSArrays.Length());
   for (uint32_t i = 0; i < aJSArrays.Length(); ++i) {
     JS::Rooted<JSObject*> arrayBuffer(aJSContext,
-                                      JS_GetArrayBufferViewBuffer(aJSArrays[i]));
+                                      JS_GetArrayBufferViewBuffer(aJSContext, aJSArrays[i]));
     uint8_t* stolenData = arrayBuffer
                           ? (uint8_t*) JS_StealArrayBufferContents(aJSContext, arrayBuffer)
                           : nullptr;
     if (stolenData) {
       result->SetData(i, stolenData, reinterpret_cast<float*>(stolenData));
     } else {
       return nullptr;
     }
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -360,16 +360,25 @@ GetObjectAllocKindForCopy(JSRuntime *rt,
 
         size_t nelements = obj->getDenseCapacity();
         return GetBackgroundAllocKind(GetGCArrayKind(nelements));
     }
 
     if (obj->is<JSFunction>())
         return obj->as<JSFunction>().getAllocKind();
 
+    /*
+     * Typed arrays in the nursery may have a lazily allocated buffer, make
+     * sure there is room for the array's fixed data when moving the array.
+     */
+    if (obj->is<TypedArrayObject>() && !obj->as<TypedArrayObject>().buffer()) {
+        size_t nbytes = obj->as<TypedArrayObject>().byteLength();
+        return GetBackgroundAllocKind(TypedArrayObject::AllocKindForLazyBuffer(nbytes));
+    }
+
     AllocKind kind = GetGCObjectFixedSlotsKind(obj->numFixedSlots());
     JS_ASSERT(!IsBackgroundFinalized(kind));
     JS_ASSERT(CanBeFinalizedInBackground(kind, obj->getClass()));
     return GetBackgroundAllocKind(kind);
 }
 
 void *
 js::Nursery::allocateFromTenured(Zone *zone, AllocKind thingKind)
@@ -556,16 +565,19 @@ js::Nursery::moveObjectToTenured(JSObjec
      */
     if (src->is<ArrayObject>())
         srcSize = sizeof(ObjectImpl);
 
     js_memcpy(dst, src, srcSize);
     tenuredSize += moveSlotsToTenured(dst, src, dstKind);
     tenuredSize += moveElementsToTenured(dst, src, dstKind);
 
+    if (src->is<TypedArrayObject>())
+        dst->setPrivate(dst->fixedData(TypedArrayObject::FIXED_DATA_START));
+
     /* The shape's list head may point into the old object. */
     if (&src->shape_ == dst->shape_->listp)
         dst->shape_->listp = &dst->shape_;
 
     return tenuredSize;
 }
 
 size_t
--- a/js/src/jsapi-tests/testTypedArrays.cpp
+++ b/js/src/jsapi-tests/testTypedArrays.cpp
@@ -104,17 +104,17 @@ TestArrayFromBuffer(JSContext *cx)
         RootedObject notArray(cx, CreateWithBuffer(cx, buffer, UINT32_MAX, -1));
         CHECK(!notArray);
     }
 
     RootedObject array(cx, CreateWithBuffer(cx, buffer, 0, -1));
     CHECK_EQUAL(JS_GetTypedArrayLength(array), elts);
     CHECK_EQUAL(JS_GetTypedArrayByteOffset(array), 0);
     CHECK_EQUAL(JS_GetTypedArrayByteLength(array), nbytes);
-    CHECK_EQUAL(JS_GetArrayBufferViewBuffer(array), (JSObject*) buffer);
+    CHECK_EQUAL(JS_GetArrayBufferViewBuffer(cx, array), (JSObject*) buffer);
 
     Element *data;
     CHECK(data = GetData(array));
     CHECK(bufdata = JS_GetStableArrayBufferData(cx, buffer));
     CHECK_EQUAL((void*) data, (void*) bufdata);
 
     CHECK_EQUAL(*bufdata, 1);
     CHECK_EQUAL(*reinterpret_cast<uint8_t*>(data), 1);
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1427,17 +1427,17 @@ extern JS_FRIEND_API(void *)
 JS_GetArrayBufferViewData(JSObject *obj);
 
 /*
  * Return the ArrayBuffer underlying an ArrayBufferView. If the buffer has been
  * neutered, this will still return the neutered buffer. |obj| must be an
  * object that would return true for JS_IsArrayBufferViewObject().
  */
 extern JS_FRIEND_API(JSObject *)
-JS_GetArrayBufferViewBuffer(JSObject *obj);
+JS_GetArrayBufferViewBuffer(JSContext *cx, JSObject *obj);
 
 /*
  * Set an ArrayBuffer's length to 0 and neuter all of its views.
  */
 extern JS_FRIEND_API(bool)
 JS_NeuterArrayBuffer(JSContext *cx, JS::HandleObject obj);
 
 /*
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1248,27 +1248,22 @@ NewObject(ExclusiveContext *cx, types::T
     JS_ASSERT_IF(parent, &parent->global() == cx->global());
 
     RootedTypeObject type(cx, type_);
 
     JSObject *metadata = nullptr;
     if (!NewObjectMetadata(cx, &metadata))
         return nullptr;
 
-    // Normally, the number of fixed slots given an object is the maximum
-    // permitted for its size class. For array buffers we only use enough to
-    // cover the class reservd slots, so that the remaining space in the
-    // object's allocation is available for the buffer's data.
-    size_t nfixed;
-    if (clasp == &ArrayBufferObject::class_) {
-        JS_STATIC_ASSERT(ArrayBufferObject::RESERVED_SLOTS == 4);
-        nfixed = ArrayBufferObject::RESERVED_SLOTS;
-    } else {
-        nfixed = GetGCKindSlots(kind, clasp);
-    }
+    // For objects which can have fixed data following the object, only use
+    // enough fixed slots to cover the number of reserved slots in the object,
+    // regardless of the allocation kind specified.
+    size_t nfixed = ClassCanHaveFixedData(clasp)
+                    ? GetGCKindSlots(gc::GetGCObjectKind(clasp), clasp)
+                    : GetGCKindSlots(kind, clasp);
 
     RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, type->proto(),
                                                       parent, metadata, nfixed));
     if (!shape)
         return nullptr;
 
     gc::InitialHeap heap = GetInitialHeap(newKind, clasp);
     JSObject *obj = JSObject::create(cx, kind, heap, shape, type);
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -470,17 +470,18 @@ JSObject::setProto(JSContext *cx, JS::Ha
 
         if (!JSObject::getProto(cx, obj2, &obj2))
             return false;
     }
 
     return SetClassAndProto(cx, obj, obj->getClass(), proto, succeeded);
 }
 
-inline bool JSObject::isVarObj()
+inline bool
+JSObject::isVarObj()
 {
     if (is<js::DebugScopeObject>())
         return as<js::DebugScopeObject>().scope().isVarObj();
     return lastProperty()->hasObjectFlag(js::BaseShape::VAROBJ);
 }
 
 /* static */ inline JSObject *
 JSObject::create(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
@@ -490,17 +491,17 @@ JSObject::create(js::ExclusiveContext *c
     /*
      * Callers must use dynamicSlotsCount to size the initial slot array of the
      * object. We can't check the allocated capacity of the dynamic slots, but
      * make sure their presence is consistent with the shape.
      */
     JS_ASSERT(shape && type);
     JS_ASSERT(type->clasp() == shape->getObjectClass());
     JS_ASSERT(type->clasp() != &js::ArrayObject::class_);
-    JS_ASSERT_IF(type->clasp() != &js::ArrayBufferObject::class_,
+    JS_ASSERT_IF(!ClassCanHaveFixedData(type->clasp()),
                  js::gc::GetGCKindSlots(kind, type->clasp()) == shape->numFixedSlots());
     JS_ASSERT_IF(type->clasp()->flags & JSCLASS_BACKGROUND_FINALIZE, IsBackgroundFinalized(kind));
     JS_ASSERT_IF(type->clasp()->finalize, heap == js::gc::TenuredHeap);
     JS_ASSERT_IF(extantSlots, dynamicSlotsCount(shape->numFixedSlots(), shape->slotSpan(),
                                                 type->clasp()));
 
     const js::Class *clasp = type->clasp();
     size_t nDynamicSlots = 0;
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -626,17 +626,17 @@ ArrayBufferObject::create(JSContext *cx,
     }
     JS_ASSERT(obj->getClass() == &class_);
 
     JS_ASSERT(!gc::IsInsideNursery(cx->runtime(), obj));
 
     if (data) {
         obj->initialize(nbytes, data, OwnsData);
     } else {
-        void *data = &obj->fixedSlots()[reservedSlots];
+        void *data = obj->fixedData(reservedSlots);
         memset(data, 0, nbytes);
         obj->initialize(nbytes, data, DoesntOwnData);
     }
 
     return obj;
 }
 
 JSObject *
@@ -924,16 +924,27 @@ ArrayBufferViewObject::neuter(void *newD
     if (is<DataViewObject>())
         as<DataViewObject>().neuter(newData);
     else if (is<TypedArrayObject>())
         as<TypedArrayObject>().neuter(newData);
     else
         as<TypedObject>().neuter(newData);
 }
 
+/* static */ ArrayBufferObject *
+ArrayBufferViewObject::bufferObject(JSContext *cx, Handle<ArrayBufferViewObject *> thisObject)
+{
+    if (thisObject->is<TypedArrayObject>()) {
+        Rooted<TypedArrayObject *> typedArray(cx, &thisObject->as<TypedArrayObject>());
+        if (!TypedArrayObject::ensureHasBuffer(cx, typedArray))
+            return nullptr;
+    }
+    return &thisObject->getFixedSlot(BUFFER_SLOT).toObject().as<ArrayBufferObject>();
+}
+
 /* JS Friend API */
 
 JS_FRIEND_API(bool)
 JS_IsArrayBufferViewObject(JSObject *obj)
 {
     obj = CheckedUnwrap(obj);
     return obj ? obj->is<ArrayBufferViewObject>() : false;
 }
@@ -1042,22 +1053,23 @@ JS_GetArrayBufferViewData(JSObject *obj)
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
     return obj->is<DataViewObject>() ? obj->as<DataViewObject>().dataPointer()
                                      : obj->as<TypedArrayObject>().viewData();
 }
 
 JS_FRIEND_API(JSObject *)
-JS_GetArrayBufferViewBuffer(JSObject *obj)
+JS_GetArrayBufferViewBuffer(JSContext *cx, JSObject *obj)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
-    return obj->as<ArrayBufferViewObject>().bufferObject();
+    Rooted<ArrayBufferViewObject *> viewObject(cx, &obj->as<ArrayBufferViewObject>());
+    return ArrayBufferViewObject::bufferObject(cx, viewObject);
 }
 
 JS_FRIEND_API(uint32_t)
 JS_GetArrayBufferViewByteLength(JSObject *obj)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return 0;
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -235,19 +235,17 @@ class ArrayBufferViewObject : public JSO
 
     /* Underlying ArrayBufferObject */
     static const size_t BUFFER_SLOT      = JS_TYPEDOBJ_SLOT_OWNER;
 
     /* ArrayBufferObjects point to a linked list of views, chained through this slot */
     static const size_t NEXT_VIEW_SLOT   = JS_TYPEDOBJ_SLOT_NEXT_VIEW;
 
   public:
-    JSObject *bufferObject() const {
-        return &getFixedSlot(BUFFER_SLOT).toObject();
-    }
+    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);
--- a/js/src/vm/ObjectImpl-inl.h
+++ b/js/src/vm/ObjectImpl-inl.h
@@ -8,25 +8,48 @@
 #define vm_ObjectImpl_inl_h
 
 #include "vm/ObjectImpl.h"
 
 #include "jscntxt.h"
 #include "jsproxy.h"
 
 #include "vm/ProxyObject.h"
+#include "vm/TypedArrayObject.h"
+
+namespace js {
 
 /* static */ inline bool
-js::ObjectImpl::isExtensible(ExclusiveContext *cx, js::Handle<ObjectImpl*> obj, bool *extensible)
+ObjectImpl::isExtensible(ExclusiveContext *cx, Handle<ObjectImpl*> obj, bool *extensible)
 {
     if (obj->asObjectPtr()->is<ProxyObject>()) {
         if (!cx->shouldBeJSContext())
             return false;
         HandleObject h =
             HandleObject::fromMarkedLocation(reinterpret_cast<JSObject* const*>(obj.address()));
         return Proxy::isExtensible(cx->asJSContext(), h, extensible);
     }
 
     *extensible = obj->nonProxyIsExtensible();
     return true;
 }
 
+inline bool
+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 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 == &ArrayBufferObject::class_ || IsTypedArrayClass(clasp);
+}
+
+inline void *
+ObjectImpl::fixedData(size_t nslots) const
+{
+    JS_ASSERT(ClassCanHaveFixedData(getClass()));
+    JS_ASSERT(nslots == numFixedSlots() + (hasPrivate() ? 1 : 0));
+    return &fixedSlots()[nslots];
+}
+
+} // namespace js
+
 #endif /* vm_ObjectImpl_inl_h */
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -89,23 +89,22 @@ class ArrayObject;
  */
 template <ExecutionMode mode>
 extern bool
 ArraySetLength(typename ExecutionModeTraits<mode>::ContextType cx,
                Handle<ArrayObject*> obj, HandleId id,
                unsigned attrs, HandleValue value, bool setterIsStrict);
 
 /*
- * Elements header used for all objects other than non-native objects (except
- * for ArrayBufferObjects!!!) and typed arrays. The elements component of such
- * objects offers an efficient representation for all or some of the indexed
- * properties of the object, using a flat array of Values rather than a shape
- * hierarchy stored in the object's slots. This structure is immediately
- * followed by an array of elements, with the elements member in an object
- * pointing to the beginning of that array (the end of this structure).
+ * Elements header used for all objects. The elements component of such objects
+ * offers an efficient representation for all or some of the indexed properties
+ * of the object, using a flat array of Values rather than a shape hierarchy
+ * stored in the object's slots. This structure is immediately followed by an
+ * array of elements, with the elements member in an object pointing to the
+ * beginning of that array (the end of this structure).
  * See below for usage of this structure.
  *
  * The sets of properties represented by an object's elements and slots
  * are disjoint. The elements contain only indexed properties, while the slots
  * can contain both named and indexed properties; any indexes in the slots are
  * distinct from those in the elements. If isIndexed() is false for an object,
  * all indexed properties (if any) are stored in the dense elements.
  *
@@ -144,24 +143,24 @@ ArraySetLength(typename ExecutionModeTra
  *
  * The initialized length of an object specifies the number of elements that
  * have been initialized. All elements above the initialized length are
  * holes in the object, and the memory for all elements between the initialized
  * length and capacity is left uninitialized. The initialized length is some
  * value less than or equal to both the object's length and the object's
  * capacity.
  *
- * With inference enabled, there is flexibility in exactly the value the
- * initialized length must hold, e.g. if an array has length 5, capacity 10,
- * completely empty, it is valid for the initialized length to be any value
- * between zero and 5, as long as the in memory values below the initialized
- * length have been initialized with a hole value. However, in such cases we
- * want to keep the initialized length as small as possible: if the object is
- * known to have no hole values below its initialized length, then it is
- * "packed" and can be accessed much faster by JIT code.
+ * There is flexibility in exactly the value the initialized length must hold,
+ * e.g. if an array has length 5, capacity 10, completely empty, it is valid
+ * for the initialized length to be any value between zero and 5, as long as
+ * the in memory values below the initialized length have been initialized with
+ * a hole value. However, in such cases we want to keep the initialized length
+ * as small as possible: if the object is known to have no hole values below
+ * its initialized length, then it is "packed" and can be accessed much faster
+ * by JIT code.
  *
  * Elements do not track property creation order, so enumerating the elements
  * of an object does not necessarily visit indexes in the order they were
  * created.
  */
 class ObjectElements
 {
   public:
@@ -283,17 +282,17 @@ IsObjectValueInCompartment(js::Value v, 
  * The |type_| member stores the type of the object, which contains its
  * prototype object and the possible types of its properties.
  *
  * The rest of the object stores its named properties and indexed elements.
  * These are stored separately from one another. Objects are followed by a
  * variable-sized array of values for inline storage, which may be used by
  * either properties of native objects (fixed slots), by elements (fixed
  * elements), or by other data for certain kinds of objects, such as
- * ArrayBufferObjects.
+ * ArrayBufferObjects and TypedArrayObjects.
  *
  * Two native objects with the same shape are guaranteed to have the same
  * number of fixed slots.
  *
  * Named property storage can be split between fixed slots and a dynamically
  * allocated array (the slots member). For an object with N fixed slots, shapes
  * with slots [0..N-1] are stored in the fixed slots, and the remainder are
  * stored in the dynamic array. If all properties fit in the fixed slots, the
@@ -822,16 +821,23 @@ class ObjectImpl : public gc::BarrieredC
     inline bool hasFixedElements() const {
         return elements == fixedElements();
     }
 
     inline bool hasEmptyElements() const {
         return elements == emptyObjectElements;
     }
 
+    /*
+     * Get a pointer to the unused data in the object's allocation immediately
+     * following this object, for use with objects which allocate a larger size
+     * class than they need and store non-elements data inline.
+     */
+    inline void *fixedData(size_t nslots) const;
+
     /* GC support. */
     static ThingRootKind rootKind() { return THING_ROOT_OBJECT; }
 
     inline void privateWriteBarrierPre(void **oldval);
 
     void privateWriteBarrierPost(void **pprivate) {
 #ifdef JSGC_GENERATIONAL
         shadowRuntimeFromAnyThread()->gcStoreBufferPtr()->putCell(reinterpret_cast<js::gc::Cell **>(pprivate));
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -808,16 +808,20 @@ JSStructuredCloneWriter::checkStack()
  * Int16Array views of the same ArrayBuffer, should the data bytes be
  * byte-swapped when writing or not? The Int8Array requires them to not be
  * swapped; the Int16Array requires that they are.
  */
 bool
 JSStructuredCloneWriter::writeTypedArray(HandleObject obj)
 {
     Rooted<TypedArrayObject*> tarr(context(), &obj->as<TypedArrayObject>());
+
+    if (!TypedArrayObject::ensureHasBuffer(context(), tarr))
+        return false;
+
     if (!out.writePair(SCTAG_TYPED_ARRAY_OBJECT, tarr->length()))
         return false;
     uint64_t type = tarr->type();
     if (!out.write(type))
         return false;
 
     // Write out the ArrayBuffer tag and contents
     RootedValue val(context(), TypedArrayObject::bufferValue(tarr));
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -101,16 +101,35 @@ TypedArrayObject::neuter(void *newData)
 }
 
 ArrayBufferObject *
 TypedArrayObject::sharedBuffer() const
 {
     return &bufferValue(const_cast<TypedArrayObject*>(this)).toObject().as<SharedArrayBufferObject>();
 }
 
+/* static */ bool
+TypedArrayObject::ensureHasBuffer(JSContext *cx, Handle<TypedArrayObject *> tarray)
+{
+    if (tarray->buffer())
+        return true;
+
+    Rooted<ArrayBufferObject *> buffer(cx, ArrayBufferObject::create(cx, tarray->byteLength()));
+    if (!buffer)
+        return false;
+
+    buffer->addView(tarray);
+
+    memcpy(buffer->dataPointer(), tarray->viewData(), tarray->byteLength());
+    InitArrayBufferViewDataPointer(tarray, buffer, 0);
+
+    tarray->setSlot(BUFFER_SLOT, ObjectValue(*buffer));
+    return true;
+}
+
 /* static */ int
 TypedArrayObject::lengthOffset()
 {
     return JSObject::getFixedSlotOffset(LENGTH_SLOT);
 }
 
 /* static */ int
 TypedArrayObject::dataOffset()
@@ -182,23 +201,23 @@ class TypedArrayObjectTemplate : public 
 
     static const size_t BYTES_PER_ELEMENT = sizeof(ThisType);
 
     static inline const Class *protoClass()
     {
         return &TypedArrayObject::protoClasses[ArrayTypeID()];
     }
 
-    static inline const Class *fastClass()
+    static inline const Class *instanceClass()
     {
         return &TypedArrayObject::classes[ArrayTypeID()];
     }
 
     static bool is(HandleValue v) {
-        return v.isObject() && v.toObject().hasClass(fastClass());
+        return v.isObject() && v.toObject().hasClass(instanceClass());
     }
 
     static void
     setIndexValue(TypedArrayObject &tarray, uint32_t index, double d)
     {
         // If the array is an integer array, we only handle up to
         // 32-bit ints from this point on.  if we want to handle
         // 64-bit ints, we'll need some changes.
@@ -217,123 +236,128 @@ class TypedArrayObjectTemplate : public 
         } else {
             JS_ASSERT(sizeof(NativeType) <= 4);
             int32_t n = ToInt32(d);
             setIndex(tarray, index, NativeType(n));
         }
     }
 
     static TypedArrayObject *
-    makeProtoInstance(JSContext *cx, HandleObject proto)
+    makeProtoInstance(JSContext *cx, HandleObject proto, AllocKind allocKind)
     {
         JS_ASSERT(proto);
 
-        RootedObject obj(cx, NewBuiltinClassInstance(cx, fastClass()));
+        RootedObject obj(cx, NewBuiltinClassInstance(cx, instanceClass(), allocKind));
         if (!obj)
             return nullptr;
 
         types::TypeObject *type = cx->getNewType(obj->getClass(), proto.get());
         if (!type)
             return nullptr;
         obj->setType(type);
 
         return &obj->as<TypedArrayObject>();
     }
 
     static TypedArrayObject *
-    makeTypedInstance(JSContext *cx, uint32_t len)
+    makeTypedInstance(JSContext *cx, uint32_t len, AllocKind allocKind)
     {
         if (len * sizeof(NativeType) >= TypedArrayObject::SINGLETON_TYPE_BYTE_LENGTH) {
-            return &NewBuiltinClassInstance(cx, fastClass(),
+            return &NewBuiltinClassInstance(cx, instanceClass(), allocKind,
                                             SingletonObject)->as<TypedArrayObject>();
         }
 
         jsbytecode *pc;
         RootedScript script(cx, cx->currentScript(&pc));
         NewObjectKind newKind = script
-                                ? UseNewTypeForInitializer(script, pc, fastClass())
+                                ? UseNewTypeForInitializer(script, pc, instanceClass())
                                 : GenericObject;
-        RootedObject obj(cx, NewBuiltinClassInstance(cx, fastClass(), newKind));
+        RootedObject obj(cx, NewBuiltinClassInstance(cx, instanceClass(), allocKind, newKind));
         if (!obj)
             return nullptr;
 
         if (script) {
             if (!types::SetInitializerObjectType(cx, script, pc, obj, newKind))
                 return nullptr;
         }
 
         return &obj->as<TypedArrayObject>();
     }
 
     static JSObject *
-    makeInstance(JSContext *cx, HandleObject bufobj, uint32_t byteOffset, uint32_t len,
+    makeInstance(JSContext *cx, Handle<ArrayBufferObject *> buffer, uint32_t byteOffset, uint32_t len,
                  HandleObject proto)
     {
+        JS_ASSERT_IF(!buffer, byteOffset == 0);
+
+        gc::AllocKind allocKind = buffer
+                                  ? GetGCObjectKind(instanceClass())
+                                  : AllocKindForLazyBuffer(len * sizeof(NativeType));
+
         Rooted<TypedArrayObject*> obj(cx);
         if (proto)
-            obj = makeProtoInstance(cx, proto);
+            obj = makeProtoInstance(cx, proto, allocKind);
         else
-            obj = makeTypedInstance(cx, len);
+            obj = makeTypedInstance(cx, len, allocKind);
         if (!obj)
             return nullptr;
-        JS_ASSERT_IF(obj->isTenured(),
-                     obj->tenuredGetAllocKind() == gc::FINALIZE_OBJECT8_BACKGROUND);
 
         obj->setSlot(TYPE_SLOT, Int32Value(ArrayTypeID()));
-        obj->setSlot(BUFFER_SLOT, ObjectValue(*bufobj));
-
-        Rooted<ArrayBufferObject *> buffer(cx, &AsArrayBuffer(bufobj));
-
-        InitArrayBufferViewDataPointer(obj, buffer, byteOffset);
+        obj->setSlot(BUFFER_SLOT, ObjectOrNullValue(buffer));
+
+        if (buffer) {
+            InitArrayBufferViewDataPointer(obj, buffer, byteOffset);
+        } 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(BYTELENGTH_SLOT, Int32Value(len * sizeof(NativeType)));
         obj->setSlot(NEXT_VIEW_SLOT, PrivateValue(nullptr));
 
-        js::Shape *empty = EmptyShape::getInitialShape(cx, fastClass(),
-                                                       obj->getProto(), obj->getParent(), obj->getMetadata(),
-                                                       gc::FINALIZE_OBJECT8_BACKGROUND);
-        if (!empty)
-            return nullptr;
-        obj->setLastPropertyInfallible(empty);
-
 #ifdef DEBUG
-        uint32_t bufferByteLength = buffer->byteLength();
-        uint32_t arrayByteLength = obj->byteLength();
-        uint32_t arrayByteOffset = obj->byteOffset();
-        JS_ASSERT_IF(!buffer->isNeutered(), buffer->dataPointer() <= obj->viewData());
-        JS_ASSERT(bufferByteLength - arrayByteOffset >= arrayByteLength);
-        JS_ASSERT(arrayByteOffset <= bufferByteLength);
+        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
 
-        buffer->addView(obj);
+        if (buffer)
+            buffer->addView(obj);
 
         return obj;
     }
 
     static JSObject *
-    makeInstance(JSContext *cx, HandleObject bufobj, uint32_t byteOffset, uint32_t len)
+    makeInstance(JSContext *cx, Handle<ArrayBufferObject *> bufobj, uint32_t byteOffset, uint32_t len)
     {
         RootedObject nullproto(cx, nullptr);
         return makeInstance(cx, bufobj, byteOffset, len, nullproto);
     }
 
     /*
      * new [Type]Array(length)
      * new [Type]Array(otherTypedArray)
      * new [Type]Array(JSArray)
      * new [Type]Array(ArrayBuffer, [optional] byteOffset, [optional] length)
      */
     static bool
     class_constructor(JSContext *cx, unsigned argc, Value *vp)
     {
-        /* N.B. this is a constructor for protoClass, not fastClass! */
+        /* N.B. this is a constructor for protoClass, not instanceClass! */
         CallArgs args = CallArgsFromVp(argc, vp);
         JSObject *obj = create(cx, args);
         if (!obj)
             return false;
         args.rval().setObject(*obj);
         return true;
     }
 
@@ -391,17 +415,17 @@ class TypedArrayObjectTemplate : public 
             }
         }
 
         Rooted<JSObject*> proto(cx, nullptr);
         return fromBuffer(cx, dataObj, byteOffset, length, proto);
     }
 
     static bool IsThisClass(HandleValue v) {
-        return v.isObject() && v.toObject().hasClass(fastClass());
+        return v.isObject() && v.toObject().hasClass(instanceClass());
     }
 
     template<Value ValueGetter(TypedArrayObject *tarr)>
     static bool
     GetterImpl(JSContext *cx, CallArgs args)
     {
         JS_ASSERT(IsThisClass(args.thisv()));
         args.rval().set(ValueGetter(&args.thisv().toObject().as<TypedArrayObject>()));
@@ -415,48 +439,68 @@ class TypedArrayObjectTemplate : public 
     static bool
     Getter(JSContext *cx, unsigned argc, Value *vp)
     {
         CallArgs args = CallArgsFromVp(argc, vp);
         return CallNonGenericMethod<ThisTypedArrayObject::IsThisClass,
                                     ThisTypedArrayObject::GetterImpl<ValueGetter> >(cx, args);
     }
 
+    static bool
+    BufferGetterImpl(JSContext *cx, CallArgs args)
+    {
+        JS_ASSERT(IsThisClass(args.thisv()));
+        Rooted<TypedArrayObject *> tarray(cx, &args.thisv().toObject().as<TypedArrayObject>());
+        if (!ensureHasBuffer(cx, tarray))
+            return false;
+        args.rval().set(bufferValue(tarray));
+        return true;
+    }
+
+    // BufferGetter is a function that lazily constructs the array buffer for a
+    // typed array before fetching it.
+    static bool
+    BufferGetter(JSContext *cx, unsigned argc, Value *vp)
+    {
+        CallArgs args = CallArgsFromVp(argc, vp);
+        return CallNonGenericMethod<ThisTypedArrayObject::IsThisClass,
+                                    ThisTypedArrayObject::BufferGetterImpl>(cx, args);
+    }
+
     // Define an accessor for a read-only property that invokes a native getter
-    template<Value ValueGetter(TypedArrayObject *tarr)>
     static bool
-    DefineGetter(JSContext *cx, PropertyName *name, HandleObject proto)
+    DefineGetter(JSContext *cx, HandleObject proto, PropertyName *name, Native native)
     {
         RootedId id(cx, NameToId(name));
         unsigned flags = JSPROP_SHARED | JSPROP_GETTER | JSPROP_PERMANENT;
 
         Rooted<GlobalObject*> global(cx, cx->compartment()->maybeGlobal());
-        JSObject *getter = NewFunction(cx, NullPtr(), Getter<ValueGetter>, 0,
+        JSObject *getter = NewFunction(cx, NullPtr(), native, 0,
                                        JSFunction::NATIVE_FUN, global, NullPtr());
         if (!getter)
             return false;
 
         return DefineNativeProperty(cx, proto, id, UndefinedHandleValue,
                                     JS_DATA_TO_FUNC_PTR(PropertyOp, getter), nullptr,
                                     flags, 0, 0);
     }
 
     static
     bool defineGetters(JSContext *cx, HandleObject proto)
     {
-        if (!DefineGetter<lengthValue>(cx, cx->names().length, proto))
+        if (!DefineGetter(cx, proto, cx->names().length, Getter<lengthValue>))
             return false;
 
-        if (!DefineGetter<bufferValue>(cx, cx->names().buffer, proto))
+        if (!DefineGetter(cx, proto, cx->names().buffer, BufferGetter))
             return false;
 
-        if (!DefineGetter<byteLengthValue>(cx, cx->names().byteLength, proto))
+        if (!DefineGetter(cx, proto, cx->names().byteLength, Getter<byteLengthValue>))
             return false;
 
-        if (!DefineGetter<byteOffsetValue>(cx, cx->names().byteOffset, proto))
+        if (!DefineGetter(cx, proto, cx->names().byteOffset, Getter<byteOffsetValue>))
             return false;
 
         return true;
     }
 
     /* subarray(start[, end]) */
     static bool
     fun_subarray_impl(JSContext *cx, CallArgs args)
@@ -666,17 +710,17 @@ class TypedArrayObjectTemplate : public 
                  * Rather than hack some crazy solution together, implement
                  * this all using a private helper function, created when
                  * ArrayBufferObject was initialized and cached in the global.
                  * This reuses all the existing cross-compartment crazy so we
                  * don't have to do anything *uniquely* crazy here.
                  */
 
                 Rooted<JSObject*> proto(cx);
-                if (!GetBuiltinPrototype(cx, JSCLASS_CACHED_PROTO_KEY(fastClass()), &proto))
+                if (!GetBuiltinPrototype(cx, JSCLASS_CACHED_PROTO_KEY(instanceClass()), &proto))
                     return nullptr;
 
                 InvokeArgs args(cx);
                 if (!args.init(3))
                     return nullptr;
 
                 args.setCallee(cx->compartment()->maybeGlobal()->createArrayFromBuffer<NativeType>());
                 args.setThis(ObjectValue(*bufobj));
@@ -690,74 +734,96 @@ class TypedArrayObjectTemplate : public 
             }
         }
 
         if (!IsArrayBuffer(bufobj)) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
             return nullptr; // must be arrayBuffer
         }
 
-        ArrayBufferObject &buffer = AsArrayBuffer(bufobj);
-
-        if (byteOffset > buffer.byteLength() || byteOffset % sizeof(NativeType) != 0) {
+        Rooted<ArrayBufferObject *> buffer(cx, &AsArrayBuffer(bufobj));
+
+        if (byteOffset > buffer->byteLength() || byteOffset % sizeof(NativeType) != 0) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
             return nullptr; // invalid byteOffset
         }
 
         uint32_t len;
         if (lengthInt == -1) {
-            len = (buffer.byteLength() - byteOffset) / sizeof(NativeType);
-            if (len * sizeof(NativeType) != buffer.byteLength() - byteOffset) {
+            len = (buffer->byteLength() - byteOffset) / sizeof(NativeType);
+            if (len * sizeof(NativeType) != buffer->byteLength() - byteOffset) {
                 JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                                      JSMSG_TYPED_ARRAY_BAD_ARGS);
                 return nullptr; // given byte array doesn't map exactly to sizeof(NativeType) * N
             }
         } else {
             len = uint32_t(lengthInt);
         }
 
         // Go slowly and check for overflow.
         uint32_t arrayByteLength = len * sizeof(NativeType);
         if (len >= INT32_MAX / sizeof(NativeType) || byteOffset >= INT32_MAX - arrayByteLength) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
             return nullptr; // overflow when calculating byteOffset + len * sizeof(NativeType)
         }
 
-        if (arrayByteLength + byteOffset > buffer.byteLength()) {
+        if (arrayByteLength + byteOffset > buffer->byteLength()) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
             return nullptr; // byteOffset + len is too big for the arraybuffer
         }
 
-        return makeInstance(cx, bufobj, byteOffset, len, proto);
+        return makeInstance(cx, buffer, byteOffset, len, proto);
+    }
+
+    static bool
+    maybeCreateArrayBuffer(JSContext *cx, uint32_t nelements, MutableHandle<ArrayBufferObject *> buffer)
+    {
+        // Make sure that array elements evenly divide into the inline buffer's
+        // size, for the test below.
+        JS_STATIC_ASSERT((INLINE_BUFFER_LIMIT / sizeof(NativeType)) * sizeof(NativeType) == INLINE_BUFFER_LIMIT);
+
+        if (nelements <= INLINE_BUFFER_LIMIT / sizeof(NativeType)) {
+            // The array's data can be inline, and the buffer created lazily.
+            return true;
+        }
+
+        if (nelements >= INT32_MAX / sizeof(NativeType)) {
+            JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
+                                 JSMSG_NEED_DIET, "size and count");
+            return false;
+        }
+
+        buffer.set(ArrayBufferObject::create(cx, nelements * sizeof(NativeType)));
+        return !!buffer;
     }
 
     static JSObject *
     fromLength(JSContext *cx, uint32_t nelements)
     {
-        RootedObject buffer(cx, createBufferWithSizeAndCount(cx, nelements));
-        if (!buffer)
+        Rooted<ArrayBufferObject *> buffer(cx);
+        if (!maybeCreateArrayBuffer(cx, nelements, &buffer))
             return nullptr;
         return makeInstance(cx, buffer, 0, nelements);
     }
 
     static JSObject *
     fromArray(JSContext *cx, HandleObject other)
     {
         uint32_t len;
         if (other->is<TypedArrayObject>()) {
             len = other->as<TypedArrayObject>().length();
         } else if (!GetLengthProperty(cx, other, &len)) {
             return nullptr;
         }
 
-        RootedObject bufobj(cx, createBufferWithSizeAndCount(cx, len));
-        if (!bufobj)
+        Rooted<ArrayBufferObject *> buffer(cx);
+        if (!maybeCreateArrayBuffer(cx, len, &buffer))
             return nullptr;
 
-        RootedObject obj(cx, makeInstance(cx, bufobj, 0, len));
+        RootedObject obj(cx, makeInstance(cx, buffer, 0, len));
         if (!obj || !copyFromArray(cx, obj, other, len))
             return nullptr;
         return obj;
     }
 
     static const NativeType
     getIndex(JSObject *obj, uint32_t index)
     {
@@ -776,17 +842,20 @@ class TypedArrayObjectTemplate : public 
     static JSObject *
     createSubarray(JSContext *cx, HandleObject tarrayArg, uint32_t begin, uint32_t end)
     {
         Rooted<TypedArrayObject*> tarray(cx, &tarrayArg->as<TypedArrayObject>());
 
         JS_ASSERT(begin <= tarray->length());
         JS_ASSERT(end <= tarray->length());
 
-        RootedObject bufobj(cx, tarray->buffer());
+        if (!ensureHasBuffer(cx, tarray))
+            return nullptr;
+
+        Rooted<ArrayBufferObject *> bufobj(cx, tarray->buffer());
         JS_ASSERT(bufobj);
 
         JS_ASSERT(begin <= end);
         uint32_t length = end - begin;
 
         JS_ASSERT(begin < UINT32_MAX / sizeof(NativeType));
         uint32_t arrayByteOffset = tarray->byteOffset();
         JS_ASSERT(UINT32_MAX - begin * sizeof(NativeType) >= arrayByteOffset);
@@ -1060,30 +1129,16 @@ class TypedArrayObjectTemplate : public 
           }
           default:
             MOZ_ASSUME_UNREACHABLE("copyFromWithOverlap with a TypedArrayObject of unknown type");
         }
 
         js_free(srcbuf);
         return true;
     }
-
-    static JSObject *
-    createBufferWithSizeAndCount(JSContext *cx, uint32_t count)
-    {
-        size_t size = sizeof(NativeType);
-        if (size != 0 && count >= INT32_MAX / size) {
-            JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
-                                 JSMSG_NEED_DIET, "size and count");
-            return nullptr;
-        }
-
-        uint32_t bytelen = size * count;
-        return ArrayBufferObject::create(cx, bytelen);
-    }
 };
 
 class Int8ArrayObject : public TypedArrayObjectTemplate<int8_t> {
   public:
     enum { ACTUAL_TYPE = ScalarTypeDescr::TYPE_INT8 };
     static const JSProtoKey key = JSProto_Int8Array;
     static const JSFunctionSpec jsfuncs[];
 };
--- a/js/src/vm/TypedArrayObject.h
+++ b/js/src/vm/TypedArrayObject.h
@@ -35,34 +35,55 @@ class TypedArrayObject : public ArrayBuf
     static const size_t TYPE_SLOT      = JS_TYPEDOBJ_SLOT_TYPE_DESCR;
     static const size_t RESERVED_SLOTS = JS_TYPEDOBJ_SLOTS;
     static const size_t DATA_SLOT      = JS_TYPEDOBJ_SLOT_DATA;
 
   public:
     static const Class classes[ScalarTypeDescr::TYPE_MAX];
     static const Class protoClasses[ScalarTypeDescr::TYPE_MAX];
 
+    static const size_t FIXED_DATA_START = DATA_SLOT + 1;
+
+    // For typed arrays which can store their data inline, the array buffer
+    // object is created lazily.
+    static const uint32_t INLINE_BUFFER_LIMIT =
+        (JSObject::MAX_FIXED_SLOTS - FIXED_DATA_START) * sizeof(Value);
+
+    static gc::AllocKind
+    AllocKindForLazyBuffer(size_t nbytes)
+    {
+        JS_ASSERT(nbytes <= INLINE_BUFFER_LIMIT);
+        int dataSlots = (nbytes - 1) / sizeof(Value) + 1;
+        JS_ASSERT(int(nbytes) <= dataSlots * int(sizeof(Value)));
+        return gc::GetGCObjectKind(FIXED_DATA_START + dataSlots);
+    }
+
     static Value bufferValue(TypedArrayObject *tarr) {
         return tarr->getFixedSlot(BUFFER_SLOT);
     }
     static Value byteOffsetValue(TypedArrayObject *tarr) {
         return tarr->getFixedSlot(BYTEOFFSET_SLOT);
     }
     static Value byteLengthValue(TypedArrayObject *tarr) {
         return tarr->getFixedSlot(BYTELENGTH_SLOT);
     }
     static Value lengthValue(TypedArrayObject *tarr) {
         return tarr->getFixedSlot(LENGTH_SLOT);
     }
 
+    static bool
+    ensureHasBuffer(JSContext *cx, Handle<TypedArrayObject *> tarray);
+
     ArrayBufferObject *sharedBuffer() const;
     ArrayBufferObject *buffer() const {
-        JSObject &obj = bufferValue(const_cast<TypedArrayObject*>(this)).toObject();
-        if (obj.is<ArrayBufferObject>())
-            return &obj.as<ArrayBufferObject>();
+        JSObject *obj = bufferValue(const_cast<TypedArrayObject*>(this)).toObjectOrNull();
+        if (!obj)
+            return nullptr;
+        if (obj->is<ArrayBufferObject>())
+            return &obj->as<ArrayBufferObject>();
         return sharedBuffer();
     }
     uint32_t byteOffset() const {
         return byteOffsetValue(const_cast<TypedArrayObject*>(this)).toInt32();
     }
     uint32_t byteLength() const {
         return byteLengthValue(const_cast<TypedArrayObject*>(this)).toInt32();
     }
@@ -223,34 +244,30 @@ class DataViewObject : public ArrayBuffe
     }
 
     static Value byteLengthValue(DataViewObject *view) {
         Value v = view->getReservedSlot(BYTELENGTH_SLOT);
         JS_ASSERT(v.toInt32() >= 0);
         return v;
     }
 
+    static Value bufferValue(DataViewObject *view) {
+        return view->getReservedSlot(BUFFER_SLOT);
+    }
+
     uint32_t byteOffset() const {
         return byteOffsetValue(const_cast<DataViewObject*>(this)).toInt32();
     }
 
     uint32_t byteLength() const {
         return byteLengthValue(const_cast<DataViewObject*>(this)).toInt32();
     }
 
-    bool hasBuffer() const {
-        return getReservedSlot(BUFFER_SLOT).isObject();
-    }
-
     ArrayBufferObject &arrayBuffer() const {
-        return getReservedSlot(BUFFER_SLOT).toObject().as<ArrayBufferObject>();
-    }
-
-    static Value bufferValue(DataViewObject *view) {
-        return view->hasBuffer() ? ObjectValue(view->arrayBuffer()) : UndefinedValue();
+        return bufferValue(const_cast<DataViewObject*>(this)).toObject().as<ArrayBufferObject>();
     }
 
     void *dataPointer() const {
         return getPrivate();
     }
 
     static bool class_constructor(JSContext *cx, unsigned argc, Value *vp);
     static bool constructWithProto(JSContext *cx, unsigned argc, Value *vp);