summaryrefslogtreecommitdiffstats
path: root/js/src/vm/ArrayBufferObject.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/ArrayBufferObject.cpp')
-rw-r--r--js/src/vm/ArrayBufferObject.cpp1950
1 files changed, 1950 insertions, 0 deletions
diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp
new file mode 100644
index 000000000..1053fa99d
--- /dev/null
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -0,0 +1,1950 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "vm/ArrayBufferObject-inl.h"
+#include "vm/ArrayBufferObject.h"
+
+#include "mozilla/Alignment.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/TaggedAnonymousMemory.h"
+
+#include <string.h>
+#ifndef XP_WIN
+# include <sys/mman.h>
+#endif
+
+#ifdef MOZ_VALGRIND
+# include <valgrind/memcheck.h>
+#endif
+
+#include "jsapi.h"
+#include "jsarray.h"
+#include "jscntxt.h"
+#include "jscpucfg.h"
+#include "jsfriendapi.h"
+#include "jsnum.h"
+#include "jsobj.h"
+#include "jstypes.h"
+#include "jsutil.h"
+#ifdef XP_WIN
+# include "jswin.h"
+#endif
+#include "jswrapper.h"
+
+#include "gc/Barrier.h"
+#include "gc/Marking.h"
+#include "gc/Memory.h"
+#include "js/Conversions.h"
+#include "js/MemoryMetrics.h"
+#include "vm/GlobalObject.h"
+#include "vm/Interpreter.h"
+#include "vm/SelfHosting.h"
+#include "vm/SharedArrayObject.h"
+#include "vm/WrapperObject.h"
+#include "wasm/WasmSignalHandlers.h"
+#include "wasm/WasmTypes.h"
+
+#include "jsatominlines.h"
+
+#include "vm/NativeObject-inl.h"
+#include "vm/Shape-inl.h"
+
+using JS::ToInt32;
+
+using mozilla::DebugOnly;
+using mozilla::CheckedInt;
+using mozilla::Some;
+using mozilla::Maybe;
+using mozilla::Nothing;
+
+using namespace js;
+using namespace js::gc;
+
+/*
+ * Convert |v| to an array index for an array of length |length| per
+ * the Typed Array Specification section 7.0, |subarray|. If successful,
+ * the output value is in the range [0, length].
+ */
+bool
+js::ToClampedIndex(JSContext* cx, HandleValue v, uint32_t length, uint32_t* out)
+{
+ int32_t result;
+ if (!ToInt32(cx, v, &result))
+ return false;
+ if (result < 0) {
+ result += length;
+ if (result < 0)
+ result = 0;
+ } else if (uint32_t(result) > length) {
+ result = length;
+ }
+ *out = uint32_t(result);
+ return true;
+}
+
+static bool
+arraybuffer_static_slice(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (args.length() < 1) {
+ ReportMissingArg(cx, args.calleev(), 1);
+ return false;
+ }
+
+ if (!GlobalObject::warnOnceAboutArrayBufferSlice(cx, cx->global()))
+ return false;
+
+ FixedInvokeArgs<2> args2(cx);
+ args2[0].set(args.get(1));
+ args2[1].set(args.get(2));
+ return CallSelfHostedFunction(cx, "ArrayBufferSlice", args[0], args2, args.rval());
+}
+
+/*
+ * ArrayBufferObject
+ *
+ * This class holds the underlying raw buffer that the TypedArrayObject classes
+ * access. It can be created explicitly and passed to a TypedArrayObject, or
+ * can be created implicitly by constructing a TypedArrayObject with a size.
+ */
+
+/*
+ * ArrayBufferObject (base)
+ */
+
+static const ClassSpec ArrayBufferObjectProtoClassSpec = {
+ DELEGATED_CLASSSPEC(ArrayBufferObject::class_.spec),
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ ClassSpec::IsDelegated
+};
+
+static const Class ArrayBufferObjectProtoClass = {
+ "ArrayBufferPrototype",
+ JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer),
+ JS_NULL_CLASS_OPS,
+ &ArrayBufferObjectProtoClassSpec
+};
+
+static JSObject*
+CreateArrayBufferPrototype(JSContext* cx, JSProtoKey key)
+{
+ return cx->global()->createBlankPrototype(cx, &ArrayBufferObjectProtoClass);
+}
+
+static const ClassOps ArrayBufferObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ ArrayBufferObject::finalize,
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ nullptr, /* construct */
+ ArrayBufferObject::trace,
+};
+
+static const JSFunctionSpec static_functions[] = {
+ JS_FN("isView", ArrayBufferObject::fun_isView, 1, 0),
+ JS_FN("slice", arraybuffer_static_slice, 3, 0),
+ JS_FS_END
+};
+
+static const JSPropertySpec static_properties[] = {
+ JS_SELF_HOSTED_SYM_GET(species, "ArrayBufferSpecies", 0),
+ JS_PS_END
+};
+
+
+static const JSFunctionSpec prototype_functions[] = {
+ JS_SELF_HOSTED_FN("slice", "ArrayBufferSlice", 2, 0),
+ JS_FS_END
+};
+
+static const JSPropertySpec prototype_properties[] = {
+ JS_PSG("byteLength", ArrayBufferObject::byteLengthGetter, 0),
+ JS_STRING_SYM_PS(toStringTag, "ArrayBuffer", JSPROP_READONLY),
+ JS_PS_END
+};
+
+static const ClassSpec ArrayBufferObjectClassSpec = {
+ GenericCreateConstructor<ArrayBufferObject::class_constructor, 1, gc::AllocKind::FUNCTION>,
+ CreateArrayBufferPrototype,
+ static_functions,
+ static_properties,
+ prototype_functions,
+ prototype_properties
+};
+
+static const ClassExtension ArrayBufferObjectClassExtension = {
+ nullptr, /* weakmapKeyDelegateOp */
+ ArrayBufferObject::objectMoved
+};
+
+const Class ArrayBufferObject::class_ = {
+ "ArrayBuffer",
+ JSCLASS_DELAY_METADATA_BUILDER |
+ JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_ArrayBuffer) |
+ JSCLASS_BACKGROUND_FINALIZE,
+ &ArrayBufferObjectClassOps,
+ &ArrayBufferObjectClassSpec,
+ &ArrayBufferObjectClassExtension
+};
+
+bool
+js::IsArrayBuffer(HandleValue v)
+{
+ return v.isObject() && v.toObject().is<ArrayBufferObject>();
+}
+
+bool
+js::IsArrayBuffer(HandleObject obj)
+{
+ return obj->is<ArrayBufferObject>();
+}
+
+bool
+js::IsArrayBuffer(JSObject* obj)
+{
+ return obj->is<ArrayBufferObject>();
+}
+
+ArrayBufferObject&
+js::AsArrayBuffer(HandleObject obj)
+{
+ MOZ_ASSERT(IsArrayBuffer(obj));
+ return obj->as<ArrayBufferObject>();
+}
+
+ArrayBufferObject&
+js::AsArrayBuffer(JSObject* obj)
+{
+ MOZ_ASSERT(IsArrayBuffer(obj));
+ return obj->as<ArrayBufferObject>();
+}
+
+MOZ_ALWAYS_INLINE bool
+ArrayBufferObject::byteLengthGetterImpl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(IsArrayBuffer(args.thisv()));
+ args.rval().setInt32(args.thisv().toObject().as<ArrayBufferObject>().byteLength());
+ return true;
+}
+
+bool
+ArrayBufferObject::byteLengthGetter(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsArrayBuffer, byteLengthGetterImpl>(cx, args);
+}
+
+/*
+ * ArrayBuffer.isView(obj); ES6 (Dec 2013 draft) 24.1.3.1
+ */
+bool
+ArrayBufferObject::fun_isView(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(args.get(0).isObject() &&
+ JS_IsArrayBufferViewObject(&args.get(0).toObject()));
+ return true;
+}
+
+/*
+ * new ArrayBuffer(byteLength)
+ */
+bool
+ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "ArrayBuffer"))
+ return false;
+
+ int32_t nbytes = 0;
+ if (argc > 0 && !ToInt32(cx, args[0], &nbytes))
+ return false;
+
+ if (nbytes < 0) {
+ /*
+ * We're just not going to support arrays that are bigger than what will fit
+ * as an integer value; if someone actually ever complains (validly), then we
+ * can fix.
+ */
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
+ return false;
+ }
+
+ RootedObject proto(cx);
+ RootedObject newTarget(cx, &args.newTarget().toObject());
+ if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
+ return false;
+
+ JSObject* bufobj = create(cx, uint32_t(nbytes), proto);
+ if (!bufobj)
+ return false;
+ args.rval().setObject(*bufobj);
+ return true;
+}
+
+static ArrayBufferObject::BufferContents
+AllocateArrayBufferContents(JSContext* cx, uint32_t nbytes)
+{
+ uint8_t* p = cx->runtime()->pod_callocCanGC<uint8_t>(nbytes);
+ if (!p)
+ ReportOutOfMemory(cx);
+
+ return ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(p);
+}
+
+static void
+NoteViewBufferWasDetached(ArrayBufferViewObject* view,
+ ArrayBufferObject::BufferContents newContents,
+ JSContext* cx)
+{
+ view->notifyBufferDetached(cx, newContents.data());
+
+ // Notify compiled jit code that the base pointer has moved.
+ MarkObjectStateChange(cx, view);
+}
+
+/* static */ void
+ArrayBufferObject::detach(JSContext* cx, Handle<ArrayBufferObject*> buffer,
+ BufferContents newContents)
+{
+ assertSameCompartment(cx, buffer);
+ MOZ_ASSERT(!buffer->isPreparedForAsmJS());
+
+ // When detaching buffers where we don't know all views, the new data must
+ // match the old data. All missing views are typed objects, which do not
+ // expect their data to ever change.
+ MOZ_ASSERT_IF(buffer->forInlineTypedObject(),
+ newContents.data() == buffer->dataPointer());
+
+ // When detaching a buffer with typed object views, any jitcode accessing
+ // such views must be deoptimized so that detachment checks are performed.
+ // This is done by setting a compartment-wide flag indicating that buffers
+ // with typed object views have been detached.
+ if (buffer->hasTypedObjectViews()) {
+ // Make sure the global object's group has been instantiated, so the
+ // flag change will be observed.
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (!cx->global()->getGroup(cx))
+ oomUnsafe.crash("ArrayBufferObject::detach");
+ MarkObjectGroupFlags(cx, cx->global(), OBJECT_FLAG_TYPED_OBJECT_HAS_DETACHED_BUFFER);
+ cx->compartment()->detachedTypedObjects = 1;
+ }
+
+ // Update all views of the buffer to account for the buffer having been
+ // detached, and clear the buffer's data and list of views.
+
+ auto& innerViews = cx->compartment()->innerViews;
+ if (InnerViewTable::ViewVector* views = innerViews.maybeViewsUnbarriered(buffer)) {
+ for (size_t i = 0; i < views->length(); i++)
+ NoteViewBufferWasDetached((*views)[i], newContents, cx);
+ innerViews.removeViews(buffer);
+ }
+ if (buffer->firstView()) {
+ if (buffer->forInlineTypedObject()) {
+ // The buffer points to inline data in its first view, so to keep
+ // this pointer alive we don't clear out the first view.
+ MOZ_ASSERT(buffer->firstView()->is<InlineTransparentTypedObject>());
+ } else {
+ NoteViewBufferWasDetached(buffer->firstView(), newContents, cx);
+ buffer->setFirstView(nullptr);
+ }
+ }
+
+ if (newContents.data() != buffer->dataPointer())
+ buffer->setNewData(cx->runtime()->defaultFreeOp(), newContents, OwnsData);
+
+ buffer->setByteLength(0);
+ buffer->setIsDetached();
+}
+
+void
+ArrayBufferObject::setNewData(FreeOp* fop, BufferContents newContents, OwnsState ownsState)
+{
+ if (ownsData()) {
+ MOZ_ASSERT(newContents.data() != dataPointer());
+ releaseData(fop);
+ }
+
+ setDataPointer(newContents, ownsState);
+}
+
+// This is called *only* from changeContents(), below.
+// By construction, every view parameter will be mapping unshared memory (an ArrayBuffer).
+// Hence no reason to worry about shared memory here.
+
+void
+ArrayBufferObject::changeViewContents(JSContext* cx, ArrayBufferViewObject* view,
+ uint8_t* oldDataPointer, BufferContents newContents)
+{
+ MOZ_ASSERT(!view->isSharedMemory());
+
+ // 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).
+ JS::AutoCheckCannotGC nogc(cx);
+ uint8_t* viewDataPointer = view->dataPointerUnshared(nogc);
+ if (viewDataPointer) {
+ MOZ_ASSERT(newContents);
+ ptrdiff_t offset = viewDataPointer - oldDataPointer;
+ viewDataPointer = static_cast<uint8_t*>(newContents.data()) + offset;
+ view->setDataPointerUnshared(viewDataPointer);
+ }
+
+ // Notify compiled jit code that the base pointer has moved.
+ MarkObjectStateChange(cx, view);
+}
+
+// BufferContents is specific to ArrayBuffer, hence it will not represent shared memory.
+
+void
+ArrayBufferObject::changeContents(JSContext* cx, BufferContents newContents,
+ OwnsState ownsState)
+{
+ MOZ_RELEASE_ASSERT(!isWasm());
+ MOZ_ASSERT(!forInlineTypedObject());
+
+ // Change buffer contents.
+ uint8_t* oldDataPointer = dataPointer();
+ setNewData(cx->runtime()->defaultFreeOp(), newContents, ownsState);
+
+ // Update all views.
+ auto& innerViews = cx->compartment()->innerViews;
+ if (InnerViewTable::ViewVector* views = 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);
+}
+
+/*
+ * Wasm Raw Buf Linear Memory Structure
+ *
+ * The linear heap in Wasm is an mmaped array buffer. Several
+ * constants manage its lifetime:
+ *
+ * - length - the wasm-visible current length of the buffer. Acesses in the
+ * range [0, length] succeed. May only increase
+ *
+ * - boundsCheckLimit - when !WASM_HUGE_MEMORY, the size against which we
+ * perform bounds checks. It is always a constant offset smaller than
+ * mappedSize. Currently that constant offset is 0.
+ *
+ * - max - the optional declared limit on how much length can grow.
+ *
+ * - mappedSize - the actual mmaped size. Access in the range
+ * [0, mappedSize] will either succeed, or be handled by the wasm signal
+ * handlers.
+ *
+ * The below diagram shows the layout of the wams heap. The wasm-visible
+ * portion of the heap starts at 0. There is one extra page prior to the
+ * start of the wasm heap which contains the WasmArrayRawBuffer struct at
+ * its end. (i.e. right before the start of the WASM heap).
+ *
+ * WasmArrayRawBuffer
+ * \ ArrayBufferObject::dataPointer()
+ * \ /
+ * \ |
+ * ______|_|____________________________________________________________
+ * |______|_|______________|___________________|____________|____________|
+ * 0 length maxSize boundsCheckLimit mappedSize
+ *
+ * \_______________________/
+ * COMMITED
+ * \____________________________________________/
+ * SLOP
+ * \_____________________________________________________________________/
+ * MAPPED
+ *
+ * Invariants:
+ * - length only increases
+ * - 0 <= length <= maxSize (if present) <= boundsCheckLimit <= mappedSize
+ * - on ARM boundsCheckLimit must be a valid ARM immediate.
+ * - if maxSize is not specified, boundsCheckLimit/mappedSize may grow. They are
+ * otherwise constant.
+ *
+ * NOTE: For asm.js on non-x64 we guarantee that
+ *
+ * length == maxSize == boundsCheckLimit == mappedSize
+ *
+ * That is, signal handlers will not be invoked, since they cannot emulate
+ * asm.js accesses on non-x64 architectures.
+ *
+ * The region between length and mappedSize is the SLOP - an area where we use
+ * signal handlers to catch things that slip by bounds checks. Logically it has
+ * two parts:
+ *
+ * - from length to boundsCheckLimit - this part of the SLOP serves to catch
+ * accesses to memory we have reserved but not yet grown into. This allows us
+ * to grow memory up to max (when present) without having to patch/update the
+ * bounds checks.
+ *
+ * - from boundsCheckLimit to mappedSize - (Note: In current patch 0) - this
+ * part of the SLOP allows us to bounds check against base pointers and fold
+ * some constant offsets inside loads. This enables better Bounds
+ * Check Elimination.
+ *
+ */
+
+class js::WasmArrayRawBuffer
+{
+ Maybe<uint32_t> maxSize_;
+ size_t mappedSize_;
+
+ protected:
+ WasmArrayRawBuffer(uint8_t* buffer, uint32_t length, Maybe<uint32_t> maxSize, size_t mappedSize)
+ : maxSize_(maxSize), mappedSize_(mappedSize)
+ {
+ MOZ_ASSERT(buffer == dataPointer());
+ }
+
+ public:
+ static WasmArrayRawBuffer* Allocate(uint32_t numBytes, Maybe<uint32_t> maxSize);
+ static void Release(void* mem);
+
+ uint8_t* dataPointer() {
+ uint8_t* ptr = reinterpret_cast<uint8_t*>(this);
+ return ptr + sizeof(WasmArrayRawBuffer);
+ }
+
+ uint8_t* basePointer() {
+ return dataPointer() - gc::SystemPageSize();
+ }
+
+ size_t mappedSize() const {
+ return mappedSize_;
+ }
+
+ Maybe<uint32_t> maxSize() const {
+ return maxSize_;
+ }
+
+ size_t allocatedBytes() const {
+ return mappedSize_ + gc::SystemPageSize();
+ }
+
+#ifndef WASM_HUGE_MEMORY
+ uint32_t boundsCheckLimit() const {
+ MOZ_ASSERT(mappedSize_ <= UINT32_MAX);
+ MOZ_ASSERT(mappedSize_ >= wasm::GuardSize);
+ MOZ_ASSERT(wasm::IsValidBoundsCheckImmediate(mappedSize_ - wasm::GuardSize));
+ return mappedSize_ - wasm::GuardSize;
+ }
+#endif
+
+ MOZ_MUST_USE bool growToSizeInPlace(uint32_t oldSize, uint32_t newSize) {
+ MOZ_ASSERT(newSize >= oldSize);
+ MOZ_ASSERT_IF(maxSize(), newSize <= maxSize().value());
+ MOZ_ASSERT(newSize <= mappedSize());
+
+ uint32_t delta = newSize - oldSize;
+ MOZ_ASSERT(delta % wasm::PageSize == 0);
+
+ uint8_t* dataEnd = dataPointer() + oldSize;
+ MOZ_ASSERT(uintptr_t(dataEnd) % gc::SystemPageSize() == 0);
+# ifdef XP_WIN
+ if (delta && !VirtualAlloc(dataEnd, delta, MEM_COMMIT, PAGE_READWRITE))
+ return false;
+# else // XP_WIN
+ if (delta && mprotect(dataEnd, delta, PROT_READ | PROT_WRITE))
+ return false;
+# endif // !XP_WIN
+
+# if defined(MOZ_VALGRIND) && defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
+ VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)dataEnd, delta);
+# endif
+
+ MemProfiler::SampleNative(dataEnd, delta);
+ return true;
+ }
+
+#ifndef WASM_HUGE_MEMORY
+ bool extendMappedSize(uint32_t maxSize) {
+ size_t newMappedSize = wasm::ComputeMappedSize(maxSize);
+ MOZ_ASSERT(mappedSize_ <= newMappedSize);
+ if (mappedSize_ == newMappedSize)
+ return true;
+
+# ifdef XP_WIN
+ uint8_t* mappedEnd = dataPointer() + mappedSize_;
+ uint32_t delta = newMappedSize - mappedSize_;
+ if (!VirtualAlloc(mappedEnd, delta, MEM_RESERVE, PAGE_NOACCESS))
+ return false;
+# elif defined(XP_LINUX)
+ // Note this will not move memory (no MREMAP_MAYMOVE specified)
+ if (MAP_FAILED == mremap(dataPointer(), mappedSize_, newMappedSize, 0))
+ return false;
+# else
+ // No mechanism for remapping on MacOS and other Unices. Luckily
+ // shouldn't need it here as most of these are 64-bit.
+ return false;
+# endif
+
+ mappedSize_ = newMappedSize;
+ return true;
+ }
+
+ // Try and grow the mapped region of memory. Does not changes current size.
+ // Does not move memory if no space to grow.
+ void tryGrowMaxSizeInPlace(uint32_t deltaMaxSize) {
+ CheckedInt<uint32_t> newMaxSize = maxSize_.value();
+ newMaxSize += deltaMaxSize;
+ MOZ_ASSERT(newMaxSize.isValid());
+ MOZ_ASSERT(newMaxSize.value() % wasm::PageSize == 0);
+
+ if (!extendMappedSize(newMaxSize.value()))
+ return;
+
+ maxSize_ = Some(newMaxSize.value());
+ }
+#endif // WASM_HUGE_MEMORY
+};
+
+/* static */ WasmArrayRawBuffer*
+WasmArrayRawBuffer::Allocate(uint32_t numBytes, Maybe<uint32_t> maxSize)
+{
+ size_t mappedSize;
+#ifdef WASM_HUGE_MEMORY
+ mappedSize = wasm::HugeMappedSize;
+#else
+ mappedSize = wasm::ComputeMappedSize(maxSize.valueOr(numBytes));
+#endif
+
+ MOZ_RELEASE_ASSERT(mappedSize <= SIZE_MAX - gc::SystemPageSize());
+ MOZ_RELEASE_ASSERT(numBytes <= maxSize.valueOr(UINT32_MAX));
+ MOZ_ASSERT(numBytes % gc::SystemPageSize() == 0);
+ MOZ_ASSERT(mappedSize % gc::SystemPageSize() == 0);
+
+ uint64_t mappedSizeWithHeader = mappedSize + gc::SystemPageSize();
+ uint64_t numBytesWithHeader = numBytes + gc::SystemPageSize();
+
+# ifdef XP_WIN
+ void* data = VirtualAlloc(nullptr, (size_t) mappedSizeWithHeader, MEM_RESERVE, PAGE_NOACCESS);
+ if (!data)
+ return nullptr;
+
+ if (!VirtualAlloc(data, numBytesWithHeader, MEM_COMMIT, PAGE_READWRITE)) {
+ VirtualFree(data, 0, MEM_RELEASE);
+ return nullptr;
+ }
+# else // XP_WIN
+ void* data = MozTaggedAnonymousMmap(nullptr, (size_t) mappedSizeWithHeader, PROT_NONE,
+ MAP_PRIVATE | MAP_ANON, -1, 0, "wasm-reserved");
+ if (data == MAP_FAILED)
+ return nullptr;
+
+ // Note we will waste a page on zero-sized memories here
+ if (mprotect(data, numBytesWithHeader, PROT_READ | PROT_WRITE)) {
+ munmap(data, mappedSizeWithHeader);
+ return nullptr;
+ }
+# endif // !XP_WIN
+ MemProfiler::SampleNative(data, numBytesWithHeader);
+
+# if defined(MOZ_VALGRIND) && defined(VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE)
+ VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE((unsigned char*)data + numBytesWithHeader,
+ mappedSizeWithHeader - numBytesWithHeader);
+# endif
+
+ uint8_t* base = reinterpret_cast<uint8_t*>(data) + gc::SystemPageSize();
+ uint8_t* header = base - sizeof(WasmArrayRawBuffer);
+
+ auto rawBuf = new (header) WasmArrayRawBuffer(base, numBytes, maxSize, mappedSize);
+ return rawBuf;
+}
+
+/* static */ void
+WasmArrayRawBuffer::Release(void* mem)
+{
+ WasmArrayRawBuffer* header = (WasmArrayRawBuffer*)((uint8_t*)mem - sizeof(WasmArrayRawBuffer));
+ uint8_t* base = header->basePointer();
+ MOZ_RELEASE_ASSERT(header->mappedSize() <= SIZE_MAX - gc::SystemPageSize());
+ size_t mappedSizeWithHeader = header->mappedSize() + gc::SystemPageSize();
+
+ MemProfiler::RemoveNative(base);
+# ifdef XP_WIN
+ VirtualFree(base, 0, MEM_RELEASE);
+# else // XP_WIN
+ munmap(base, mappedSizeWithHeader);
+# endif // !XP_WIN
+
+# if defined(MOZ_VALGRIND) && defined(VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE)
+ VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(base, mappedSizeWithHeader);
+# endif
+}
+
+WasmArrayRawBuffer*
+ArrayBufferObject::BufferContents::wasmBuffer() const
+{
+ MOZ_RELEASE_ASSERT(kind_ == WASM);
+ return (WasmArrayRawBuffer*)(data_ - sizeof(WasmArrayRawBuffer));
+}
+
+#define ROUND_UP(v, a) ((v) % (a) == 0 ? (v) : v + a - ((v) % (a)))
+
+/* static */ ArrayBufferObject*
+ArrayBufferObject::createForWasm(JSContext* cx, uint32_t initialSize, Maybe<uint32_t> maxSize)
+{
+ MOZ_ASSERT(initialSize % wasm::PageSize == 0);
+ MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
+
+ // Prevent applications specifying a large max (like UINT32_MAX) from
+ // unintentially OOMing the browser on 32-bit: they just want "a lot of
+ // memory". Maintain the invariant that initialSize <= maxSize.
+ if (sizeof(void*) == 4 && maxSize) {
+ static const uint32_t OneGiB = 1 << 30;
+ uint32_t clamp = Max(OneGiB, initialSize);
+ maxSize = Some(Min(clamp, maxSize.value()));
+ }
+
+ RootedArrayBufferObject buffer(cx, ArrayBufferObject::createEmpty(cx));
+ if (!buffer)
+ return nullptr;
+
+ // Try to reserve the maximum requested memory
+ WasmArrayRawBuffer* wasmBuf = WasmArrayRawBuffer::Allocate(initialSize, maxSize);
+ if (!wasmBuf) {
+#ifdef WASM_HUGE_MEMORY
+ ReportOutOfMemory(cx);
+ return nullptr;
+#else
+ // If we fail, and have a maxSize, try to reserve the biggest chunk in
+ // the range [initialSize, maxSize) using log backoff.
+ if (!maxSize) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ uint32_t cur = maxSize.value() / 2;
+
+ for (; cur > initialSize; cur /= 2) {
+ wasmBuf = WasmArrayRawBuffer::Allocate(initialSize, Some(ROUND_UP(cur, wasm::PageSize)));
+ if (wasmBuf)
+ break;
+ }
+
+ if (!wasmBuf) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ // Try to grow our chunk as much as possible.
+ for (size_t d = cur / 2; d >= wasm::PageSize; d /= 2)
+ wasmBuf->tryGrowMaxSizeInPlace(ROUND_UP(d, wasm::PageSize));
+#endif
+ }
+
+ auto contents = BufferContents::create<WASM>(wasmBuf->dataPointer());
+ buffer->initialize(initialSize, contents, OwnsData);
+ cx->zone()->updateMallocCounter(wasmBuf->mappedSize());
+ return buffer;
+}
+
+// Note this function can return false with or without an exception pending. The
+// asm.js caller checks cx->isExceptionPending before propagating failure.
+// Returning false without throwing means that asm.js linking will fail which
+// will recompile as non-asm.js.
+/* static */ bool
+ArrayBufferObject::prepareForAsmJS(JSContext* cx, Handle<ArrayBufferObject*> buffer, bool needGuard)
+{
+#ifdef WASM_HUGE_MEMORY
+ MOZ_ASSERT(needGuard);
+#endif
+ MOZ_ASSERT(buffer->byteLength() % wasm::PageSize == 0);
+ MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
+
+ if (buffer->forInlineTypedObject())
+ return false;
+
+ if (needGuard) {
+ if (buffer->isWasm() && buffer->isPreparedForAsmJS())
+ return true;
+
+ // Non-prepared-for-asm.js wasm buffers can be detached at any time.
+ // This error can only be triggered for SIMD.js (which isn't shipping)
+ // on !WASM_HUGE_MEMORY so this error is only visible in testing.
+ if (buffer->isWasm() || buffer->isPreparedForAsmJS())
+ return false;
+
+ uint32_t length = buffer->byteLength();
+ WasmArrayRawBuffer* wasmBuf = WasmArrayRawBuffer::Allocate(length, Some(length));
+ if (!wasmBuf) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ void* data = wasmBuf->dataPointer();
+ memcpy(data, buffer->dataPointer(), length);
+
+ // Swap the new elements into the ArrayBufferObject. Mark the
+ // ArrayBufferObject so we don't do this again.
+ buffer->changeContents(cx, BufferContents::create<WASM>(data), OwnsData);
+ buffer->setIsPreparedForAsmJS();
+ MOZ_ASSERT(data == buffer->dataPointer());
+ cx->zone()->updateMallocCounter(wasmBuf->mappedSize());
+ return true;
+ }
+
+ if (!buffer->isWasm() && buffer->isPreparedForAsmJS())
+ return true;
+
+ // Non-prepared-for-asm.js wasm buffers can be detached at any time.
+ if (buffer->isWasm())
+ return false;
+
+ if (!buffer->ownsData()) {
+ BufferContents contents = AllocateArrayBufferContents(cx, buffer->byteLength());
+ if (!contents)
+ return false;
+ memcpy(contents.data(), buffer->dataPointer(), buffer->byteLength());
+ buffer->changeContents(cx, contents, OwnsData);
+ }
+
+ buffer->setIsPreparedForAsmJS();
+ return true;
+}
+
+ArrayBufferObject::BufferContents
+ArrayBufferObject::createMappedContents(int fd, size_t offset, size_t length)
+{
+ void* data = AllocateMappedContent(fd, offset, length, ARRAY_BUFFER_ALIGNMENT);
+ MemProfiler::SampleNative(data, length);
+ return BufferContents::create<MAPPED>(data);
+}
+
+uint8_t*
+ArrayBufferObject::inlineDataPointer() const
+{
+ return static_cast<uint8_t*>(fixedData(JSCLASS_RESERVED_SLOTS(&class_)));
+}
+
+uint8_t*
+ArrayBufferObject::dataPointer() const
+{
+ return static_cast<uint8_t*>(getSlot(DATA_SLOT).toPrivate());
+}
+
+SharedMem<uint8_t*>
+ArrayBufferObject::dataPointerShared() const
+{
+ return SharedMem<uint8_t*>::unshared(getSlot(DATA_SLOT).toPrivate());
+}
+
+void
+ArrayBufferObject::releaseData(FreeOp* fop)
+{
+ MOZ_ASSERT(ownsData());
+
+ switch (bufferKind()) {
+ case PLAIN:
+ fop->free_(dataPointer());
+ break;
+ case MAPPED:
+ MemProfiler::RemoveNative(dataPointer());
+ DeallocateMappedContent(dataPointer(), byteLength());
+ break;
+ case WASM:
+ WasmArrayRawBuffer::Release(dataPointer());
+ break;
+ case KIND_MASK:
+ MOZ_CRASH("bad bufferKind()");
+ }
+}
+
+void
+ArrayBufferObject::setDataPointer(BufferContents contents, OwnsState ownsData)
+{
+ setSlot(DATA_SLOT, PrivateValue(contents.data()));
+ setOwnsData(ownsData);
+ setFlags((flags() & ~KIND_MASK) | contents.kind());
+}
+
+uint32_t
+ArrayBufferObject::byteLength() const
+{
+ return getSlot(BYTE_LENGTH_SLOT).toInt32();
+}
+
+void
+ArrayBufferObject::setByteLength(uint32_t length)
+{
+ MOZ_ASSERT(length <= INT32_MAX);
+ setSlot(BYTE_LENGTH_SLOT, Int32Value(length));
+}
+
+size_t
+ArrayBufferObject::wasmMappedSize() const
+{
+ if (isWasm())
+ return contents().wasmBuffer()->mappedSize();
+ return byteLength();
+}
+
+size_t
+js::WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf)
+{
+ if (buf->is<ArrayBufferObject>())
+ return buf->as<ArrayBufferObject>().wasmMappedSize();
+#ifdef WASM_HUGE_MEMORY
+ return wasm::HugeMappedSize;
+#else
+ return buf->as<SharedArrayBufferObject>().byteLength();
+#endif
+}
+
+Maybe<uint32_t>
+ArrayBufferObject::wasmMaxSize() const
+{
+ if (isWasm())
+ return contents().wasmBuffer()->maxSize();
+ else
+ return Some<uint32_t>(byteLength());
+}
+
+Maybe<uint32_t>
+js::WasmArrayBufferMaxSize(const ArrayBufferObjectMaybeShared* buf)
+{
+ if (buf->is<ArrayBufferObject>())
+ return buf->as<ArrayBufferObject>().wasmMaxSize();
+
+ return Some(buf->as<SharedArrayBufferObject>().byteLength());
+}
+
+/* static */ bool
+ArrayBufferObject::wasmGrowToSizeInPlace(uint32_t newSize,
+ HandleArrayBufferObject oldBuf,
+ MutableHandleArrayBufferObject newBuf,
+ JSContext* cx)
+{
+ // On failure, do not throw and ensure that the original buffer is
+ // unmodified and valid. After WasmArrayRawBuffer::growToSizeInPlace(), the
+ // wasm-visible length of the buffer has been increased so it must be the
+ // last fallible operation.
+
+ // byteLength can be at most INT32_MAX.
+ if (newSize > INT32_MAX)
+ return false;
+
+ newBuf.set(ArrayBufferObject::createEmpty(cx));
+ if (!newBuf) {
+ cx->clearPendingException();
+ return false;
+ }
+
+ if (!oldBuf->contents().wasmBuffer()->growToSizeInPlace(oldBuf->byteLength(), newSize))
+ return false;
+
+ bool hasStealableContents = true;
+ BufferContents contents = ArrayBufferObject::stealContents(cx, oldBuf, hasStealableContents);
+ MOZ_ASSERT(contents);
+ newBuf->initialize(newSize, contents, OwnsData);
+ return true;
+}
+
+#ifndef WASM_HUGE_MEMORY
+/* static */ bool
+ArrayBufferObject::wasmMovingGrowToSize(uint32_t newSize,
+ HandleArrayBufferObject oldBuf,
+ MutableHandleArrayBufferObject newBuf,
+ JSContext* cx)
+{
+ // On failure, do not throw and ensure that the original buffer is
+ // unmodified and valid.
+
+ // byteLength can be at most INT32_MAX.
+ if (newSize > INT32_MAX)
+ return false;
+
+ if (newSize <= oldBuf->wasmBoundsCheckLimit() ||
+ oldBuf->contents().wasmBuffer()->extendMappedSize(newSize))
+ {
+ return wasmGrowToSizeInPlace(newSize, oldBuf, newBuf, cx);
+ }
+
+ newBuf.set(ArrayBufferObject::createEmpty(cx));
+ if (!newBuf) {
+ cx->clearPendingException();
+ return false;
+ }
+
+ WasmArrayRawBuffer* newRawBuf = WasmArrayRawBuffer::Allocate(newSize, Nothing());
+ if (!newRawBuf)
+ return false;
+ BufferContents contents = BufferContents::create<WASM>(newRawBuf->dataPointer());
+ newBuf->initialize(newSize, contents, OwnsData);
+
+ memcpy(newBuf->dataPointer(), oldBuf->dataPointer(), oldBuf->byteLength());
+ ArrayBufferObject::detach(cx, oldBuf, BufferContents::createPlain(nullptr));
+ return true;
+}
+
+uint32_t
+ArrayBufferObject::wasmBoundsCheckLimit() const
+{
+ if (isWasm())
+ return contents().wasmBuffer()->boundsCheckLimit();
+ else
+ return byteLength();
+}
+
+uint32_t
+ArrayBufferObjectMaybeShared::wasmBoundsCheckLimit() const
+{
+ if (is<ArrayBufferObject>())
+ return as<ArrayBufferObject>().wasmBoundsCheckLimit();
+
+ return as<SharedArrayBufferObject>().byteLength();
+}
+#endif
+
+uint32_t
+ArrayBufferObject::flags() const
+{
+ return uint32_t(getSlot(FLAGS_SLOT).toInt32());
+}
+
+void
+ArrayBufferObject::setFlags(uint32_t flags)
+{
+ setSlot(FLAGS_SLOT, Int32Value(flags));
+}
+
+ArrayBufferObject*
+ArrayBufferObject::create(JSContext* cx, uint32_t nbytes, BufferContents contents,
+ OwnsState ownsState /* = OwnsData */,
+ HandleObject proto /* = nullptr */,
+ NewObjectKind newKind /* = GenericObject */)
+{
+ MOZ_ASSERT_IF(contents.kind() == MAPPED, contents);
+
+ // 24.1.1.1, step 3 (Inlined 6.2.6.1 CreateByteDataBlock, step 2).
+ // Refuse to allocate too large buffers, currently limited to ~2 GiB.
+ if (nbytes > INT32_MAX) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH);
+ return nullptr;
+ }
+
+ // If we need to allocate data, try to use a larger object size class so
+ // that the array buffer's data can be allocated inline with the object.
+ // The extra space will be left unused by the object's fixed slots and
+ // available for the buffer's data, see NewObject().
+ size_t reservedSlots = JSCLASS_RESERVED_SLOTS(&class_);
+
+ size_t nslots = reservedSlots;
+ bool allocated = false;
+ if (contents) {
+ if (ownsState == OwnsData) {
+ // The ABO is taking ownership, so account the bytes against the zone.
+ size_t nAllocated = nbytes;
+ if (contents.kind() == MAPPED)
+ nAllocated = JS_ROUNDUP(nbytes, js::gc::SystemPageSize());
+ else if (contents.kind() == WASM)
+ nAllocated = contents.wasmBuffer()->allocatedBytes();
+ cx->zone()->updateMallocCounter(nAllocated);
+ }
+ } else {
+ MOZ_ASSERT(ownsState == OwnsData);
+ size_t usableSlots = NativeObject::MAX_FIXED_SLOTS - reservedSlots;
+ if (nbytes <= usableSlots * sizeof(Value)) {
+ int newSlots = (nbytes - 1) / sizeof(Value) + 1;
+ MOZ_ASSERT(int(nbytes) <= newSlots * int(sizeof(Value)));
+ nslots = reservedSlots + newSlots;
+ contents = BufferContents::createPlain(nullptr);
+ } else {
+ contents = AllocateArrayBufferContents(cx, nbytes);
+ if (!contents)
+ return nullptr;
+ allocated = true;
+ }
+ }
+
+ MOZ_ASSERT(!(class_.flags & JSCLASS_HAS_PRIVATE));
+ gc::AllocKind allocKind = GetGCObjectKind(nslots);
+
+ AutoSetNewObjectMetadata metadata(cx);
+ Rooted<ArrayBufferObject*> obj(cx,
+ NewObjectWithClassProto<ArrayBufferObject>(cx, proto, allocKind, newKind));
+ if (!obj) {
+ if (allocated)
+ js_free(contents.data());
+ return nullptr;
+ }
+
+ MOZ_ASSERT(obj->getClass() == &class_);
+ MOZ_ASSERT(!gc::IsInsideNursery(obj));
+
+ if (!contents) {
+ void* data = obj->inlineDataPointer();
+ memset(data, 0, nbytes);
+ obj->initialize(nbytes, BufferContents::createPlain(data), DoesntOwnData);
+ } else {
+ obj->initialize(nbytes, contents, ownsState);
+ }
+
+ return obj;
+}
+
+ArrayBufferObject*
+ArrayBufferObject::create(JSContext* cx, uint32_t nbytes,
+ HandleObject proto /* = nullptr */,
+ NewObjectKind newKind /* = GenericObject */)
+{
+ return create(cx, nbytes, BufferContents::createPlain(nullptr),
+ OwnsState::OwnsData, proto);
+}
+
+ArrayBufferObject*
+ArrayBufferObject::createEmpty(JSContext* cx)
+{
+ AutoSetNewObjectMetadata metadata(cx);
+ ArrayBufferObject* obj = NewObjectWithClassProto<ArrayBufferObject>(cx, nullptr);
+ if (!obj)
+ return nullptr;
+
+ obj->initEmpty();
+ return obj;
+}
+
+bool
+ArrayBufferObject::createDataViewForThisImpl(JSContext* cx, const CallArgs& args)
+{
+ MOZ_ASSERT(IsArrayBuffer(args.thisv()));
+
+ /*
+ * This method is only called for |DataView(alienBuf, ...)| which calls
+ * this as |createDataViewForThis.call(alienBuf, byteOffset, byteLength,
+ * DataView.prototype)|,
+ * ergo there must be exactly 3 arguments.
+ */
+ MOZ_ASSERT(args.length() == 3);
+
+ uint32_t byteOffset = args[0].toPrivateUint32();
+ uint32_t byteLength = args[1].toPrivateUint32();
+ Rooted<ArrayBufferObject*> buffer(cx, &args.thisv().toObject().as<ArrayBufferObject>());
+
+ /*
+ * Pop off the passed-along prototype and delegate to normal DataViewObject
+ * construction.
+ */
+ JSObject* obj = DataViewObject::create(cx, byteOffset, byteLength, buffer, &args[2].toObject());
+ if (!obj)
+ return false;
+ args.rval().setObject(*obj);
+ return true;
+}
+
+bool
+ArrayBufferObject::createDataViewForThis(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsArrayBuffer, createDataViewForThisImpl>(cx, args);
+}
+
+/* static */ ArrayBufferObject::BufferContents
+ArrayBufferObject::externalizeContents(JSContext* cx, Handle<ArrayBufferObject*> buffer,
+ bool hasStealableContents)
+{
+ MOZ_ASSERT(buffer->isPlain(), "Only support doing this on plain ABOs");
+ MOZ_ASSERT(!buffer->isDetached(), "must have contents to externalize");
+ MOZ_ASSERT_IF(hasStealableContents, buffer->hasStealableContents());
+
+ BufferContents contents(buffer->dataPointer(), buffer->bufferKind());
+
+ if (hasStealableContents) {
+ buffer->setOwnsData(DoesntOwnData);
+ return contents;
+ }
+
+ // Create a new chunk of memory to return since we cannot steal the
+ // existing contents away from the buffer.
+ BufferContents newContents = AllocateArrayBufferContents(cx, buffer->byteLength());
+ if (!newContents)
+ return BufferContents::createPlain(nullptr);
+ memcpy(newContents.data(), contents.data(), buffer->byteLength());
+ buffer->changeContents(cx, newContents, DoesntOwnData);
+
+ return newContents;
+}
+
+/* static */ ArrayBufferObject::BufferContents
+ArrayBufferObject::stealContents(JSContext* cx, Handle<ArrayBufferObject*> buffer,
+ bool hasStealableContents)
+{
+ // While wasm buffers cannot generally be transferred by content, the
+ // stealContents() is used internally by the impl of memory growth.
+ MOZ_ASSERT_IF(hasStealableContents, buffer->hasStealableContents() ||
+ (buffer->isWasm() && !buffer->isPreparedForAsmJS()));
+ assertSameCompartment(cx, buffer);
+
+ BufferContents oldContents(buffer->dataPointer(), buffer->bufferKind());
+
+ if (hasStealableContents) {
+ // Return the old contents and reset the detached buffer's data
+ // pointer. This pointer should never be accessed.
+ auto newContents = BufferContents::createPlain(nullptr);
+ buffer->setOwnsData(DoesntOwnData); // Do not free the stolen data.
+ ArrayBufferObject::detach(cx, buffer, newContents);
+ buffer->setOwnsData(DoesntOwnData); // Do not free the nullptr.
+ return oldContents;
+ }
+
+ // Create a new chunk of memory to return since we cannot steal the
+ // existing contents away from the buffer.
+ BufferContents contentsCopy = AllocateArrayBufferContents(cx, buffer->byteLength());
+ if (!contentsCopy)
+ return BufferContents::createPlain(nullptr);
+
+ if (buffer->byteLength() > 0)
+ memcpy(contentsCopy.data(), oldContents.data(), buffer->byteLength());
+ ArrayBufferObject::detach(cx, buffer, oldContents);
+ return contentsCopy;
+}
+
+/* static */ void
+ArrayBufferObject::addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSizeOf mallocSizeOf,
+ JS::ClassInfo* info)
+{
+ ArrayBufferObject& buffer = AsArrayBuffer(obj);
+
+ if (!buffer.ownsData())
+ return;
+
+ switch (buffer.bufferKind()) {
+ case PLAIN:
+ if (buffer.isPreparedForAsmJS())
+ info->objectsMallocHeapElementsAsmJS += mallocSizeOf(buffer.dataPointer());
+ else
+ info->objectsMallocHeapElementsNormal += mallocSizeOf(buffer.dataPointer());
+ break;
+ case MAPPED:
+ info->objectsNonHeapElementsNormal += buffer.byteLength();
+ break;
+ case WASM:
+ info->objectsNonHeapElementsWasm += buffer.byteLength();
+ MOZ_ASSERT(buffer.wasmMappedSize() >= buffer.byteLength());
+ info->wasmGuardPages += buffer.wasmMappedSize() - buffer.byteLength();
+ break;
+ case KIND_MASK:
+ MOZ_CRASH("bad bufferKind()");
+ }
+}
+
+/* static */ void
+ArrayBufferObject::finalize(FreeOp* fop, JSObject* obj)
+{
+ ArrayBufferObject& buffer = obj->as<ArrayBufferObject>();
+
+ if (buffer.ownsData())
+ buffer.releaseData(fop);
+}
+
+/* static */ void
+ArrayBufferObject::copyData(Handle<ArrayBufferObject*> toBuffer,
+ Handle<ArrayBufferObject*> fromBuffer,
+ uint32_t fromIndex, uint32_t count)
+{
+ MOZ_ASSERT(toBuffer->byteLength() >= count);
+ MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex);
+ MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count);
+
+ memcpy(toBuffer->dataPointer(), fromBuffer->dataPointer() + fromIndex, count);
+}
+
+/* static */ void
+ArrayBufferObject::trace(JSTracer* trc, JSObject* obj)
+{
+ // If this buffer is associated with an inline typed object,
+ // fix up the data pointer if the typed object was moved.
+ ArrayBufferObject& buf = obj->as<ArrayBufferObject>();
+
+ if (!buf.forInlineTypedObject())
+ return;
+
+ JSObject* view = MaybeForwarded(buf.firstView());
+ MOZ_ASSERT(view && view->is<InlineTransparentTypedObject>());
+
+ TraceManuallyBarrieredEdge(trc, &view, "array buffer inline typed object owner");
+ buf.setSlot(DATA_SLOT, PrivateValue(view->as<InlineTransparentTypedObject>().inlineTypedMem()));
+}
+
+/* static */ void
+ArrayBufferObject::objectMoved(JSObject* obj, const JSObject* old)
+{
+ ArrayBufferObject& dst = obj->as<ArrayBufferObject>();
+ const ArrayBufferObject& src = old->as<ArrayBufferObject>();
+
+ // Fix up possible inline data pointer.
+ if (src.hasInlineData())
+ dst.setSlot(DATA_SLOT, PrivateValue(dst.inlineDataPointer()));
+}
+
+ArrayBufferViewObject*
+ArrayBufferObject::firstView()
+{
+ return getSlot(FIRST_VIEW_SLOT).isObject()
+ ? static_cast<ArrayBufferViewObject*>(&getSlot(FIRST_VIEW_SLOT).toObject())
+ : nullptr;
+}
+
+void
+ArrayBufferObject::setFirstView(ArrayBufferViewObject* view)
+{
+ setSlot(FIRST_VIEW_SLOT, ObjectOrNullValue(view));
+}
+
+bool
+ArrayBufferObject::addView(JSContext* cx, JSObject* viewArg)
+{
+ // Note: we don't pass in an ArrayBufferViewObject as the argument due to
+ // tricky inheritance in the various view classes. View classes do not
+ // inherit from ArrayBufferViewObject so won't be upcast automatically.
+ MOZ_ASSERT(viewArg->is<ArrayBufferViewObject>() || viewArg->is<TypedObject>());
+ ArrayBufferViewObject* view = static_cast<ArrayBufferViewObject*>(viewArg);
+
+ if (!firstView()) {
+ setFirstView(view);
+ return true;
+ }
+ return cx->compartment()->innerViews.get().addView(cx, this, view);
+}
+
+/*
+ * InnerViewTable
+ */
+
+static size_t VIEW_LIST_MAX_LENGTH = 500;
+
+bool
+InnerViewTable::addView(JSContext* cx, ArrayBufferObject* buffer, ArrayBufferViewObject* view)
+{
+ // ArrayBufferObject entries are only added when there are multiple views.
+ MOZ_ASSERT(buffer->firstView());
+
+ if (!map.initialized() && !map.init()) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ Map::AddPtr p = map.lookupForAdd(buffer);
+
+ MOZ_ASSERT(!gc::IsInsideNursery(buffer));
+ bool addToNursery = nurseryKeysValid && gc::IsInsideNursery(view);
+
+ if (p) {
+ ViewVector& views = p->value();
+ MOZ_ASSERT(!views.empty());
+
+ 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;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!views.append(view)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ } else {
+ if (!map.add(p, buffer, ViewVector())) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+ // ViewVector has one inline element, so the first insertion is
+ // guaranteed to succeed.
+ MOZ_ALWAYS_TRUE(p->value().append(view));
+ }
+
+ if (addToNursery && !nurseryKeys.append(buffer))
+ nurseryKeysValid = false;
+
+ return true;
+}
+
+InnerViewTable::ViewVector*
+InnerViewTable::maybeViewsUnbarriered(ArrayBufferObject* buffer)
+{
+ if (!map.initialized())
+ return nullptr;
+
+ Map::Ptr p = map.lookup(buffer);
+ if (p)
+ return &p->value();
+ return nullptr;
+}
+
+void
+InnerViewTable::removeViews(ArrayBufferObject* buffer)
+{
+ Map::Ptr p = map.lookup(buffer);
+ MOZ_ASSERT(p);
+
+ map.remove(p);
+}
+
+/* static */ bool
+InnerViewTable::sweepEntry(JSObject** pkey, ViewVector& views)
+{
+ if (IsAboutToBeFinalizedUnbarriered(pkey))
+ return true;
+
+ MOZ_ASSERT(!views.empty());
+ for (size_t i = 0; i < views.length(); i++) {
+ if (IsAboutToBeFinalizedUnbarriered(&views[i])) {
+ views[i--] = views.back();
+ views.popBack();
+ }
+ }
+
+ return views.empty();
+}
+
+void
+InnerViewTable::sweep()
+{
+ MOZ_ASSERT(nurseryKeys.empty());
+ map.sweep();
+}
+
+void
+InnerViewTable::sweepAfterMinorGC()
+{
+ MOZ_ASSERT(needsSweepAfterMinorGC());
+
+ if (nurseryKeysValid) {
+ for (size_t i = 0; i < nurseryKeys.length(); i++) {
+ JSObject* buffer = MaybeForwarded(nurseryKeys[i]);
+ Map::Ptr p = map.lookup(buffer);
+ if (!p)
+ continue;
+
+ if (sweepEntry(&p->mutableKey(), p->value()))
+ map.remove(buffer);
+ }
+ nurseryKeys.clear();
+ } else {
+ // Do the required sweeping by looking at every map entry.
+ nurseryKeys.clear();
+ sweep();
+
+ 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 to move the object's data pointer if its owner was moved and
+ * stores its data inline.
+ */
+/* static */ void
+ArrayBufferViewObject::trace(JSTracer* trc, JSObject* objArg)
+{
+ NativeObject* obj = &objArg->as<NativeObject>();
+ HeapSlot& bufSlot = obj->getFixedSlotRef(TypedArrayObject::BUFFER_SLOT);
+ TraceEdge(trc, &bufSlot, "typedarray.buffer");
+
+ // Update obj's data pointer if it moved.
+ if (bufSlot.isObject()) {
+ if (IsArrayBuffer(&bufSlot.toObject())) {
+ ArrayBufferObject& buf = AsArrayBuffer(MaybeForwarded(&bufSlot.toObject()));
+ uint32_t offset = uint32_t(obj->getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT).toInt32());
+ MOZ_ASSERT(offset <= INT32_MAX);
+
+ if (buf.forInlineTypedObject()) {
+ MOZ_ASSERT(buf.dataPointer() != nullptr);
+
+ // The data is inline with an InlineTypedObject associated with the
+ // buffer. Get a new address for the typed object if it moved.
+ JSObject* view = buf.firstView();
+
+ // Mark the object to move it into the tenured space.
+ TraceManuallyBarrieredEdge(trc, &view, "typed array nursery owner");
+ MOZ_ASSERT(view->is<InlineTypedObject>());
+ MOZ_ASSERT(view != obj);
+
+ void* srcData = obj->getPrivate();
+ void* dstData = view->as<InlineTypedObject>().inlineTypedMemForGC() + offset;
+ obj->setPrivateUnbarriered(dstData);
+
+ // We can't use a direct forwarding pointer here, as there might
+ // not be enough bytes available, and other views might have data
+ // pointers whose forwarding pointers would overlap this one.
+ trc->runtime()->gc.nursery.maybeSetForwardingPointer(trc, srcData, dstData,
+ /* direct = */ false);
+ } else {
+ MOZ_ASSERT_IF(buf.dataPointer() == nullptr, offset == 0);
+
+ // The data may or may not be inline with the buffer. The buffer
+ // can only move during a compacting GC, in which case its
+ // objectMoved hook has already updated the buffer's data pointer.
+ obj->initPrivate(buf.dataPointer() + offset);
+ }
+ }
+ }
+}
+
+template <>
+bool
+JSObject::is<js::ArrayBufferViewObject>() const
+{
+ return is<DataViewObject>() || is<TypedArrayObject>();
+}
+
+template <>
+bool
+JSObject::is<js::ArrayBufferObjectMaybeShared>() const
+{
+ return is<ArrayBufferObject>() || is<SharedArrayBufferObject>();
+}
+
+void
+ArrayBufferViewObject::notifyBufferDetached(JSContext* cx, void* newData)
+{
+ if (is<DataViewObject>()) {
+ as<DataViewObject>().notifyBufferDetached(newData);
+ } else if (is<TypedArrayObject>()) {
+ if (as<TypedArrayObject>().isSharedMemory())
+ return;
+ as<TypedArrayObject>().notifyBufferDetached(cx, newData);
+ } else {
+ as<OutlineTypedObject>().notifyBufferDetached(newData);
+ }
+}
+
+uint8_t*
+ArrayBufferViewObject::dataPointerUnshared(const JS::AutoRequireNoGC& nogc)
+{
+ if (is<DataViewObject>())
+ return static_cast<uint8_t*>(as<DataViewObject>().dataPointer());
+ if (is<TypedArrayObject>()) {
+ MOZ_ASSERT(!as<TypedArrayObject>().isSharedMemory());
+ return static_cast<uint8_t*>(as<TypedArrayObject>().viewDataUnshared());
+ }
+ return as<TypedObject>().typedMem(nogc);
+}
+
+#ifdef DEBUG
+bool
+ArrayBufferViewObject::isSharedMemory()
+{
+ if (is<TypedArrayObject>())
+ return as<TypedArrayObject>().isSharedMemory();
+ return false;
+}
+#endif
+
+void
+ArrayBufferViewObject::setDataPointerUnshared(uint8_t* data)
+{
+ if (is<DataViewObject>()) {
+ as<DataViewObject>().setPrivate(data);
+ } else if (is<TypedArrayObject>()) {
+ MOZ_ASSERT(!as<TypedArrayObject>().isSharedMemory());
+ as<TypedArrayObject>().setPrivate(data);
+ } else if (is<OutlineTypedObject>()) {
+ as<OutlineTypedObject>().setData(data);
+ } else {
+ MOZ_CRASH();
+ }
+}
+
+/* static */ ArrayBufferObjectMaybeShared*
+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->as<TypedArrayObject>().bufferEither();
+ }
+ MOZ_ASSERT(thisObject->is<DataViewObject>());
+ return &thisObject->as<DataViewObject>().arrayBuffer();
+}
+
+/* JS Friend API */
+
+JS_FRIEND_API(bool)
+JS_IsArrayBufferViewObject(JSObject* obj)
+{
+ obj = CheckedUnwrap(obj);
+ return obj && obj->is<ArrayBufferViewObject>();
+}
+
+JS_FRIEND_API(JSObject*)
+js::UnwrapArrayBufferView(JSObject* obj)
+{
+ if (JSObject* unwrapped = CheckedUnwrap(obj))
+ return unwrapped->is<ArrayBufferViewObject>() ? unwrapped : nullptr;
+ return nullptr;
+}
+
+JS_FRIEND_API(uint32_t)
+JS_GetArrayBufferByteLength(JSObject* obj)
+{
+ obj = CheckedUnwrap(obj);
+ return obj ? AsArrayBuffer(obj).byteLength() : 0;
+}
+
+JS_FRIEND_API(uint8_t*)
+JS_GetArrayBufferData(JSObject* obj, bool* isSharedMemory, const JS::AutoCheckCannotGC&)
+{
+ obj = CheckedUnwrap(obj);
+ if (!obj)
+ return nullptr;
+ if (!IsArrayBuffer(obj))
+ return nullptr;
+ *isSharedMemory = false;
+ return AsArrayBuffer(obj).dataPointer();
+}
+
+JS_FRIEND_API(bool)
+JS_DetachArrayBuffer(JSContext* cx, HandleObject obj)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, obj);
+
+ if (!obj->is<ArrayBufferObject>()) {
+ JS_ReportErrorASCII(cx, "ArrayBuffer object required");
+ return false;
+ }
+
+ Rooted<ArrayBufferObject*> buffer(cx, &obj->as<ArrayBufferObject>());
+
+ if (buffer->isWasm() || buffer->isPreparedForAsmJS()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_NO_TRANSFER);
+ return false;
+ }
+
+ ArrayBufferObject::BufferContents newContents =
+ buffer->hasStealableContents() ? ArrayBufferObject::BufferContents::createPlain(nullptr)
+ : buffer->contents();
+
+ ArrayBufferObject::detach(cx, buffer, newContents);
+
+ return true;
+}
+
+JS_FRIEND_API(bool)
+JS_IsDetachedArrayBufferObject(JSObject* obj)
+{
+ obj = CheckedUnwrap(obj);
+ if (!obj)
+ return false;
+
+ return obj->is<ArrayBufferObject>() && obj->as<ArrayBufferObject>().isDetached();
+}
+
+JS_FRIEND_API(JSObject*)
+JS_NewArrayBuffer(JSContext* cx, uint32_t nbytes)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ MOZ_ASSERT(nbytes <= INT32_MAX);
+ return ArrayBufferObject::create(cx, nbytes);
+}
+
+JS_PUBLIC_API(JSObject*)
+JS_NewArrayBufferWithContents(JSContext* cx, size_t nbytes, void* data)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ MOZ_ASSERT_IF(!data, nbytes == 0);
+ ArrayBufferObject::BufferContents contents =
+ ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(data);
+ return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::OwnsData,
+ /* proto = */ nullptr, TenuredObject);
+}
+
+JS_PUBLIC_API(JSObject*)
+JS_NewArrayBufferWithExternalContents(JSContext* cx, size_t nbytes, void* data)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ MOZ_ASSERT_IF(!data, nbytes == 0);
+ ArrayBufferObject::BufferContents contents =
+ ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN>(data);
+ return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::DoesntOwnData,
+ /* proto = */ nullptr, TenuredObject);
+}
+
+JS_FRIEND_API(bool)
+JS_IsArrayBufferObject(JSObject* obj)
+{
+ obj = CheckedUnwrap(obj);
+ return obj && obj->is<ArrayBufferObject>();
+}
+
+JS_FRIEND_API(bool)
+JS_ArrayBufferHasData(JSObject* obj)
+{
+ return CheckedUnwrap(obj)->as<ArrayBufferObject>().hasData();
+}
+
+JS_FRIEND_API(JSObject*)
+js::UnwrapArrayBuffer(JSObject* obj)
+{
+ if (JSObject* unwrapped = CheckedUnwrap(obj))
+ return unwrapped->is<ArrayBufferObject>() ? unwrapped : nullptr;
+ return nullptr;
+}
+
+JS_FRIEND_API(JSObject*)
+js::UnwrapSharedArrayBuffer(JSObject* obj)
+{
+ if (JSObject* unwrapped = CheckedUnwrap(obj))
+ return unwrapped->is<SharedArrayBufferObject>() ? unwrapped : nullptr;
+ return nullptr;
+}
+
+JS_PUBLIC_API(void*)
+JS_ExternalizeArrayBufferContents(JSContext* cx, HandleObject obj)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, obj);
+
+ if (!obj->is<ArrayBufferObject>()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
+ return nullptr;
+ }
+
+ Handle<ArrayBufferObject*> buffer = obj.as<ArrayBufferObject>();
+ if (!buffer->isPlain()) {
+ // This operation isn't supported on mapped or wsm ArrayBufferObjects.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
+ return nullptr;
+ }
+ if (buffer->isDetached()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
+ return nullptr;
+ }
+
+ // The caller assumes that a plain malloc'd buffer is returned.
+ // hasStealableContents is true for mapped buffers, so we must additionally
+ // require that the buffer is plain. In the future, we could consider
+ // returning something that handles releasing the memory.
+ bool hasStealableContents = buffer->hasStealableContents();
+
+ return ArrayBufferObject::externalizeContents(cx, buffer, hasStealableContents).data();
+}
+
+JS_PUBLIC_API(void*)
+JS_StealArrayBufferContents(JSContext* cx, HandleObject objArg)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, objArg);
+
+ JSObject* obj = CheckedUnwrap(objArg);
+ if (!obj)
+ return nullptr;
+
+ if (!obj->is<ArrayBufferObject>()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
+ return nullptr;
+ }
+
+ Rooted<ArrayBufferObject*> buffer(cx, &obj->as<ArrayBufferObject>());
+ if (buffer->isDetached()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
+ return nullptr;
+ }
+
+ if (buffer->isWasm() || buffer->isPreparedForAsmJS()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_NO_TRANSFER);
+ return nullptr;
+ }
+
+ // The caller assumes that a plain malloc'd buffer is returned.
+ // hasStealableContents is true for mapped buffers, so we must additionally
+ // require that the buffer is plain. In the future, we could consider
+ // returning something that handles releasing the memory.
+ bool hasStealableContents = buffer->hasStealableContents() && buffer->isPlain();
+
+ AutoCompartment ac(cx, buffer);
+ return ArrayBufferObject::stealContents(cx, buffer, hasStealableContents).data();
+}
+
+JS_PUBLIC_API(JSObject*)
+JS_NewMappedArrayBufferWithContents(JSContext* cx, size_t nbytes, void* data)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+
+ MOZ_ASSERT(data);
+ ArrayBufferObject::BufferContents contents =
+ ArrayBufferObject::BufferContents::create<ArrayBufferObject::MAPPED>(data);
+ return ArrayBufferObject::create(cx, nbytes, contents, ArrayBufferObject::OwnsData,
+ /* proto = */ nullptr, TenuredObject);
+}
+
+JS_PUBLIC_API(void*)
+JS_CreateMappedArrayBufferContents(int fd, size_t offset, size_t length)
+{
+ return ArrayBufferObject::createMappedContents(fd, offset, length).data();
+}
+
+JS_PUBLIC_API(void)
+JS_ReleaseMappedArrayBufferContents(void* contents, size_t length)
+{
+ MemProfiler::RemoveNative(contents);
+ DeallocateMappedContent(contents, length);
+}
+
+JS_FRIEND_API(bool)
+JS_IsMappedArrayBufferObject(JSObject* obj)
+{
+ obj = CheckedUnwrap(obj);
+ if (!obj)
+ return false;
+
+ return obj->is<ArrayBufferObject>() && obj->as<ArrayBufferObject>().isMapped();
+}
+
+JS_FRIEND_API(void*)
+JS_GetArrayBufferViewData(JSObject* obj, bool* isSharedMemory, const JS::AutoCheckCannotGC&)
+{
+ obj = CheckedUnwrap(obj);
+ if (!obj)
+ return nullptr;
+ if (obj->is<DataViewObject>()) {
+ *isSharedMemory = false;
+ return obj->as<DataViewObject>().dataPointer();
+ }
+ TypedArrayObject& ta = obj->as<TypedArrayObject>();
+ *isSharedMemory = ta.isSharedMemory();
+ return ta.viewDataEither().unwrap(/*safe - caller sees isShared flag*/);
+}
+
+JS_FRIEND_API(JSObject*)
+JS_GetArrayBufferViewBuffer(JSContext* cx, HandleObject objArg, bool* isSharedMemory)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, objArg);
+
+ JSObject* obj = CheckedUnwrap(objArg);
+ if (!obj)
+ return nullptr;
+ MOZ_ASSERT(obj->is<ArrayBufferViewObject>());
+
+ Rooted<ArrayBufferViewObject*> viewObject(cx, static_cast<ArrayBufferViewObject*>(obj));
+ ArrayBufferObjectMaybeShared* buffer = ArrayBufferViewObject::bufferObject(cx, viewObject);
+ *isSharedMemory = buffer->is<SharedArrayBufferObject>();
+ return buffer;
+}
+
+JS_FRIEND_API(uint32_t)
+JS_GetArrayBufferViewByteLength(JSObject* obj)
+{
+ obj = CheckedUnwrap(obj);
+ if (!obj)
+ return 0;
+ return obj->is<DataViewObject>()
+ ? obj->as<DataViewObject>().byteLength()
+ : obj->as<TypedArrayObject>().byteLength();
+}
+
+JS_FRIEND_API(JSObject*)
+JS_GetObjectAsArrayBufferView(JSObject* obj, uint32_t* length, bool* isSharedMemory, uint8_t** data)
+{
+ if (!(obj = CheckedUnwrap(obj)))
+ return nullptr;
+ if (!(obj->is<ArrayBufferViewObject>()))
+ return nullptr;
+
+ js::GetArrayBufferViewLengthAndData(obj, length, isSharedMemory, data);
+ return obj;
+}
+
+JS_FRIEND_API(void)
+js::GetArrayBufferViewLengthAndData(JSObject* obj, uint32_t* length, bool* isSharedMemory, uint8_t** data)
+{
+ MOZ_ASSERT(obj->is<ArrayBufferViewObject>());
+
+ *length = obj->is<DataViewObject>()
+ ? obj->as<DataViewObject>().byteLength()
+ : obj->as<TypedArrayObject>().byteLength();
+
+ if (obj->is<DataViewObject>()) {
+ *isSharedMemory = false;
+ *data = static_cast<uint8_t*>(obj->as<DataViewObject>().dataPointer());
+ }
+ else {
+ TypedArrayObject& ta = obj->as<TypedArrayObject>();
+ *isSharedMemory = ta.isSharedMemory();
+ *data = static_cast<uint8_t*>(ta.viewDataEither().unwrap(/*safe - caller sees isShared flag*/));
+ }
+}
+
+JS_FRIEND_API(JSObject*)
+JS_GetObjectAsArrayBuffer(JSObject* obj, uint32_t* length, uint8_t** data)
+{
+ if (!(obj = CheckedUnwrap(obj)))
+ return nullptr;
+ if (!IsArrayBuffer(obj))
+ return nullptr;
+
+ *length = AsArrayBuffer(obj).byteLength();
+ *data = AsArrayBuffer(obj).dataPointer();
+
+ return obj;
+}
+
+JS_FRIEND_API(void)
+js::GetArrayBufferLengthAndData(JSObject* obj, uint32_t* length, bool* isSharedMemory, uint8_t** data)
+{
+ MOZ_ASSERT(IsArrayBuffer(obj));
+ *length = AsArrayBuffer(obj).byteLength();
+ *data = AsArrayBuffer(obj).dataPointer();
+ *isSharedMemory = false;
+}