/* -*- 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 // 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, also inheriting // from TypedArrayObjectTemplate // - FixedLengthTypedArrayObjectTemplate // - FixedLengthTypedArrayObjectTemplate // - ... // - ResizableTypedArrayObject // - ResizableTypedArrayObjectTemplate, also inheriting // from TypedArrayObjectTemplate // - ResizableTypedArrayObjectTemplate // - ResizableTypedArrayObjectTemplate // - ... // // 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 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 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 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 static std::tuple createUninitializedBufferAndData(JSContext* cx, size_t nbytes, AutoSetNewObjectMetadata&, JS::Handle proto); template static std::tuple createBufferAndData( JSContext* cx, size_t nbytes, AutoSetNewObjectMetadata& metadata, JS::Handle 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(data), INLINE_DATA); } static BufferContents createMallocedArrayBufferContentsArena(void* data) { return BufferContents(static_cast(data), MALLOCED_ARRAYBUFFER_CONTENTS_ARENA); } static BufferContents createMallocedUnknownArena(void* data) { return BufferContents(static_cast(data), MALLOCED_UNKNOWN_ARENA); } static BufferContents createNoData() { return BufferContents(nullptr, NO_DATA); } static BufferContents createUserOwned(void* data) { return BufferContents(static_cast(data), USER_OWNED); } static BufferContents createWasm(void* data) { return BufferContents(static_cast(data), WASM); } static BufferContents createMapped(void* data) { return BufferContents(static_cast(data), MAPPED); } static BufferContents createExternal(void* data, JS::BufferContentsFreeFunc freeFunc, void* freeUserData = nullptr) { MOZ_ASSERT(freeFunc); return BufferContents(static_cast(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 source); static ArrayBufferObject* copyAndDetach( JSContext* cx, size_t newByteLength, JS::Handle source); private: static ArrayBufferObject* copyAndDetachSteal( JSContext* cx, JS::Handle source); static ArrayBufferObject* copyAndDetachRealloc( JSContext* cx, size_t newByteLength, JS::Handle 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 static size_t objectMoved(JSObject* obj, JSObject* old); static uint8_t* stealMallocedContents(JSContext* cx, Handle buffer); static BufferContents extractStructuredCloneContents( JSContext* cx, Handle 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 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 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 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 wasmSourceMaxPages() const; [[nodiscard]] static ArrayBufferObject* wasmGrowToPagesInPlace( wasm::IndexType t, wasm::Pages newPages, Handle oldBuf, JSContext* cx); [[nodiscard]] static ArrayBufferObject* wasmMovingGrowToPages( wasm::IndexType t, wasm::Pages newPages, Handle oldBuf, JSContext* cx); static void wasmDiscard(Handle 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); /** * Return the byte length for fixed-length buffers or the maximum byte length * for resizable buffers. */ inline size_t maxByteLength() const; 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 static std::tuple createBufferAndData( JSContext* cx, size_t byteLength, size_t maxByteLength, AutoSetNewObjectMetadata& metadata, Handle proto); static ResizableArrayBufferObject* createEmpty(JSContext* cx); public: static ResizableArrayBufferObject* createZeroed( JSContext* cx, size_t byteLength, size_t maxByteLength, Handle 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 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 source); private: static ResizableArrayBufferObject* copyAndDetachSteal( JSContext* cx, size_t newByteLength, JS::Handle source); }; size_t ArrayBufferObject::maxByteLength() const { if (isResizable()) { return as().maxByteLength(); } return byteLength(); } // 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, 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, Views, StableCellHasher, 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 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; friend class ResizableArrayBufferObject; bool addView(JSContext* cx, ArrayBufferObject* buffer, ArrayBufferViewObject* view); ViewVector* maybeViewsUnbarriered(ArrayBufferObject* buffer); void removeViews(ArrayBufferObject* buffer); }; template class MutableWrappedPtrOperations : public WrappedPtrOperations { InnerViewTable& table() { return static_cast(this)->get(); } public: size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { return table().sizeOfExcludingThis(mallocSizeOf); } }; class WasmArrayRawBuffer { wasm::IndexType indexType_; wasm::Pages clampedMaxPages_; mozilla::Maybe 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& 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& sourceMaxPages, const mozilla::Maybe& mappedSize); static void Release(void* mem); uint8_t* dataPointer() { uint8_t* ptr = reinterpret_cast(this); return ptr + sizeof(WasmArrayRawBuffer); } static const WasmArrayRawBuffer* fromDataPtr(const uint8_t* dataPtr) { return reinterpret_cast( dataPtr - sizeof(WasmArrayRawBuffer)); } static WasmArrayRawBuffer* fromDataPtr(uint8_t* dataPtr) { return reinterpret_cast(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 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() const { return is() || is(); } template <> bool JSObject::is() const; #endif // vm_ArrayBufferObject_h