diff options
Diffstat (limited to 'js/src/vm/ArrayBufferObject.h')
-rw-r--r-- | js/src/vm/ArrayBufferObject.h | 641 |
1 files changed, 641 insertions, 0 deletions
diff --git a/js/src/vm/ArrayBufferObject.h b/js/src/vm/ArrayBufferObject.h new file mode 100644 index 000000000..6614f5220 --- /dev/null +++ b/js/src/vm/ArrayBufferObject.h @@ -0,0 +1,641 @@ +/* -*- 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/. */ + +#ifndef vm_ArrayBufferObject_h +#define vm_ArrayBufferObject_h + +#include "mozilla/Maybe.h" + +#include "jsobj.h" + +#include "builtin/TypedObjectConstants.h" +#include "js/GCHashTable.h" +#include "vm/Runtime.h" +#include "vm/SharedMem.h" + +typedef struct JSProperty JSProperty; + +namespace js { + +class ArrayBufferViewObject; +class WasmArrayRawBuffer; + +// The inheritance hierarchy for the various classes relating to typed arrays +// is as follows. +// +// - NativeObject +// - ArrayBufferObjectMaybeShared +// - ArrayBufferObject +// - SharedArrayBufferObject +// - DataViewObject +// - TypedArrayObject (declared in vm/TypedArrayObject.h) +// - TypedArrayObjectTemplate +// - Int8ArrayObject +// - Uint8ArrayObject +// - ... +// - JSObject +// - ArrayBufferViewObject +// - TypedObject (declared in builtin/TypedObject.h) +// +// Note that |TypedArrayObjectTemplate| is just an implementation +// detail that makes implementing its various subclasses easier. +// +// 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. +// (4) Data allocated inline with an InlineTypedObject. +// +// 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) and (4) may move. During a compacting GC, (2), (3), +// and (4) may move. + +class ArrayBufferObjectMaybeShared; + +uint32_t AnyArrayBufferByteLength(const ArrayBufferObjectMaybeShared* buf); +mozilla::Maybe<uint32_t> WasmArrayBufferMaxSize(const ArrayBufferObjectMaybeShared* buf); +size_t WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf); +bool WasmArrayBufferGrowForWasm(ArrayBufferObjectMaybeShared* buf, uint32_t delta); +bool AnyArrayBufferIsPreparedForAsmJS(const ArrayBufferObjectMaybeShared* buf); +ArrayBufferObjectMaybeShared& AsAnyArrayBuffer(HandleValue val); + +class ArrayBufferObjectMaybeShared : public NativeObject +{ + public: + uint32_t byteLength() { + return AnyArrayBufferByteLength(this); + } + + inline bool isDetached() const; + + inline SharedMem<uint8_t*> dataPointerEither(); + + // WebAssembly support: + // Note: the eventual goal is to remove this from ArrayBuffer and have + // (Shared)ArrayBuffers alias memory owned by some wasm::Memory object. + + mozilla::Maybe<uint32_t> wasmMaxSize() const { + return WasmArrayBufferMaxSize(this); + } + size_t wasmMappedSize() const { + return WasmArrayBufferMappedSize(this); + } +#ifndef WASM_HUGE_MEMORY + uint32_t wasmBoundsCheckLimit() const; +#endif + + bool isPreparedForAsmJS() const { + return AnyArrayBufferIsPreparedForAsmJS(this); + } +}; + +typedef Rooted<ArrayBufferObjectMaybeShared*> RootedArrayBufferObjectMaybeShared; +typedef Handle<ArrayBufferObjectMaybeShared*> HandleArrayBufferObjectMaybeShared; +typedef MutableHandle<ArrayBufferObjectMaybeShared*> MutableHandleArrayBufferObjectMaybeShared; + +/* + * ArrayBufferObject + * + * This class holds the underlying raw buffer that the various ArrayBufferViews + * (eg DataViewObject, the TypedArrays, TypedObjects) 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 or TypedObject that + * doesn't have an explicit buffer. + * + * 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 fun_slice_impl(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"); + + public: + + enum OwnsState { + DoesntOwnData = 0, + OwnsData = 1, + }; + + enum BufferKind { + PLAIN = 0, // malloced or inline data + WASM = 1, + MAPPED = 2, + + KIND_MASK = 0x3 + }; + + protected: + + enum ArrayBufferFlags { + // The flags also store the BufferKind + BUFFER_KIND_MASK = BufferKind::KIND_MASK, + + DETACHED = 0x4, + + // The dataPointer() is owned by this buffer and should be released + // when no longer in use. Releasing the pointer may be done by either + // freeing or unmapping it, and how to do this is determined by the + // buffer's other flags. + // + // Array buffers which do not own their data include buffers that + // allocate their data inline, and buffers that are created lazily for + // typed objects with inline storage, in which case the buffer points + // directly to the typed object's storage. + OWNS_DATA = 0x8, + + // This array buffer was created lazily for a typed object with inline + // data. This implies both that the typed object owns the buffer's data + // and that the list of views sharing this buffer's data might be + // incomplete. Any missing views will be typed objects. + FOR_INLINE_TYPED_OBJECT = 0x10, + + // Views of this buffer might include typed objects. + TYPED_OBJECT_VIEWS = 0x20, + + // This PLAIN or WASM buffer has been prepared for asm.js and cannot + // henceforth be transferred/detached. + FOR_ASMJS = 0x40 + }; + + static_assert(JS_ARRAYBUFFER_DETACHED_FLAG == DETACHED, + "self-hosted code with burned-in constants must use the " + "correct DETACHED bit value"); + public: + + class BufferContents { + uint8_t* data_; + BufferKind kind_; + + friend class ArrayBufferObject; + + BufferContents(uint8_t* data, BufferKind kind) : data_(data), kind_(kind) { + MOZ_ASSERT((kind_ & ~KIND_MASK) == 0); + } + + public: + + template<BufferKind Kind> + static BufferContents create(void* data) + { + return BufferContents(static_cast<uint8_t*>(data), Kind); + } + + static BufferContents createPlain(void* data) + { + return BufferContents(static_cast<uint8_t*>(data), PLAIN); + } + + uint8_t* data() const { return data_; } + BufferKind kind() const { return kind_; } + + explicit operator bool() const { return data_ != nullptr; } + WasmArrayRawBuffer* wasmBuffer() const; + }; + + static const Class class_; + + static bool byteLengthGetter(JSContext* cx, unsigned argc, Value* vp); + + static bool fun_slice(JSContext* cx, unsigned argc, Value* vp); + + static bool fun_isView(JSContext* cx, unsigned argc, Value* vp); + + static bool fun_species(JSContext* cx, unsigned argc, Value* vp); + + static bool class_constructor(JSContext* cx, unsigned argc, Value* vp); + + static ArrayBufferObject* create(JSContext* cx, uint32_t nbytes, + BufferContents contents, + OwnsState ownsState = OwnsData, + HandleObject proto = nullptr, + NewObjectKind newKind = GenericObject); + static ArrayBufferObject* create(JSContext* cx, uint32_t nbytes, + HandleObject proto = nullptr, + NewObjectKind newKind = GenericObject); + + // 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); + + static bool createDataViewForThisImpl(JSContext* cx, const CallArgs& args); + static bool createDataViewForThis(JSContext* cx, unsigned argc, Value* vp); + + template<typename T> + static bool createTypedArrayFromBufferImpl(JSContext* cx, const CallArgs& args); + + template<typename T> + static bool createTypedArrayFromBuffer(JSContext* cx, unsigned argc, Value* vp); + + static void copyData(Handle<ArrayBufferObject*> toBuffer, + Handle<ArrayBufferObject*> fromBuffer, + uint32_t fromIndex, uint32_t count); + + static void trace(JSTracer* trc, JSObject* obj); + static void objectMoved(JSObject* obj, const JSObject* old); + + static BufferContents externalizeContents(JSContext* cx, + Handle<ArrayBufferObject*> buffer, + bool hasStealableContents); + static BufferContents stealContents(JSContext* cx, + Handle<ArrayBufferObject*> buffer, + bool hasStealableContents); + + bool hasStealableContents() const { + // Inline elements strictly adhere to the corresponding buffer. + return ownsData() && !isPreparedForAsmJS() && !isWasm(); + } + + static void addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSizeOf mallocSizeOf, + JS::ClassInfo* info); + + // 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. + ArrayBufferViewObject* firstView(); + + bool addView(JSContext* cx, JSObject* view); + + void setNewData(FreeOp* fop, BufferContents newContents, OwnsState ownsState); + void changeContents(JSContext* cx, BufferContents newContents, OwnsState ownsState); + + // 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, BufferContents newContents); + + private: + void changeViewContents(JSContext* cx, ArrayBufferViewObject* view, + uint8_t* oldDataPointer, BufferContents newContents); + void setFirstView(ArrayBufferViewObject* view); + + uint8_t* inlineDataPointer() const; + + public: + uint8_t* dataPointer() const; + SharedMem<uint8_t*> dataPointerShared() const; + uint32_t byteLength() const; + + BufferContents contents() const { + return BufferContents(dataPointer(), bufferKind()); + } + bool hasInlineData() const { + return dataPointer() == inlineDataPointer(); + } + + void releaseData(FreeOp* fop); + + /* + * Check if the arrayBuffer contains any data. This will return false for + * ArrayBuffer.prototype and detached ArrayBuffers. + */ + bool hasData() const { + return getClass() == &class_; + } + + BufferKind bufferKind() const { return BufferKind(flags() & BUFFER_KIND_MASK); } + bool isPlain() const { return bufferKind() == PLAIN; } + bool isWasm() const { return bufferKind() == WASM; } + bool isMapped() const { return bufferKind() == MAPPED; } + bool isDetached() const { return flags() & DETACHED; } + bool isPreparedForAsmJS() const { return flags() & FOR_ASMJS; } + + // WebAssembly support: + static ArrayBufferObject* createForWasm(JSContext* cx, uint32_t initialSize, + mozilla::Maybe<uint32_t> maxSize); + static MOZ_MUST_USE bool prepareForAsmJS(JSContext* cx, Handle<ArrayBufferObject*> buffer, + bool needGuard); + size_t wasmMappedSize() const; + mozilla::Maybe<uint32_t> wasmMaxSize() const; + static MOZ_MUST_USE bool wasmGrowToSizeInPlace(uint32_t newSize, + Handle<ArrayBufferObject*> oldBuf, + MutableHandle<ArrayBufferObject*> newBuf, + JSContext* cx); +#ifndef WASM_HUGE_MEMORY + static MOZ_MUST_USE bool wasmMovingGrowToSize(uint32_t newSize, + Handle<ArrayBufferObject*> oldBuf, + MutableHandle<ArrayBufferObject*> newBuf, + JSContext* cx); + uint32_t wasmBoundsCheckLimit() const; +#endif + + static void finalize(FreeOp* fop, JSObject* obj); + + static BufferContents createMappedContents(int fd, size_t offset, size_t length); + + static size_t offsetOfFlagsSlot() { + return getFixedSlotOffset(FLAGS_SLOT); + } + static size_t offsetOfDataSlot() { + return getFixedSlotOffset(DATA_SLOT); + } + + void setForInlineTypedObject() { + setFlags(flags() | FOR_INLINE_TYPED_OBJECT); + } + void setHasTypedObjectViews() { + setFlags(flags() | TYPED_OBJECT_VIEWS); + } + + bool forInlineTypedObject() const { return flags() & FOR_INLINE_TYPED_OBJECT; } + + protected: + void setDataPointer(BufferContents contents, OwnsState ownsState); + void setByteLength(uint32_t length); + + uint32_t flags() const; + void setFlags(uint32_t flags); + + bool ownsData() const { return flags() & OWNS_DATA; } + void setOwnsData(OwnsState owns) { + setFlags(owns ? (flags() | OWNS_DATA) : (flags() & ~OWNS_DATA)); + } + + bool hasTypedObjectViews() const { return flags() & TYPED_OBJECT_VIEWS; } + + void setIsDetached() { setFlags(flags() | DETACHED); } + void setIsPreparedForAsmJS() { setFlags(flags() | FOR_ASMJS); } + + void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState) { + setByteLength(byteLength); + setFlags(0); + setFirstView(nullptr); + setDataPointer(contents, ownsState); + } + + // Note: initialize() may be called after initEmpty(); initEmpty() must + // only initialize the ArrayBufferObject to a safe, finalizable state. + void initEmpty() { + setByteLength(0); + setFlags(0); + setFirstView(nullptr); + setDataPointer(BufferContents::createPlain(nullptr), DoesntOwnData); + } +}; + +typedef Rooted<ArrayBufferObject*> RootedArrayBufferObject; +typedef Handle<ArrayBufferObject*> HandleArrayBufferObject; +typedef MutableHandle<ArrayBufferObject*> MutableHandleArrayBufferObject; + +/* + * ArrayBufferViewObject + * + * Common definitions shared by all array buffer views. + */ + +class ArrayBufferViewObject : public JSObject +{ + public: + static ArrayBufferObjectMaybeShared* bufferObject(JSContext* cx, Handle<ArrayBufferViewObject*> obj); + + void notifyBufferDetached(JSContext* cx, void* newData); + +#ifdef DEBUG + bool isSharedMemory(); +#endif + + // By construction we only need unshared variants here. See + // comments in ArrayBufferObject.cpp. + uint8_t* dataPointerUnshared(const JS::AutoRequireNoGC&); + void setDataPointerUnshared(uint8_t* data); + + static void trace(JSTracer* trc, JSObject* obj); +}; + +bool +ToClampedIndex(JSContext* cx, HandleValue v, uint32_t length, uint32_t* out); + +/* + * Tests for ArrayBufferObject, like obj->is<ArrayBufferObject>(). + */ +bool IsArrayBuffer(HandleValue v); +bool IsArrayBuffer(HandleObject obj); +bool IsArrayBuffer(JSObject* obj); +ArrayBufferObject& AsArrayBuffer(HandleObject obj); +ArrayBufferObject& AsArrayBuffer(JSObject* obj); + +extern uint32_t JS_FASTCALL +ClampDoubleToUint8(const double x); + +struct uint8_clamped { + uint8_t val; + + uint8_clamped() { } + uint8_clamped(const uint8_clamped& other) : val(other.val) { } + + // invoke our assignment helpers for constructor conversion + explicit uint8_clamped(uint8_t x) { *this = x; } + explicit uint8_clamped(uint16_t x) { *this = x; } + explicit uint8_clamped(uint32_t x) { *this = x; } + explicit uint8_clamped(int8_t x) { *this = x; } + explicit uint8_clamped(int16_t x) { *this = x; } + explicit uint8_clamped(int32_t x) { *this = x; } + explicit uint8_clamped(double x) { *this = x; } + + uint8_clamped& operator=(const uint8_clamped& x) { + val = x.val; + return *this; + } + + uint8_clamped& operator=(uint8_t x) { + val = x; + return *this; + } + + uint8_clamped& operator=(uint16_t x) { + val = (x > 255) ? 255 : uint8_t(x); + return *this; + } + + uint8_clamped& operator=(uint32_t x) { + val = (x > 255) ? 255 : uint8_t(x); + return *this; + } + + uint8_clamped& operator=(int8_t x) { + val = (x >= 0) ? uint8_t(x) : 0; + return *this; + } + + uint8_clamped& operator=(int16_t x) { + val = (x >= 0) + ? ((x < 255) + ? uint8_t(x) + : 255) + : 0; + return *this; + } + + uint8_clamped& operator=(int32_t x) { + val = (x >= 0) + ? ((x < 255) + ? uint8_t(x) + : 255) + : 0; + return *this; + } + + uint8_clamped& operator=(const double x) { + val = uint8_t(ClampDoubleToUint8(x)); + return *this; + } + + operator uint8_t() const { + return val; + } + + void staticAsserts() { + static_assert(sizeof(uint8_clamped) == 1, + "uint8_clamped must be layout-compatible with uint8_t"); + } +}; + +/* Note that we can't use std::numeric_limits here due to uint8_clamped. */ +template<typename T> inline bool TypeIsFloatingPoint() { return false; } +template<> inline bool TypeIsFloatingPoint<float>() { return true; } +template<> inline bool TypeIsFloatingPoint<double>() { return true; } + +template<typename T> inline bool TypeIsUnsigned() { return false; } +template<> inline bool TypeIsUnsigned<uint8_t>() { return true; } +template<> inline bool TypeIsUnsigned<uint16_t>() { return true; } +template<> inline bool TypeIsUnsigned<uint32_t>() { return true; } + +// Per-compartment table that manages the relationship between array buffers +// and the views that use their storage. +class InnerViewTable +{ + public: + typedef Vector<ArrayBufferViewObject*, 1, SystemAllocPolicy> ViewVector; + + friend class ArrayBufferObject; + friend class WeakCacheBase<InnerViewTable>; + + private: + struct MapGCPolicy { + static bool needsSweep(JSObject** key, ViewVector* value) { + return InnerViewTable::sweepEntry(key, *value); + } + }; + + // This key is a raw pointer and not a ReadBarriered 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 tenured 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. + typedef GCHashMap<JSObject*, + ViewVector, + MovableCellHasher<JSObject*>, + SystemAllocPolicy, + MapGCPolicy> Map; + + // 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. + Map 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<JSObject*, 0, SystemAllocPolicy> nurseryKeys; + + // Whether nurseryKeys is a complete list. + bool nurseryKeysValid; + + // Sweep an entry during GC, returning whether the entry should be removed. + static bool sweepEntry(JSObject** pkey, ViewVector& views); + + bool addView(JSContext* cx, ArrayBufferObject* obj, ArrayBufferViewObject* view); + ViewVector* maybeViewsUnbarriered(ArrayBufferObject* obj); + void removeViews(ArrayBufferObject* obj); + + public: + InnerViewTable() + : nurseryKeysValid(true) + {} + + // Remove references to dead objects in the table and update table entries + // to reflect moved objects. + void sweep(); + void sweepAfterMinorGC(); + + bool needsSweepAfterMinorGC() const { + return !nurseryKeys.empty() || !nurseryKeysValid; + } + + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf); +}; + +template <> +class WeakCacheBase<InnerViewTable> +{ + InnerViewTable& table() { + return static_cast<JS::WeakCache<InnerViewTable>*>(this)->get(); + } + const InnerViewTable& table() const { + return static_cast<const JS::WeakCache<InnerViewTable>*>(this)->get(); + } + + public: + InnerViewTable::ViewVector* maybeViewsUnbarriered(ArrayBufferObject* obj) { + return table().maybeViewsUnbarriered(obj); + } + void removeViews(ArrayBufferObject* obj) { table().removeViews(obj); } + void sweepAfterMinorGC() { table().sweepAfterMinorGC(); } + bool needsSweepAfterMinorGC() const { return table().needsSweepAfterMinorGC(); } + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) { + return table().sizeOfExcludingThis(mallocSizeOf); + } +}; + +} // namespace js + +template <> +bool +JSObject::is<js::ArrayBufferViewObject>() const; + +template <> +bool +JSObject::is<js::ArrayBufferObjectMaybeShared>() const; + +#endif // vm_ArrayBufferObject_h |