blob: e7fc142ecd8bdc701444d8b275a19deaea33cddc [file] [log] [blame]
/* -*- 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 "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;
// 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 neutered 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);
ArrayBufferObjectMaybeShared& AsAnyArrayBuffer(HandleValue val);
class ArrayBufferObjectMaybeShared : public NativeObject
{
public:
uint32_t byteLength() {
return AnyArrayBufferByteLength(this);
}
inline SharedMem<uint8_t*> dataPointerEither();
};
/*
* 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
ASMJS_MALLOCED = 1,
ASMJS_MAPPED = 2,
MAPPED = 3,
KIND_MASK = 0x3
};
protected:
enum ArrayBufferFlags {
// The flags also store the BufferKind
BUFFER_KIND_MASK = BufferKind::KIND_MASK,
NEUTERED = 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
};
static_assert(JS_ARRAYBUFFER_NEUTERED_FLAG == NEUTERED,
"self-hosted code with burned-in constants must use the "
"correct NEUTERED 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; }
};
static const Class class_;
static const Class protoClass;
static const JSFunctionSpec jsfuncs[];
static const JSFunctionSpec jsstaticfuncs[];
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);
#ifdef NIGHTLY_BUILD
static bool fun_transfer(JSContext* cx, unsigned argc, Value* vp);
#endif
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);
static JSObject* createSlice(JSContext* cx, Handle<ArrayBufferObject*> arrayBuffer,
uint32_t begin, uint32_t end);
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 trace(JSTracer* trc, JSObject* obj);
static void objectMoved(JSObject* obj, const JSObject* old);
static BufferContents stealContents(JSContext* cx,
Handle<ArrayBufferObject*> buffer,
bool hasStealableContents);
bool hasStealableContents() const {
// Inline elements strictly adhere to the corresponding buffer.
if (!ownsData())
return false;
// Neutered contents aren't transferrable because we want a neutered
// array's contents to be backed by zeroed memory equal in length to
// the original buffer contents. Transferring these contents would
// allocate new ones based on the current byteLength, which is 0 for a
// neutered array -- not the original byteLength.
return !isNeutered();
}
// Return whether the buffer is allocated by js_malloc and should be freed
// with js_free.
bool hasMallocedContents() const {
return (ownsData() && isPlain()) || isAsmJSMalloced();
}
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 setNewOwnedData(FreeOp* fop, BufferContents newContents);
void changeContents(JSContext* cx, BufferContents newContents);
/*
* Ensure data is not stored inline in the object. Used when handing back a
* GC-safe pointer.
*/
static bool ensureNonInline(JSContext* cx, Handle<ArrayBufferObject*> buffer);
/* Neuter this buffer and all its views. */
static MOZ_WARN_UNUSED_RESULT bool
neuter(JSContext* cx, Handle<ArrayBufferObject*> buffer, BufferContents newContents);
private:
void neuterView(JSContext* cx, ArrayBufferViewObject* view,
BufferContents newContents);
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;
size_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 neutered ArrayBuffers.
*/
bool hasData() const {
return getClass() == &class_;
}
BufferKind bufferKind() const { return BufferKind(flags() & BUFFER_KIND_MASK); }
bool isPlain() const { return bufferKind() == PLAIN; }
bool isAsmJSMapped() const { return bufferKind() == ASMJS_MAPPED; }
bool isAsmJSMalloced() const { return bufferKind() == ASMJS_MALLOCED; }
bool isAsmJS() const { return isAsmJSMapped() || isAsmJSMalloced(); }
bool isMapped() const { return bufferKind() == MAPPED; }
bool isNeutered() const { return flags() & NEUTERED; }
static bool prepareForAsmJS(JSContext* cx, Handle<ArrayBufferObject*> buffer,
bool usesSignalHandlers);
static bool prepareForAsmJSNoSignals(JSContext* cx, Handle<ArrayBufferObject*> buffer);
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);
}
static uint32_t neuteredFlag() { return NEUTERED; }
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(size_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 setIsAsmJSMalloced() { setFlags((flags() & ~KIND_MASK) | ASMJS_MALLOCED); }
void setIsNeutered() { setFlags(flags() | NEUTERED); }
void initialize(size_t byteLength, BufferContents contents, OwnsState ownsState) {
setByteLength(byteLength);
setFlags(0);
setFirstView(nullptr);
setDataPointer(contents, ownsState);
}
};
/*
* ArrayBufferViewObject
*
* Common definitions shared by all array buffer views.
*/
class ArrayBufferViewObject : public JSObject
{
public:
static ArrayBufferObjectMaybeShared* bufferObject(JSContext* cx, Handle<ArrayBufferViewObject*> obj);
void neuter(void* newData);
#ifdef DEBUG
bool isSharedMemory();
#endif
// By construction we only need unshared variants here. See
// comments in ArrayBufferObject.cpp.
uint8_t* dataPointerUnshared();
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;
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() {
return !nurseryKeys.empty() || !nurseryKeysValid;
}
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
};
extern JSObject*
InitArrayBufferClass(JSContext* cx, HandleObject obj);
} // namespace js
template <>
bool
JSObject::is<js::ArrayBufferViewObject>() const;
template <>
bool
JSObject::is<js::ArrayBufferObjectMaybeShared>() const;
#endif // vm_ArrayBufferObject_h