#ifndef vm_ArrayBufferObject_h
#define vm_ArrayBufferObject_h
#include "mozilla/Maybe.h"
#include "builtin/TypedObjectConstants.h"
#include "js/ArrayBuffer.h"
#include "js/GCHashTable.h"
#include "vm/JSObject.h"
#include "vm/Runtime.h"
#include "vm/SharedMem.h"
#include "wasm/WasmTypes.h"
namespace js {
class ArrayBufferViewObject;
class WasmArrayRawBuffer;
void* MapBufferMemory(size_t mappedSize, size_t initialCommittedSize);
bool CommitBufferMemory(void* dataEnd, uint32_t delta);
#ifndef WASM_HUGE_MEMORY
bool ExtendBufferMapping(void* dataStart, size_t mappedSize,
size_t newMappedSize);
#endif
void UnmapBufferMemory(void* dataStart, size_t mappedSize);
int32_t LiveMappedBufferCount();
class ArrayBufferObjectMaybeShared;
mozilla::Maybe<uint32_t> WasmArrayBufferMaxSize(
const ArrayBufferObjectMaybeShared* buf);
size_t WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf);
class ArrayBufferObjectMaybeShared : public NativeObject {
public:
inline uint32_t byteLength() const;
inline bool isDetached() const;
inline SharedMem<uint8_t*> dataPointerEither();
mozilla::Maybe<uint32_t> wasmMaxSize() const {
return WasmArrayBufferMaxSize(this);
}
size_t wasmMappedSize() const { return WasmArrayBufferMappedSize(this); }
uint32_t wasmBoundsCheckLimit() const;
inline bool isPreparedForAsmJS() const;
inline bool isWasm() const;
};
typedef Rooted<ArrayBufferObjectMaybeShared*>
RootedArrayBufferObjectMaybeShared;
typedef Handle<ArrayBufferObjectMaybeShared*>
HandleArrayBufferObjectMaybeShared;
typedef MutableHandle<ArrayBufferObjectMaybeShared*>
MutableHandleArrayBufferObjectMaybeShared;
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");
static constexpr size_t MaxBufferByteLength = INT32_MAX;
static constexpr size_t MaxInlineBytes =
(NativeObject::MAX_FIXED_SLOTS - RESERVED_SLOTS) * sizeof(JS::Value);
public:
enum OwnsState {
DoesntOwnData = 0,
OwnsData = 1,
};
enum BufferKind {
INLINE_DATA = 0b000,
MALLOCED = 0b001,
NO_DATA = 0b010,
USER_OWNED = 0b011,
WASM = 0b100,
MAPPED = 0b101,
EXTERNAL = 0b110,
BAD1 = 0b111,
KIND_MASK = 0b111
};
protected:
enum ArrayBufferFlags {
BUFFER_KIND_MASK = BufferKind::KIND_MASK,
DETACHED = 0b1000,
TYPED_OBJECT_VIEWS = 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
FOR_ASMJS = 0b10'0000,
};
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_;
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
}
public:
static BufferContents createInlineData(void* data) {
return BufferContents(static_cast<uint8_t*>(data), INLINE_DATA);
}
static BufferContents createMalloced(void* data) {
return BufferContents(static_cast<uint8_t*>(data), MALLOCED);
}
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) {
return BufferContents(static_cast<uint8_t*>(data), EXTERNAL, freeFunc,
freeUserData);
}
static BufferContents createFailed() {
return BufferContents(nullptr, MALLOCED);
}
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 Class class_;
static const Class protoClass_;
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* createForContents(JSContext* cx, uint32_t nbytes,
BufferContents contents);
static ArrayBufferObject* createZeroed(JSContext* cx, uint32_t nbytes,
HandleObject proto = nullptr);
static ArrayBufferObject* createEmpty(JSContext* cx);
static ArrayBufferObject* createFromNewRawBuffer(JSContext* cx,
WasmArrayRawBuffer* buffer,
uint32_t initialSize);
static void copyData(Handle<ArrayBufferObject*> toBuffer, uint32_t toIndex,
Handle<ArrayBufferObject*> fromBuffer,
uint32_t fromIndex, uint32_t count);
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);
JSObject* firstView();
bool addView(JSContext* cx, JSObject* view);
static void detach(JSContext* cx, Handle<ArrayBufferObject*> buffer);
private:
void setFirstView(JSObject* view);
uint8_t* inlineDataPointer() const;
struct FreeInfo {
JS::BufferContentsFreeFunc freeFunc;
void* freeUserData;
};
FreeInfo* freeInfo() const;
public:
uint8_t* dataPointer() const;
SharedMem<uint8_t*> dataPointerShared() const;
uint32_t byteLength() const;
BufferContents contents() const {
if (isExternal()) {
return BufferContents(dataPointer(), EXTERNAL, freeInfo()->freeFunc,
freeInfo()->freeUserData);
}
return BufferContents(dataPointer(), bufferKind());
}
bool hasInlineData() const { return dataPointer() == inlineDataPointer(); }
void releaseData(FreeOp* fop);
BufferKind bufferKind() const {
return BufferKind(flags() & BUFFER_KIND_MASK);
}
bool isInlineData() const { return bufferKind() == INLINE_DATA; }
bool isMalloced() const { return bufferKind() == MALLOCED; }
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 isPreparedForAsmJS() const { return flags() & FOR_ASMJS; }
MOZ_MUST_USE bool prepareForAsmJS();
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);
#endif
uint32_t wasmBoundsCheckLimit() const;
static void finalize(FreeOp* fop, JSObject* obj);
static BufferContents createMappedContents(int fd, size_t offset,
size_t length);
static size_t offsetOfDataSlot() { return getFixedSlotOffset(DATA_SLOT); }
void setHasTypedObjectViews() { setFlags(flags() | TYPED_OBJECT_VIEWS); }
protected:
void setDataPointer(BufferContents contents);
void setByteLength(uint32_t length);
uint32_t flags() const;
void setFlags(uint32_t flags);
bool hasTypedObjectViews() const { return flags() & TYPED_OBJECT_VIEWS; }
void setIsDetached() { 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);
}
void* initializeToInlineData(size_t byteLength) {
void* data = inlineDataPointer();
initialize(byteLength, BufferContents::createInlineData(data));
return data;
}
};
typedef Rooted<ArrayBufferObject*> RootedArrayBufferObject;
typedef Handle<ArrayBufferObject*> HandleArrayBufferObject;
typedef MutableHandle<ArrayBufferObject*> MutableHandleArrayBufferObject;
bool CreateWasmBuffer(JSContext* cx, const wasm::Limits& memory,
MutableHandleArrayBufferObjectMaybeShared buffer);
bool ToClampedIndex(JSContext* cx, HandleValue v, uint32_t length,
uint32_t* out);
bool IsArrayBuffer(HandleValue v);
bool IsArrayBuffer(HandleObject obj);
bool IsArrayBuffer(JSObject* obj);
ArrayBufferObject& AsArrayBuffer(HandleObject obj);
ArrayBufferObject& AsArrayBuffer(JSObject* obj);
bool IsArrayBufferMaybeShared(HandleValue v);
bool IsArrayBufferMaybeShared(HandleObject obj);
bool IsArrayBufferMaybeShared(JSObject* obj);
ArrayBufferObjectMaybeShared& AsArrayBufferMaybeShared(HandleObject obj);
ArrayBufferObjectMaybeShared& AsArrayBufferMaybeShared(JSObject* obj);
extern uint32_t JS_FASTCALL ClampDoubleToUint8(const double x);
struct uint8_clamped {
uint8_t val;
uint8_clamped() = default;
uint8_clamped(const uint8_clamped& other) = default;
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) = default;
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");
}
};
template <typename T>
inline constexpr bool TypeIsFloatingPoint() {
return false;
}
template <>
inline constexpr bool TypeIsFloatingPoint<float>() {
return true;
}
template <>
inline constexpr bool TypeIsFloatingPoint<double>() {
return true;
}
template <typename T>
inline constexpr bool TypeIsUnsigned() {
return false;
}
template <>
inline constexpr bool TypeIsUnsigned<uint8_t>() {
return true;
}
template <>
inline constexpr bool TypeIsUnsigned<uint16_t>() {
return true;
}
template <>
inline constexpr bool TypeIsUnsigned<uint32_t>() {
return true;
}
class InnerViewTable {
public:
typedef Vector<JSObject*, 1, SystemAllocPolicy> ViewVector;
friend class ArrayBufferObject;
private:
struct MapGCPolicy {
static bool needsSweep(JSObject** key, ViewVector* value) {
return InnerViewTable::sweepEntry(key, *value);
}
};
typedef GCHashMap<JSObject*, ViewVector, MovableCellHasher<JSObject*>,
SystemAllocPolicy, MapGCPolicy>
Map;
Map map;
Vector<JSObject*, 0, SystemAllocPolicy> nurseryKeys;
bool nurseryKeysValid;
static bool sweepEntry(JSObject** pkey, ViewVector& views);
bool addView(JSContext* cx, ArrayBufferObject* buffer, JSObject* view);
ViewVector* maybeViewsUnbarriered(ArrayBufferObject* obj);
void removeViews(ArrayBufferObject* obj);
public:
InnerViewTable() : nurseryKeysValid(true) {}
void sweep();
void sweepAfterMinorGC();
bool needsSweep() const { return map.needsSweep(); }
bool needsSweepAfterMinorGC() const {
return !nurseryKeys.empty() || !nurseryKeysValid;
}
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
};
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);
}
};
}
template <>
bool JSObject::is<js::ArrayBufferObjectMaybeShared>() const;
#endif