Raw File
ArrayBufferObject.h
/* -*- 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
back to top