Bug 861925 - Allow grabbing data from ArrayBuffers and neutering them independently (in addition to Steal, which does both at the same time). r=Waldo
authorSteve Fink <sfink@mozilla.com>
Tue, 15 Oct 2013 23:48:01 -0700
changeset 165537 0efef923cc179420f5897c19e5e9c0164f1a7693
parent 165536 dbc5f50a2a8d57795279ecd6e483a94f39ad7128
child 165538 b12f63beba2cbfd92dbc4e9a820ea6c9b2bbc8fa
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs861925
milestone27.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 861925 - Allow grabbing data from ArrayBuffers and neutering them independently (in addition to Steal, which does both at the same time). r=Waldo
js/src/jsfriendapi.h
js/src/vm/StructuredClone.cpp
js/src/vm/TypedArrayObject.cpp
js/src/vm/TypedArrayObject.h
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1236,16 +1236,22 @@ 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);
 
 /*
+ * Set an ArrayBuffer's length to 0 and neuter all of its views.
+ */
+extern JS_FRIEND_API(void)
+JS_NeuterArrayBuffer(JSObject *obj, JSContext *cx);
+
+/*
  * Check whether obj supports JS_GetDataView* APIs.
  */
 JS_FRIEND_API(bool)
 JS_IsDataViewObject(JSObject *obj);
 
 /*
  * Return the byte offset of a data view into its array buffer. |obj| must be a
  * DataView.
--- a/js/src/vm/StructuredClone.cpp
+++ b/js/src/vm/StructuredClone.cpp
@@ -27,16 +27,18 @@
  * array object.
  */
 
 #include "js/StructuredClone.h"
 
 #include "mozilla/Endian.h"
 #include "mozilla/FloatingPoint.h"
 
+#include <algorithm>
+
 #include "jsapi.h"
 #include "jscntxt.h"
 #include "jsdate.h"
 #include "jswrapper.h"
 
 #include "vm/TypedArrayObject.h"
 #include "vm/WrapperObject.h"
 
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -240,17 +240,17 @@ AllocateArrayBufferContents(JSContext *m
             js_ReportOutOfMemory(maybecx);
         return nullptr;
     }
 
     if (initdata)
         memcpy(newheader->elements(), initdata, nbytes);
 
     // we rely on this being correct
-    ArrayBufferObject::setElementsHeader(newheader, nbytes);
+    ArrayBufferObject::updateElementsHeader(newheader, nbytes);
 
     return newheader;
 }
 
 bool
 ArrayBufferObject::allocateSlots(JSContext *maybecx, uint32_t bytes, uint8_t *contents)
 {
     /*
@@ -270,17 +270,17 @@ ArrayBufferObject::allocateSlots(JSConte
     } else {
         elements = fixedElements();
         if (contents)
             memcpy(elements, contents, bytes);
         else
             memset(elements, 0, bytes);
     }
 
-    setElementsHeader(getElementsHeader(), bytes);
+    initElementsHeader(getElementsHeader(), bytes);
 
     return true;
 }
 
 static inline void
 PostBarrierTypedArrayObject(JSObject *obj)
 {
 #ifdef JSGC_GENERATIONAL
@@ -293,17 +293,17 @@ PostBarrierTypedArrayObject(JSObject *ob
 
 // The list of views must be stored somewhere in the ArrayBufferObject, but
 // the slots are already being used for the element storage and the private
 // field is used for a delegate object. The ObjectElements header has space
 // for it, but I don't want to mess around with adding unions to it with
 // JS_USE_NEW_OBJECT_REPRESENTATION pending, since it will solve this much
 // more cleanly.
 struct OldObjectRepresentationHack {
-    uint32_t capacity;
+    uint32_t flags;
     uint32_t initializedLength;
     EncapsulatedPtr<ArrayBufferViewObject> views;
 };
 
 static ArrayBufferViewObject *
 GetViewList(ArrayBufferObject *obj)
 {
     return reinterpret_cast<OldObjectRepresentationHack*>(obj->getElementsHeader())->views;
@@ -326,56 +326,119 @@ InitViewList(ArrayBufferObject *obj, Arr
 static EncapsulatedPtr<ArrayBufferViewObject> &
 GetViewListRef(ArrayBufferObject *obj)
 {
     JS_ASSERT(obj->runtimeFromMainThread()->isHeapBusy());
     return reinterpret_cast<OldObjectRepresentationHack*>(obj->getElementsHeader())->views;
 }
 
 void
+ArrayBufferObject::neuterViews(JSContext *maybecx)
+{
+    ArrayBufferViewObject *view;
+    for (view = GetViewList(this); view; view = view->nextView()) {
+        view->neuter();
+
+        // Notify compiled jit code that the base pointer has moved.
+        if (maybecx)
+            MarkObjectStateChange(maybecx, view);
+    }
+
+    // neuterAsmJSArrayBuffer adjusts state specific to the ArrayBuffer data
+    // itself, but it only affects the behavior of views
+    if (isAsmJSArrayBuffer())
+        ArrayBufferObject::neuterAsmJSArrayBuffer(*this);
+}
+
+void
 ArrayBufferObject::changeContents(JSContext *maybecx, ObjectElements *newHeader)
 {
     // Grab out data before invalidating it.
     uint32_t byteLengthCopy = byteLength();
     uintptr_t oldDataPointer = uintptr_t(dataPointer());
-   ArrayBufferViewObject *viewListHead = GetViewList(this);
+    ArrayBufferViewObject *viewListHead = GetViewList(this);
 
     // Update all views.
     uintptr_t newDataPointer = uintptr_t(newHeader->elements());
-   for (ArrayBufferViewObject *view = viewListHead; view; view = view->nextView()) {
+    for (ArrayBufferViewObject *view = viewListHead; view; view = view->nextView()) {
         uintptr_t newDataPtr = uintptr_t(view->getPrivate()) - oldDataPointer + newDataPointer;
         view->setPrivate(reinterpret_cast<uint8_t*>(newDataPtr));
 
         // Notify compiled jit code that the base pointer has moved.
         if (maybecx)
             MarkObjectStateChange(maybecx, view);
     }
 
-   // Change to the new header (now, so we can use SetViewList).
+    // The list of views in the old header is reachable if the contents are
+    // being transferred, so NULL it out
+    SetViewList(this, NULL);
+
     elements = newHeader->elements();
 
-    // Initialize 'newHeader'.
-    ArrayBufferObject::setElementsHeader(newHeader, byteLengthCopy);
-   SetViewList(this, viewListHead);
+    initElementsHeader(newHeader, byteLengthCopy);
+    InitViewList(this, viewListHead);
+}
+
+void
+ArrayBufferObject::neuter(JSContext *cx)
+{
+    JS_ASSERT(cx);
+    if (hasDynamicElements() && !isAsmJSArrayBuffer()) {
+        ObjectElements *oldHeader = getElementsHeader();
+        changeContents(cx, ObjectElements::fromElements(fixedElements()));
+
+        FreeOp fop(cx->runtime(), false);
+        fop.free_(oldHeader);
+    }
+
+    uint32_t byteLen = 0;
+    updateElementsHeader(getElementsHeader(), byteLen);
 }
 
 bool
-ArrayBufferObject::uninlineData(JSContext *maybecx)
+ArrayBufferObject::copyData(JSContext *maybecx)
 {
-    if (hasDynamicElements())
-        return true;
-
     ObjectElements *newHeader = AllocateArrayBufferContents(maybecx, byteLength(), dataPointer());
     if (!newHeader)
         return false;
 
     changeContents(maybecx, newHeader);
     return true;
 }
 
+bool
+ArrayBufferObject::ensureNonInline(JSContext *maybecx)
+{
+    if (hasDynamicElements())
+        return true;
+    return copyData(maybecx);
+}
+
+// If the ArrayBuffer already contains dynamic contents, hand them back.
+// Otherwise, allocate some new contents and copy the data over, but in no case
+// modify the original ArrayBuffer. (Also, any allocated contents will have no
+// views linked to in its header.)
+ObjectElements *
+ArrayBufferObject::getTransferableContents(JSContext *maybecx, bool *callerOwns)
+{
+    if (hasDynamicElements() && !isAsmJSArrayBuffer()) {
+        *callerOwns = false;
+        return getElementsHeader();
+    }
+
+    uint32_t byteLen = byteLength();
+    ObjectElements *newheader = AllocateArrayBufferContents(maybecx, byteLen, dataPointer());
+    if (!newheader)
+        return NULL;
+
+    initElementsHeader(newheader, byteLen);
+    *callerOwns = true;
+    return newheader;
+}
+
 #if defined(JS_ION) && defined(JS_CPU_X64)
 // To avoid dynamically checking bounds on each load/store, asm.js code relies
 // on the SIGSEGV handler in AsmJSSignalHandlers.cpp. However, this only works
 // if we can guarantee that *any* out-of-bounds access generates a fault. This
 // isn't generally true since an out-of-bounds access could land on other
 // Mozilla data. To overcome this on x64, we reserve an entire 4GB space,
 // making only the range [0, byteLength) accessible, and use a 32-bit unsigned
 // index into this space. (x86 and ARM require different tricks.)
@@ -475,18 +538,22 @@ ArrayBufferObject::neuterAsmJSArrayBuffe
     if (mprotect(buffer.dataPointer(), buffer.byteLength(), PROT_NONE))
         MOZ_CRASH();
 #endif
 }
 #else  /* defined(JS_ION) && defined(JS_CPU_X64) */
 bool
 ArrayBufferObject::prepareForAsmJS(JSContext *cx, Handle<ArrayBufferObject*> buffer)
 {
-    if (!buffer->uninlineData(cx))
+    if (buffer->isAsmJSArrayBuffer())
+        return true;
+
+    if (!buffer->copyData(cx))
         return false;
+    JS_ASSERT(buffer->hasDynamicElements());
 
     buffer->getElementsHeader()->setIsAsmJSArrayBuffer();
     return true;
 }
 
 void
 ArrayBufferObject::releaseAsmJSArrayBuffer(FreeOp *fop, JSObject *obj)
 {
@@ -593,51 +660,38 @@ ArrayBufferObject::createDataViewForThis
 bool
 ArrayBufferObject::createDataViewForThis(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsArrayBuffer, createDataViewForThisImpl>(cx, args);
 }
 
 bool
-ArrayBufferObject::stealContents(JSContext *cx, JSObject *obj, void **contents,
-                                 uint8_t **data)
+ArrayBufferObject::stealContents(JSContext *cx, JSObject *obj, void **contents, uint8_t **data)
 {
     ArrayBufferObject &buffer = obj->as<ArrayBufferObject>();
-    ArrayBufferViewObject *views = GetViewList(&buffer);
-    js::ObjectElements *header = js::ObjectElements::fromElements((js::HeapSlot*)buffer.dataPointer());
-    if (buffer.hasDynamicElements() && !buffer.isAsmJSArrayBuffer()) {
-        SetViewList(&buffer, nullptr);
-        *contents = header;
-        *data = buffer.dataPointer();
-
-        buffer.setFixedElements();
-        header = js::ObjectElements::fromElements((js::HeapSlot*)buffer.dataPointer());
-    } else {
-        uint32_t length = buffer.byteLength();
-        js::ObjectElements *newheader =
-            AllocateArrayBufferContents(cx, length, buffer.dataPointer());
-        if (!newheader) {
-            js_ReportOutOfMemory(cx);
-            return false;
-        }
-
-        ArrayBufferObject::setElementsHeader(newheader, length);
-        *contents = newheader;
-        *data = reinterpret_cast<uint8_t *>(newheader + 1);
-
-        if (buffer.isAsmJSArrayBuffer())
-            ArrayBufferObject::neuterAsmJSArrayBuffer(buffer);
+
+    // Make the data stealable
+    bool own;
+    ObjectElements *header = reinterpret_cast<ObjectElements*>(buffer.getTransferableContents(cx, &own));
+    if (!header)
+        return false;
+    *contents = header;
+    *data = reinterpret_cast<uint8_t *>(header + 1);
+
+    // Neuter the views, which may also mprotect(PROT_NONE) the buffer. So do
+    // it after copying out the data.
+    buffer.neuterViews(cx);
+
+    if (!own) {
+        // If header has dynamically allocated elements, revert it back to
+        // fixed-element storage before neutering it.
+        buffer.changeContents(cx, ObjectElements::fromElements(buffer.fixedElements()));
     }
-
-    // Neuter the donor ArrayBufferObject and all views of it
-    ArrayBufferObject::setElementsHeader(header, 0);
-    InitViewList(&buffer, views);
-    for (ArrayBufferViewObject *view = views; view; view = view->nextView())
-        view->neuter();
+    buffer.neuter(cx);
 
     return true;
 }
 
 void
 ArrayBufferObject::obj_trace(JSTracer *trc, JSObject *obj)
 {
     /*
@@ -3960,21 +4014,29 @@ JS_GetArrayBufferByteLength(JSObject *ob
 
 JS_FRIEND_API(uint8_t *)
 JS_GetArrayBufferData(JSObject *obj)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
     ArrayBufferObject &buffer = obj->as<ArrayBufferObject>();
-    if (!buffer.uninlineData(nullptr))
+    if (!buffer.ensureNonInline(NULL))
         return nullptr;
     return buffer.dataPointer();
 }
 
+JS_FRIEND_API(void)
+JS_NeuterArrayBuffer(JSObject *obj, JSContext *cx)
+{
+    ArrayBufferObject &buffer = obj->as<ArrayBufferObject>();
+    buffer.neuterViews(cx);
+    buffer.neuter(cx);
+}
+
 JS_FRIEND_API(JSObject *)
 JS_NewArrayBuffer(JSContext *cx, uint32_t nbytes)
 {
     JS_ASSERT(nbytes <= INT32_MAX);
     return ArrayBufferObject::create(cx, nbytes);
 }
 
 JS_PUBLIC_API(JSObject *)
@@ -3991,31 +4053,31 @@ JS_NewArrayBufferWithContents(JSContext 
 
 JS_PUBLIC_API(bool)
 JS_AllocateArrayBufferContents(JSContext *cx, uint32_t nbytes, void **contents, uint8_t **data)
 {
     js::ObjectElements *header = AllocateArrayBufferContents(cx, nbytes, nullptr);
     if (!header)
         return false;
 
-    ArrayBufferObject::setElementsHeader(header, nbytes);
+    ArrayBufferObject::updateElementsHeader(header, nbytes);
 
     *contents = header;
     *data = reinterpret_cast<uint8_t*>(header->elements());
     return true;
 }
 
 JS_PUBLIC_API(bool)
 JS_ReallocateArrayBufferContents(JSContext *cx, uint32_t nbytes, void **contents, uint8_t **data)
 {
     js::ObjectElements *header = AllocateArrayBufferContents(cx, nbytes, nullptr, *contents);
     if (!header)
         return false;
 
-    ArrayBufferObject::setElementsHeader(header, nbytes);
+    ArrayBufferObject::initElementsHeader(header, nbytes);
 
     *contents = header;
     *data = reinterpret_cast<uint8_t*>(header->elements());
     return true;
 }
 
 JS_PUBLIC_API(bool)
 JS_StealArrayBufferContents(JSContext *cx, JSObject *obj, void **contents,
--- a/js/src/vm/TypedArrayObject.h
+++ b/js/src/vm/TypedArrayObject.h
@@ -141,51 +141,81 @@ class ArrayBufferObject : public JSObjec
 
     static void resetArrayBufferList(JSCompartment *rt);
     static bool saveArrayBufferList(JSCompartment *c, ArrayBufferVector &vector);
     static void restoreArrayBufferLists(ArrayBufferVector &vector);
 
     static bool stealContents(JSContext *cx, JSObject *obj, void **contents,
                               uint8_t **data);
 
-    static void setElementsHeader(js::ObjectElements *header, uint32_t bytes) {
-        header->flags = 0;
+    static void updateElementsHeader(js::ObjectElements *header, uint32_t bytes) {
         header->initializedLength = bytes;
 
         // NB: one or both of these fields is clobbered by GetViewList to store
         // the 'views' link. Set them to 0 to effectively initialize 'views'
         // to nullptr.
         header->length = 0;
         header->capacity = 0;
     }
 
+    static void initElementsHeader(js::ObjectElements *header, uint32_t bytes) {
+        header->flags = 0;
+        updateElementsHeader(header, bytes);
+    }
+
     static uint32_t headerInitializedLength(const js::ObjectElements *header) {
         return header->initializedLength;
     }
 
     void addView(ArrayBufferViewObject *view);
 
     bool allocateSlots(JSContext *cx, uint32_t size, uint8_t *contents = nullptr);
+
     void changeContents(JSContext *cx, ObjectElements *newHeader);
 
     /*
-     * Ensure that the data is not stored inline. Used when handing back a
+     * Copy the data into freshly-allocated memory. Used when un-inlining or
+     * when converting an ArrayBuffer to an AsmJS (MMU-assisted) ArrayBuffer.
+     */
+    bool copyData(JSContext *maybecx);
+
+    /*
+     * Ensure data is not stored inline in the object. Used when handing back a
      * GC-safe pointer.
      */
-    bool uninlineData(JSContext *cx);
+    bool ensureNonInline(JSContext *maybecx);
 
     uint32_t byteLength() const {
         return getElementsHeader()->initializedLength;
     }
 
+    /*
+     * Return the contents of an ArrayBuffer without modifying the ArrayBuffer
+     * itself. Set *callerOwns to true if the caller has the only pointer to
+     * the returned contents (which is the case for inline or asm.js buffers),
+     * and false if the ArrayBuffer still owns the pointer.
+     */
+    ObjectElements *getTransferableContents(JSContext *maybecx, bool *callerOwns);
+
+    /*
+     * Neuter all views of an ArrayBuffer.
+     */
+    void neuterViews(JSContext *maybecx);
+
     inline uint8_t * dataPointer() const {
         return (uint8_t *) elements;
     }
 
     /*
+     * Discard the ArrayBuffer contents. For asm.js buffers, at least, should
+     * be called after neuterViews().
+     */
+    void neuter(JSContext *maybecx);
+
+    /*
      * Check if the arrayBuffer contains any data. This will return false for
      * ArrayBuffer.prototype and neutered ArrayBuffers.
      */
     bool hasData() const {
         return getClass() == &class_;
     }
 
     bool isAsmJSArrayBuffer() const {
@@ -516,16 +546,17 @@ class DataViewObject : public ArrayBuffe
 
     static bool setFloat32Impl(JSContext *cx, CallArgs args);
     static bool fun_setFloat32(JSContext *cx, unsigned argc, Value *vp);
 
     static bool setFloat64Impl(JSContext *cx, CallArgs args);
     static bool fun_setFloat64(JSContext *cx, unsigned argc, Value *vp);
 
     static JSObject *initClass(JSContext *cx);
+    static void neuter(JSObject *view);
     static bool getDataPointer(JSContext *cx, Handle<DataViewObject*> obj,
                                CallArgs args, size_t typeSize, uint8_t **data);
     template<typename NativeType>
     static bool read(JSContext *cx, Handle<DataViewObject*> obj,
                      CallArgs &args, NativeType *val, const char *method);
     template<typename NativeType>
     static bool write(JSContext *cx, Handle<DataViewObject*> obj,
                       CallArgs &args, const char *method);