☠☠ backed out by d0fa5c45cabf ☠ ☠ | |
author | Steve Fink <sfink@mozilla.com> |
Thu, 09 May 2013 15:53:11 -0700 | |
changeset 150849 | e646195f32aea58681eb93eef3fe4dcc99579354 |
parent 150848 | 015a92e94c0713bdf60dc4b6e0a62699882c3b2f |
child 150850 | 8febf2f0e35dcc341b8acea6ae882a338144cc72 |
push id | 25469 |
push user | cbook@mozilla.com |
push date | Wed, 16 Oct 2013 10:46:01 +0000 |
treeherder | autoland@afae5911a1e0 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | Waldo |
bugs | 861925 |
milestone | 27.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
|
--- 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 @@ -239,17 +239,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) { /* @@ -269,17 +269,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 @@ -292,17 +292,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; @@ -325,56 +325,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.) @@ -474,18 +537,19 @@ 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->copyData(cx)) return false; + JS_ASSERT(buffer->hasDynamicElements()); buffer->getElementsHeader()->setIsAsmJSArrayBuffer(); return true; } void ArrayBufferObject::releaseAsmJSArrayBuffer(FreeOp *fop, JSObject *obj) { @@ -592,51 +656,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) { /* @@ -3961,21 +4012,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 *) @@ -3992,31 +4051,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);