summaryrefslogtreecommitdiffstats
path: root/js/src/vm/ArrayBufferObject.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/ArrayBufferObject.h')
-rw-r--r--js/src/vm/ArrayBufferObject.h879
1 files changed, 879 insertions, 0 deletions
diff --git a/js/src/vm/ArrayBufferObject.h b/js/src/vm/ArrayBufferObject.h
new file mode 100644
index 0000000000..17faf7682e
--- /dev/null
+++ b/js/src/vm/ArrayBufferObject.h
@@ -0,0 +1,879 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/. */
+
+#ifndef vm_ArrayBufferObject_h
+#define vm_ArrayBufferObject_h
+
+#include "mozilla/Maybe.h"
+
+#include <tuple> // std::tuple
+
+#include "builtin/TypedArrayConstants.h"
+#include "gc/Memory.h"
+#include "gc/ZoneAllocator.h"
+#include "js/ArrayBuffer.h"
+#include "js/GCHashTable.h"
+#include "vm/JSFunction.h"
+#include "vm/JSObject.h"
+#include "vm/SharedMem.h"
+#include "wasm/WasmMemory.h"
+
+namespace js {
+
+class JS_PUBLIC_API GenericPrinter;
+class JSONPrinter;
+
+class ArrayBufferViewObject;
+class AutoSetNewObjectMetadata;
+class WasmArrayRawBuffer;
+
+namespace wasm {
+struct MemoryDesc;
+} // namespace wasm
+
+// Create a new mapping of size `mappedSize` with an initially committed prefix
+// of size `initialCommittedSize`. Both arguments denote bytes and must be
+// multiples of the page size, with `initialCommittedSize` <= `mappedSize`.
+// Returns nullptr on failure.
+void* MapBufferMemory(wasm::IndexType, size_t mappedSize,
+ size_t initialCommittedSize);
+
+// Commit additional memory in an existing mapping. `dataEnd` must be the
+// correct value for the end of the existing committed area, and `delta` must be
+// a byte amount to grow the mapping by, and must be a multiple of the page
+// size. Returns false on failure.
+bool CommitBufferMemory(void* dataEnd, size_t delta);
+
+// Extend an existing mapping by adding uncommited pages to it. `dataStart`
+// must be the pointer to the start of the existing mapping, `mappedSize` the
+// size of the existing mapping, and `newMappedSize` the size of the extended
+// mapping (sizes in bytes), with `mappedSize` <= `newMappedSize`. Both sizes
+// must be divisible by the page size. Returns false on failure.
+bool ExtendBufferMapping(void* dataStart, size_t mappedSize,
+ size_t newMappedSize);
+
+// Remove an existing mapping. `dataStart` must be the pointer to the start of
+// the mapping, and `mappedSize` the size of that mapping.
+void UnmapBufferMemory(wasm::IndexType t, void* dataStart, size_t mappedSize);
+
+// Return the number of bytes currently reserved for WebAssembly memory
+uint64_t WasmReservedBytes();
+
+// The inheritance hierarchy for the various classes relating to typed arrays
+// is as follows.
+//
+//
+// - JSObject
+// - NativeObject
+// - ArrayBufferObjectMaybeShared
+// - ArrayBufferObject
+// - FixedLengthArrayBufferObject
+// - ResizableArrayBufferObject
+// - SharedArrayBufferObject
+// - FixedLengthSharedArrayBufferObject
+// - GrowableSharedArrayBufferObject
+// - ArrayBufferViewObject
+// - DataViewObject
+// - FixedLengthDataViewObject
+// - ResizableDataViewObject
+// - TypedArrayObject (declared in vm/TypedArrayObject.h)
+// - FixedLengthTypedArrayObject
+// - FixedLengthTypedArrayObjectTemplate<NativeType>, also inheriting
+// from TypedArrayObjectTemplate<NativeType>
+// - FixedLengthTypedArrayObjectTemplate<int8_t>
+// - FixedLengthTypedArrayObjectTemplate<uint8_t>
+// - ...
+// - ResizableTypedArrayObject
+// - ResizableTypedArrayObjectTemplate<NativeType>, also inheriting
+// from TypedArrayObjectTemplate<NativeType>
+// - ResizableTypedArrayObjectTemplate<int8_t>
+// - ResizableTypedArrayObjectTemplate<uint8_t>
+// - ...
+//
+// Note that |{FixedLength,Resizable}TypedArrayObjectTemplate| is just an
+// implementation detail that makes implementing its various subclasses easier.
+//
+// FixedLengthArrayBufferObject and ResizableArrayBufferObject are also
+// implementation specific types to differentiate between fixed-length and
+// resizable ArrayBuffers.
+//
+// ArrayBufferObject and SharedArrayBufferObject are unrelated data types:
+// the racy memory of the latter cannot substitute for the non-racy memory of
+// the former; the non-racy memory of the former cannot be used with the
+// atomics; the former can be detached and the latter not. Hence they have been
+// separated completely.
+//
+// Most APIs will only accept ArrayBufferObject. ArrayBufferObjectMaybeShared
+// exists as a join point to allow APIs that can take or use either, notably
+// AsmJS.
+//
+// In contrast with the separation of ArrayBufferObject and
+// SharedArrayBufferObject, the TypedArray types can map either.
+//
+// The possible data ownership and reference relationships with ArrayBuffers
+// and related classes are enumerated below. These are the possible locations
+// for typed data:
+//
+// (1) malloc'ed or mmap'ed data owned by an ArrayBufferObject.
+// (2) Data allocated inline with an ArrayBufferObject.
+// (3) Data allocated inline with a TypedArrayObject.
+//
+// An ArrayBufferObject may point to any of these sources of data, except (3).
+// All array buffer views may point to any of these sources of data, except
+// that (3) may only be pointed to by the typed array the data is inline with.
+//
+// During a minor GC, (3) may move. During a compacting GC, (2) and (3) may
+// move.
+
+class ArrayBufferObjectMaybeShared;
+
+wasm::IndexType WasmArrayBufferIndexType(
+ const ArrayBufferObjectMaybeShared* buf);
+wasm::Pages WasmArrayBufferPages(const ArrayBufferObjectMaybeShared* buf);
+wasm::Pages WasmArrayBufferClampedMaxPages(
+ const ArrayBufferObjectMaybeShared* buf);
+mozilla::Maybe<wasm::Pages> WasmArrayBufferSourceMaxPages(
+ const ArrayBufferObjectMaybeShared* buf);
+size_t WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf);
+
+class ArrayBufferObjectMaybeShared : public NativeObject {
+ public:
+ inline size_t byteLength() const;
+ inline bool isDetached() const;
+ inline bool isResizable() const;
+ inline SharedMem<uint8_t*> dataPointerEither();
+
+ inline bool pinLength(bool pin);
+
+ // WebAssembly support:
+ // Note: the eventual goal is to remove this from ArrayBuffer and have
+ // (Shared)ArrayBuffers alias memory owned by some wasm::Memory object.
+
+ wasm::IndexType wasmIndexType() const {
+ return WasmArrayBufferIndexType(this);
+ }
+ wasm::Pages wasmPages() const { return WasmArrayBufferPages(this); }
+ wasm::Pages wasmClampedMaxPages() const {
+ return WasmArrayBufferClampedMaxPages(this);
+ }
+ mozilla::Maybe<wasm::Pages> wasmSourceMaxPages() const {
+ return WasmArrayBufferSourceMaxPages(this);
+ }
+ size_t wasmMappedSize() const { return WasmArrayBufferMappedSize(this); }
+
+ inline bool isPreparedForAsmJS() const;
+ inline bool isWasm() const;
+};
+
+class FixedLengthArrayBufferObject;
+class ResizableArrayBufferObject;
+
+/*
+ * ArrayBufferObject
+ *
+ * This class holds the underlying raw buffer that the various ArrayBufferViews
+ * (DataViewObject and the TypedArrays) access. It can be created explicitly and
+ * used to construct an ArrayBufferView, or can be created lazily when it is
+ * first accessed for a TypedArrayObject that doesn't have an explicit buffer.
+ *
+ * ArrayBufferObject is an abstract base class and has exactly two concrete
+ * subclasses, FixedLengthArrayBufferObject and ResizableArrayBufferObject.
+ *
+ * ArrayBufferObject (or really the underlying memory) /is not racy/: the
+ * memory is private to a single worker.
+ */
+class ArrayBufferObject : public ArrayBufferObjectMaybeShared {
+ static bool byteLengthGetterImpl(JSContext* cx, const CallArgs& args);
+ static bool maxByteLengthGetterImpl(JSContext* cx, const CallArgs& args);
+ static bool resizableGetterImpl(JSContext* cx, const CallArgs& args);
+ static bool detachedGetterImpl(JSContext* cx, const CallArgs& args);
+ static bool resizeImpl(JSContext* cx, const CallArgs& args);
+ static bool transferImpl(JSContext* cx, const CallArgs& args);
+ static bool transferToFixedLengthImpl(JSContext* cx, const CallArgs& args);
+
+ public:
+ static const uint8_t DATA_SLOT = 0;
+ static const uint8_t BYTE_LENGTH_SLOT = 1;
+ static const uint8_t FIRST_VIEW_SLOT = 2;
+ static const uint8_t FLAGS_SLOT = 3;
+
+ static const uint8_t RESERVED_SLOTS = 4;
+
+ static const size_t ARRAY_BUFFER_ALIGNMENT = 8;
+
+ static_assert(FLAGS_SLOT == JS_ARRAYBUFFER_FLAGS_SLOT,
+ "self-hosted code with burned-in constants must get the "
+ "right flags slot");
+
+ // The length of an ArrayBuffer or SharedArrayBuffer can be at most INT32_MAX
+ // on 32-bit platforms. Allow a larger limit on 64-bit platforms.
+ static constexpr size_t ByteLengthLimitForSmallBuffer = INT32_MAX;
+#ifdef JS_64BIT
+ static constexpr size_t ByteLengthLimit =
+ size_t(8) * 1024 * 1024 * 1024; // 8 GB.
+#else
+ static constexpr size_t ByteLengthLimit = ByteLengthLimitForSmallBuffer;
+#endif
+
+ public:
+ enum BufferKind {
+ /** Inline data kept in the repurposed slots of this ArrayBufferObject. */
+ INLINE_DATA = 0b000,
+
+ /*
+ * Data allocated using the SpiderMonkey allocator, created within
+ * js::ArrayBufferContentsArena.
+ */
+ MALLOCED_ARRAYBUFFER_CONTENTS_ARENA = 0b001,
+
+ /**
+ * No bytes are associated with this buffer. (This could be because the
+ * buffer is detached, because it's an internal, newborn buffer not yet
+ * overwritten with user-exposable semantics, or some other reason. The
+ * point is, don't read precise language semantics into this kind.)
+ */
+ NO_DATA = 0b010,
+
+ /**
+ * User-owned memory. The associated buffer must be manually detached
+ * before the user invalidates (deallocates, reuses the storage of, &c.)
+ * the user-owned memory.
+ */
+ USER_OWNED = 0b011,
+
+ WASM = 0b100,
+ MAPPED = 0b101,
+ EXTERNAL = 0b110,
+
+ /**
+ * Data allocated using the SpiderMonkey allocator, created within an
+ * unknown memory arena.
+ */
+ MALLOCED_UNKNOWN_ARENA = 0b111,
+
+ KIND_MASK = 0b111
+ };
+
+ public:
+ enum ArrayBufferFlags {
+ // The flags also store the BufferKind
+ BUFFER_KIND_MASK = BufferKind::KIND_MASK,
+
+ DETACHED = 0b1000,
+
+ // Resizable ArrayBuffer.
+ RESIZABLE = 0b1'0000,
+
+ // This MALLOCED, MAPPED, or EXTERNAL buffer has been prepared for asm.js
+ // and cannot henceforth be transferred/detached. (WASM, USER_OWNED, and
+ // INLINE_DATA buffers can't be prepared for asm.js -- although if an
+ // INLINE_DATA buffer is used with asm.js, it's silently rewritten into a
+ // MALLOCED buffer which *can* be prepared.)
+ FOR_ASMJS = 0b10'0000,
+
+ // The length is temporarily pinned, so it should not be detached. In the
+ // future, this will also prevent GrowableArrayBuffer/ResizeableArrayBuffer
+ // from modifying the length while this is set.
+ PINNED_LENGTH = 0b100'0000
+ };
+
+ static_assert(JS_ARRAYBUFFER_DETACHED_FLAG == DETACHED,
+ "self-hosted code with burned-in constants must use the "
+ "correct DETACHED bit value");
+
+ protected:
+ enum class FillContents { Zero, Uninitialized };
+
+ template <class ArrayBufferType, FillContents FillType>
+ static std::tuple<ArrayBufferType*, uint8_t*>
+ createUninitializedBufferAndData(JSContext* cx, size_t nbytes,
+ AutoSetNewObjectMetadata&,
+ JS::Handle<JSObject*> proto);
+
+ template <FillContents FillType>
+ static std::tuple<ArrayBufferObject*, uint8_t*> createBufferAndData(
+ JSContext* cx, size_t nbytes, AutoSetNewObjectMetadata& metadata,
+ JS::Handle<JSObject*> proto = nullptr);
+
+ public:
+ class BufferContents {
+ uint8_t* data_;
+ BufferKind kind_;
+ JS::BufferContentsFreeFunc free_;
+ void* freeUserData_;
+
+ friend class ArrayBufferObject;
+
+ BufferContents(uint8_t* data, BufferKind kind,
+ JS::BufferContentsFreeFunc freeFunc = nullptr,
+ void* freeUserData = nullptr)
+ : data_(data),
+ kind_(kind),
+ free_(freeFunc),
+ freeUserData_(freeUserData) {
+ MOZ_ASSERT((kind_ & ~KIND_MASK) == 0);
+ MOZ_ASSERT_IF(free_ || freeUserData_, kind_ == EXTERNAL);
+
+ // It is the caller's responsibility to ensure that the
+ // BufferContents does not outlive the data.
+ }
+
+ public:
+ static BufferContents createInlineData(void* data) {
+ return BufferContents(static_cast<uint8_t*>(data), INLINE_DATA);
+ }
+
+ static BufferContents createMallocedArrayBufferContentsArena(void* data) {
+ return BufferContents(static_cast<uint8_t*>(data),
+ MALLOCED_ARRAYBUFFER_CONTENTS_ARENA);
+ }
+
+ static BufferContents createMallocedUnknownArena(void* data) {
+ return BufferContents(static_cast<uint8_t*>(data),
+ MALLOCED_UNKNOWN_ARENA);
+ }
+
+ static BufferContents createNoData() {
+ return BufferContents(nullptr, NO_DATA);
+ }
+
+ static BufferContents createUserOwned(void* data) {
+ return BufferContents(static_cast<uint8_t*>(data), USER_OWNED);
+ }
+
+ static BufferContents createWasm(void* data) {
+ return BufferContents(static_cast<uint8_t*>(data), WASM);
+ }
+
+ static BufferContents createMapped(void* data) {
+ return BufferContents(static_cast<uint8_t*>(data), MAPPED);
+ }
+
+ static BufferContents createExternal(void* data,
+ JS::BufferContentsFreeFunc freeFunc,
+ void* freeUserData = nullptr) {
+ MOZ_ASSERT(freeFunc);
+ return BufferContents(static_cast<uint8_t*>(data), EXTERNAL, freeFunc,
+ freeUserData);
+ }
+
+ static BufferContents createFailed() {
+ // There's no harm in tagging this as MALLOCED_ARRAYBUFFER_CONTENTS_ARENA,
+ // even tho obviously it isn't. And adding an extra tag purely for this
+ // case is a complication that presently appears avoidable.
+ return BufferContents(nullptr, MALLOCED_ARRAYBUFFER_CONTENTS_ARENA);
+ }
+
+ uint8_t* data() const { return data_; }
+ BufferKind kind() const { return kind_; }
+ JS::BufferContentsFreeFunc freeFunc() const { return free_; }
+ void* freeUserData() const { return freeUserData_; }
+
+ explicit operator bool() const { return data_ != nullptr; }
+ WasmArrayRawBuffer* wasmBuffer() const;
+ };
+
+ static const JSClass protoClass_;
+
+ static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
+
+ static bool maxByteLengthGetter(JSContext* cx, unsigned argc, Value* vp);
+
+ static bool resizableGetter(JSContext* cx, unsigned argc, Value* vp);
+
+ static bool detachedGetter(JSContext* cx, unsigned argc, Value* vp);
+
+ static bool fun_isView(JSContext* cx, unsigned argc, Value* vp);
+
+ static bool resize(JSContext* cx, unsigned argc, Value* vp);
+
+ static bool transfer(JSContext* cx, unsigned argc, Value* vp);
+
+ static bool transferToFixedLength(JSContext* cx, unsigned argc, Value* vp);
+
+ static bool class_constructor(JSContext* cx, unsigned argc, Value* vp);
+
+ static bool isOriginalByteLengthGetter(Native native) {
+ return native == byteLengthGetter;
+ }
+
+ static ArrayBufferObject* createForContents(JSContext* cx, size_t nbytes,
+ BufferContents contents);
+
+ static ArrayBufferObject* copy(JSContext* cx, size_t newByteLength,
+ JS::Handle<ArrayBufferObject*> source);
+
+ static ArrayBufferObject* copyAndDetach(
+ JSContext* cx, size_t newByteLength,
+ JS::Handle<ArrayBufferObject*> source);
+
+ private:
+ static ArrayBufferObject* copyAndDetachSteal(
+ JSContext* cx, JS::Handle<ArrayBufferObject*> source);
+
+ static ArrayBufferObject* copyAndDetachRealloc(
+ JSContext* cx, size_t newByteLength,
+ JS::Handle<ArrayBufferObject*> source);
+
+ public:
+ static ArrayBufferObject* createZeroed(JSContext* cx, size_t nbytes,
+ HandleObject proto = nullptr);
+
+ // Create an ArrayBufferObject that is safely finalizable and can later be
+ // initialize()d to become a real, content-visible ArrayBufferObject.
+ static ArrayBufferObject* createEmpty(JSContext* cx);
+
+ // Create an ArrayBufferObject using the provided buffer and size. Assumes
+ // ownership of |buffer| even in case of failure, i.e. on failure |buffer|
+ // is deallocated.
+ static ArrayBufferObject* createFromNewRawBuffer(JSContext* cx,
+ WasmArrayRawBuffer* buffer,
+ size_t initialSize);
+
+ static void copyData(ArrayBufferObject* toBuffer, size_t toIndex,
+ ArrayBufferObject* fromBuffer, size_t fromIndex,
+ size_t count);
+
+ template <class ArrayBufferType>
+ static size_t objectMoved(JSObject* obj, JSObject* old);
+
+ static uint8_t* stealMallocedContents(JSContext* cx,
+ Handle<ArrayBufferObject*> buffer);
+
+ static BufferContents extractStructuredCloneContents(
+ JSContext* cx, Handle<ArrayBufferObject*> buffer);
+
+ static void addSizeOfExcludingThis(JSObject* obj,
+ mozilla::MallocSizeOf mallocSizeOf,
+ JS::ClassInfo* info,
+ JS::RuntimeSizes* runtimeSizes);
+
+ // ArrayBufferObjects (strongly) store the first view added to them, while
+ // later views are (weakly) stored in the compartment's InnerViewTable
+ // below. Buffers usually only have one view, so this slot optimizes for
+ // the common case. Avoiding entries in the InnerViewTable saves memory and
+ // non-incrementalized sweep time.
+ JSObject* firstView();
+
+ bool addView(JSContext* cx, ArrayBufferViewObject* view);
+
+ // Pin or unpin the length. Returns whether pinned status was changed.
+ bool pinLength(bool pin) {
+ if (bool(flags() & PINNED_LENGTH) == pin) {
+ return false;
+ }
+ setFlags(flags() ^ PINNED_LENGTH);
+ return true;
+ }
+
+ static bool ensureNonInline(JSContext* cx, Handle<ArrayBufferObject*> buffer);
+
+ // Detach this buffer from its original memory. (This necessarily makes
+ // views of this buffer unusable for modifying that original memory.)
+ static void detach(JSContext* cx, Handle<ArrayBufferObject*> buffer);
+
+ static constexpr size_t offsetOfByteLengthSlot() {
+ return getFixedSlotOffset(BYTE_LENGTH_SLOT);
+ }
+ static constexpr size_t offsetOfFlagsSlot() {
+ return getFixedSlotOffset(FLAGS_SLOT);
+ }
+
+ protected:
+ void setFirstView(ArrayBufferViewObject* view);
+
+ private:
+ struct FreeInfo {
+ JS::BufferContentsFreeFunc freeFunc;
+ void* freeUserData;
+ };
+ FreeInfo* freeInfo() const;
+
+ public:
+ uint8_t* dataPointer() const;
+ SharedMem<uint8_t*> dataPointerShared() const;
+ size_t byteLength() const;
+
+ BufferContents contents() const {
+ if (isExternal()) {
+ return BufferContents(dataPointer(), EXTERNAL, freeInfo()->freeFunc,
+ freeInfo()->freeUserData);
+ }
+ return BufferContents(dataPointer(), bufferKind());
+ }
+
+ void releaseData(JS::GCContext* gcx);
+
+ BufferKind bufferKind() const {
+ return BufferKind(flags() & BUFFER_KIND_MASK);
+ }
+
+ bool isInlineData() const { return bufferKind() == INLINE_DATA; }
+ bool isMalloced() const {
+ return bufferKind() == MALLOCED_ARRAYBUFFER_CONTENTS_ARENA ||
+ bufferKind() == MALLOCED_UNKNOWN_ARENA;
+ }
+ bool isNoData() const { return bufferKind() == NO_DATA; }
+ bool hasUserOwnedData() const { return bufferKind() == USER_OWNED; }
+
+ bool isWasm() const { return bufferKind() == WASM; }
+ bool isMapped() const { return bufferKind() == MAPPED; }
+ bool isExternal() const { return bufferKind() == EXTERNAL; }
+
+ bool isDetached() const { return flags() & DETACHED; }
+ bool isResizable() const { return flags() & RESIZABLE; }
+ bool isLengthPinned() const { return flags() & PINNED_LENGTH; }
+ bool isPreparedForAsmJS() const { return flags() & FOR_ASMJS; }
+
+ // Only WASM and asm.js buffers have a non-undefined [[ArrayBufferDetachKey]].
+ //
+ // https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-instances
+ bool hasDefinedDetachKey() const { return isWasm() || isPreparedForAsmJS(); }
+
+ // WebAssembly support:
+
+ /**
+ * Prepare this ArrayBuffer for use with asm.js. Returns true on success,
+ * false on failure. This function reports no errors.
+ */
+ [[nodiscard]] bool prepareForAsmJS();
+
+ size_t wasmMappedSize() const;
+
+ wasm::IndexType wasmIndexType() const;
+ wasm::Pages wasmPages() const;
+ wasm::Pages wasmClampedMaxPages() const;
+ mozilla::Maybe<wasm::Pages> wasmSourceMaxPages() const;
+
+ [[nodiscard]] static ArrayBufferObject* wasmGrowToPagesInPlace(
+ wasm::IndexType t, wasm::Pages newPages,
+ Handle<ArrayBufferObject*> oldBuf, JSContext* cx);
+ [[nodiscard]] static ArrayBufferObject* wasmMovingGrowToPages(
+ wasm::IndexType t, wasm::Pages newPages,
+ Handle<ArrayBufferObject*> oldBuf, JSContext* cx);
+ static void wasmDiscard(Handle<ArrayBufferObject*> buf, uint64_t byteOffset,
+ uint64_t byteLength);
+
+ static void finalize(JS::GCContext* gcx, JSObject* obj);
+
+ static BufferContents createMappedContents(int fd, size_t offset,
+ size_t length);
+
+ protected:
+ void setDataPointer(BufferContents contents);
+ void setByteLength(size_t length);
+
+ size_t associatedBytes() const;
+
+ uint32_t flags() const;
+ void setFlags(uint32_t flags);
+
+ void setIsDetached() {
+ MOZ_ASSERT(!(flags() & PINNED_LENGTH));
+ setFlags(flags() | DETACHED);
+ }
+ void setIsPreparedForAsmJS() {
+ MOZ_ASSERT(!isWasm());
+ MOZ_ASSERT(!hasUserOwnedData());
+ MOZ_ASSERT(!isInlineData());
+ MOZ_ASSERT(isMalloced() || isMapped() || isExternal());
+ setFlags(flags() | FOR_ASMJS);
+ }
+
+ void initialize(size_t byteLength, BufferContents contents) {
+ setByteLength(byteLength);
+ setFlags(0);
+ setFirstView(nullptr);
+ setDataPointer(contents);
+ }
+
+ public:
+#if defined(DEBUG) || defined(JS_JITSPEW)
+ void dumpOwnFields(js::JSONPrinter& json) const;
+ void dumpOwnStringContent(js::GenericPrinter& out) const;
+#endif
+};
+
+/**
+ * FixedLengthArrayBufferObject
+ *
+ * ArrayBuffer object with a fixed length. Its length is unmodifiable, except
+ * when zeroing it for detached buffers. Supports all possible memory stores
+ * for ArrayBuffer objects, including inline data, malloc'ed memory, mapped
+ * memory, and user-owner memory.
+ *
+ * Fixed-length ArrayBuffers can be used for asm.js and WebAssembly.
+ */
+class FixedLengthArrayBufferObject : public ArrayBufferObject {
+ friend class ArrayBufferObject;
+
+ uint8_t* inlineDataPointer() const;
+
+ bool hasInlineData() const { return dataPointer() == inlineDataPointer(); }
+
+ public:
+ // Fixed-length ArrayBuffer objects don't have any additional reserved slots.
+ static const uint8_t RESERVED_SLOTS = ArrayBufferObject::RESERVED_SLOTS;
+
+ /** The largest number of bytes that can be stored inline. */
+ static constexpr size_t MaxInlineBytes =
+ (NativeObject::MAX_FIXED_SLOTS - RESERVED_SLOTS) * sizeof(JS::Value);
+
+ static const JSClass class_;
+};
+
+/**
+ * ResizableArrayBufferObject
+ *
+ * ArrayBuffer object which can both grow and shrink. The maximum byte length it
+ * can grow to is set when creating the object. The data of resizable
+ * ArrayBuffer object is either stored inline or malloc'ed memory.
+ *
+ * When a resizable ArrayBuffer object is detached, its maximum byte length
+ * slot is set to zero in addition to the byte length slot.
+ *
+ * Resizable ArrayBuffers can neither be used for asm.js nor WebAssembly.
+ */
+class ResizableArrayBufferObject : public ArrayBufferObject {
+ friend class ArrayBufferObject;
+
+ template <FillContents FillType>
+ static std::tuple<ResizableArrayBufferObject*, uint8_t*> createBufferAndData(
+ JSContext* cx, size_t byteLength, size_t maxByteLength,
+ AutoSetNewObjectMetadata& metadata, Handle<JSObject*> proto);
+
+ static ResizableArrayBufferObject* createEmpty(JSContext* cx);
+
+ public:
+ static ResizableArrayBufferObject* createZeroed(
+ JSContext* cx, size_t byteLength, size_t maxByteLength,
+ Handle<JSObject*> proto = nullptr);
+
+ private:
+ uint8_t* inlineDataPointer() const;
+
+ bool hasInlineData() const { return dataPointer() == inlineDataPointer(); }
+
+ void setMaxByteLength(size_t length) {
+ MOZ_ASSERT(length <= ArrayBufferObject::ByteLengthLimit);
+ setFixedSlot(MAX_BYTE_LENGTH_SLOT, PrivateValue(length));
+ }
+
+ void initialize(size_t byteLength, size_t maxByteLength,
+ BufferContents contents) {
+ setByteLength(byteLength);
+ setMaxByteLength(maxByteLength);
+ setFlags(RESIZABLE);
+ setFirstView(nullptr);
+ setDataPointer(contents);
+ }
+
+ // Resize this buffer.
+ void resize(size_t newByteLength);
+
+ static ResizableArrayBufferObject* copy(
+ JSContext* cx, size_t newByteLength,
+ JS::Handle<ResizableArrayBufferObject*> source);
+
+ public:
+ static const uint8_t MAX_BYTE_LENGTH_SLOT = ArrayBufferObject::RESERVED_SLOTS;
+
+ static const uint8_t RESERVED_SLOTS = ArrayBufferObject::RESERVED_SLOTS + 1;
+
+ /** The largest number of bytes that can be stored inline. */
+ static constexpr size_t MaxInlineBytes =
+ (NativeObject::MAX_FIXED_SLOTS - RESERVED_SLOTS) * sizeof(JS::Value);
+
+ static const JSClass class_;
+
+ size_t maxByteLength() const {
+ return size_t(getFixedSlot(MAX_BYTE_LENGTH_SLOT).toPrivate());
+ }
+
+ static ResizableArrayBufferObject* copyAndDetach(
+ JSContext* cx, size_t newByteLength,
+ JS::Handle<ResizableArrayBufferObject*> source);
+
+ private:
+ static ResizableArrayBufferObject* copyAndDetachSteal(
+ JSContext* cx, size_t newByteLength,
+ JS::Handle<ResizableArrayBufferObject*> source);
+};
+
+// Create a buffer for a wasm memory, whose type is determined by
+// memory.indexType().
+ArrayBufferObjectMaybeShared* CreateWasmBuffer(JSContext* cx,
+ const wasm::MemoryDesc& memory);
+
+// Per-compartment table that manages the relationship between array buffers
+// and the views that use their storage.
+class InnerViewTable {
+ // Store views in a vector such that all the tenured views come before any
+ // nursery views. Maintain the index of the first nursery view so there is an
+ // efficient way to access only the nursery views.
+ using ViewVector =
+ GCVector<UnsafeBarePtr<ArrayBufferViewObject*>, 1, ZoneAllocPolicy>;
+ struct Views {
+ ViewVector views; // List of views with tenured views at the front.
+ size_t firstNurseryView = 0;
+
+ explicit Views(JS::Zone* zone) : views(zone) {}
+ bool empty();
+ bool hasNurseryViews();
+ bool addView(ArrayBufferViewObject* view);
+
+ bool traceWeak(JSTracer* trc, size_t startIndex = 0);
+ bool sweepAfterMinorGC(JSTracer* trc);
+
+ void check();
+ };
+
+ // For all objects sharing their storage with some other view, this maps
+ // the object to the list of such views. All entries in this map are weak.
+ //
+ // This key is a raw pointer and not a WeakHeapPtr because the post-barrier
+ // would hold nursery-allocated entries live unconditionally. It is a very
+ // common pattern in low-level and performance-oriented JavaScript to create
+ // hundreds or thousands of very short lived temporary views on a larger
+ // buffer; having to tenure all of these would be a catastrophic performance
+ // regression. Thus, it is vital that nursery pointers in this map not be held
+ // live. Special support is required in the minor GC, implemented in
+ // sweepAfterMinorGC.
+ using ArrayBufferViewMap =
+ GCHashMap<UnsafeBarePtr<ArrayBufferObject*>, Views,
+ StableCellHasher<JSObject*>, ZoneAllocPolicy>;
+ ArrayBufferViewMap map;
+
+ // List of keys from innerViews where either the source or at least one
+ // target is in the nursery. The raw pointer to a JSObject is allowed here
+ // because this vector is cleared after every minor collection. Users in
+ // sweepAfterMinorCollection must be careful to use MaybeForwarded before
+ // touching these pointers.
+ Vector<ArrayBufferObject*, 0, SystemAllocPolicy> nurseryKeys;
+
+ // Whether nurseryKeys is a complete list.
+ bool nurseryKeysValid = true;
+
+ public:
+ explicit InnerViewTable(Zone* zone) : map(zone) {}
+
+ // Remove references to dead objects in the table and update table entries
+ // to reflect moved objects.
+ bool traceWeak(JSTracer* trc);
+ void sweepAfterMinorGC(JSTracer* trc);
+
+ bool empty() const { return map.empty(); }
+
+ bool needsSweepAfterMinorGC() const {
+ return !nurseryKeys.empty() || !nurseryKeysValid;
+ }
+
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
+
+ private:
+ friend class ArrayBufferObject;
+ bool addView(JSContext* cx, ArrayBufferObject* buffer,
+ ArrayBufferViewObject* view);
+ ViewVector* maybeViewsUnbarriered(ArrayBufferObject* buffer);
+ void removeViews(ArrayBufferObject* buffer);
+};
+
+template <typename Wrapper>
+class MutableWrappedPtrOperations<InnerViewTable, Wrapper>
+ : public WrappedPtrOperations<InnerViewTable, Wrapper> {
+ InnerViewTable& table() { return static_cast<Wrapper*>(this)->get(); }
+
+ public:
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) {
+ return table().sizeOfExcludingThis(mallocSizeOf);
+ }
+};
+
+class WasmArrayRawBuffer {
+ wasm::IndexType indexType_;
+ wasm::Pages clampedMaxPages_;
+ mozilla::Maybe<wasm::Pages> sourceMaxPages_;
+ size_t mappedSize_; // Not including the header page
+ size_t length_;
+
+ protected:
+ WasmArrayRawBuffer(wasm::IndexType indexType, uint8_t* buffer,
+ wasm::Pages clampedMaxPages,
+ const mozilla::Maybe<wasm::Pages>& sourceMaxPages,
+ size_t mappedSize, size_t length)
+ : indexType_(indexType),
+ clampedMaxPages_(clampedMaxPages),
+ sourceMaxPages_(sourceMaxPages),
+ mappedSize_(mappedSize),
+ length_(length) {
+ MOZ_ASSERT(buffer == dataPointer());
+ }
+
+ public:
+ static WasmArrayRawBuffer* AllocateWasm(
+ wasm::IndexType indexType, wasm::Pages initialPages,
+ wasm::Pages clampedMaxPages,
+ const mozilla::Maybe<wasm::Pages>& sourceMaxPages,
+ const mozilla::Maybe<size_t>& mappedSize);
+ static void Release(void* mem);
+
+ uint8_t* dataPointer() {
+ uint8_t* ptr = reinterpret_cast<uint8_t*>(this);
+ return ptr + sizeof(WasmArrayRawBuffer);
+ }
+
+ static const WasmArrayRawBuffer* fromDataPtr(const uint8_t* dataPtr) {
+ return reinterpret_cast<const WasmArrayRawBuffer*>(
+ dataPtr - sizeof(WasmArrayRawBuffer));
+ }
+
+ static WasmArrayRawBuffer* fromDataPtr(uint8_t* dataPtr) {
+ return reinterpret_cast<WasmArrayRawBuffer*>(dataPtr -
+ sizeof(WasmArrayRawBuffer));
+ }
+
+ wasm::IndexType indexType() const { return indexType_; }
+
+ uint8_t* basePointer() { return dataPointer() - gc::SystemPageSize(); }
+
+ size_t mappedSize() const { return mappedSize_; }
+
+ size_t byteLength() const { return length_; }
+
+ wasm::Pages pages() const {
+ return wasm::Pages::fromByteLengthExact(length_);
+ }
+
+ wasm::Pages clampedMaxPages() const { return clampedMaxPages_; }
+
+ mozilla::Maybe<wasm::Pages> sourceMaxPages() const { return sourceMaxPages_; }
+
+ [[nodiscard]] bool growToPagesInPlace(wasm::Pages newPages);
+
+ [[nodiscard]] bool extendMappedSize(wasm::Pages maxPages);
+
+ // Try and grow the mapped region of memory. Does not change current size.
+ // Does not move memory if no space to grow.
+ void tryGrowMaxPagesInPlace(wasm::Pages deltaMaxPages);
+
+ // Discard a region of memory, zeroing the pages and releasing physical memory
+ // back to the operating system. byteOffset and byteLen must be wasm page
+ // aligned and in bounds. A discard of zero bytes will have no effect.
+ void discard(size_t byteOffset, size_t byteLen);
+};
+
+} // namespace js
+
+template <>
+inline bool JSObject::is<js::ArrayBufferObject>() const {
+ return is<js::FixedLengthArrayBufferObject>() ||
+ is<js::ResizableArrayBufferObject>();
+}
+
+template <>
+bool JSObject::is<js::ArrayBufferObjectMaybeShared>() const;
+
+#endif // vm_ArrayBufferObject_h