/* -*- 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_TypedArrayObject_h #define vm_TypedArrayObject_h #include "mozilla/Attributes.h" #include "jsobj.h" #include "gc/Barrier.h" #include "js/Class.h" #include "vm/ArrayBufferObject.h" #include "vm/SharedArrayObject.h" #define JS_FOR_EACH_TYPED_ARRAY(macro) \ macro(int8_t, Int8) \ macro(uint8_t, Uint8) \ macro(int16_t, Int16) \ macro(uint16_t, Uint16) \ macro(int32_t, Int32) \ macro(uint32_t, Uint32) \ macro(float, Float32) \ macro(double, Float64) \ macro(uint8_clamped, Uint8Clamped) typedef struct JSProperty JSProperty; namespace js { enum class TypedArrayLength { Fixed, Dynamic }; /* * TypedArrayObject * * The non-templated base class for the specific typed implementations. * This class holds all the member variables that are used by * the subclasses. */ class TypedArrayObject : public NativeObject { public: // Underlying (Shared)ArrayBufferObject. static const size_t BUFFER_SLOT = 0; static_assert(BUFFER_SLOT == JS_TYPEDARRAYLAYOUT_BUFFER_SLOT, "self-hosted code with burned-in constants must get the " "right buffer slot"); // Slot containing length of the view in number of typed elements. static const size_t LENGTH_SLOT = 1; static_assert(LENGTH_SLOT == JS_TYPEDARRAYLAYOUT_LENGTH_SLOT, "self-hosted code with burned-in constants must get the " "right length slot"); // Offset of view within underlying (Shared)ArrayBufferObject. static const size_t BYTEOFFSET_SLOT = 2; static_assert(BYTEOFFSET_SLOT == JS_TYPEDARRAYLAYOUT_BYTEOFFSET_SLOT, "self-hosted code with burned-in constants must get the " "right byteOffset slot"); static const size_t RESERVED_SLOTS = 3; #ifdef DEBUG static const uint8_t ZeroLengthArrayData = 0x4A; #endif static int lengthOffset(); static int dataOffset(); // The raw pointer to the buffer memory, the "private" value. // // This offset is exposed for performance reasons - so that it // need not be looked up on accesses. static const size_t DATA_SLOT = 3; static_assert(js::detail::TypedArrayLengthSlot == LENGTH_SLOT, "bad inlined constant in jsfriendapi.h"); typedef TypedArrayObject SomeTypedArray; typedef ArrayBufferObject BufferType; template struct OfType; static bool sameBuffer(Handle a, Handle b) { // Inline buffers. if (!a->hasBuffer() || !b->hasBuffer()) return a.get() == b.get(); // Shared buffers. if (a->isSharedMemory() && b->isSharedMemory()) { return (a->bufferObject()->as().globalID() == b->bufferObject()->as().globalID()); } return a->bufferObject() == b->bufferObject(); } static const Class classes[Scalar::MaxTypedArrayViewType]; static const Class protoClasses[Scalar::MaxTypedArrayViewType]; static const Class sharedTypedArrayPrototypeClass; static const Class* classForType(Scalar::Type type) { MOZ_ASSERT(type < Scalar::MaxTypedArrayViewType); return &classes[type]; } static const Class* protoClassForType(Scalar::Type type) { MOZ_ASSERT(type < Scalar::MaxTypedArrayViewType); return &protoClasses[type]; } static const size_t FIXED_DATA_START = DATA_SLOT + 1; // For typed arrays which can store their data inline, the array buffer // object is created lazily. static const uint32_t INLINE_BUFFER_LIMIT = (NativeObject::MAX_FIXED_SLOTS - FIXED_DATA_START) * sizeof(Value); static gc::AllocKind AllocKindForLazyBuffer(size_t nbytes) { MOZ_ASSERT(nbytes <= INLINE_BUFFER_LIMIT); if (nbytes == 0) nbytes += sizeof(uint8_t); size_t dataSlots = AlignBytes(nbytes, sizeof(Value)) / sizeof(Value); MOZ_ASSERT(nbytes <= dataSlots * sizeof(Value)); return gc::GetGCObjectKind(FIXED_DATA_START + dataSlots); } inline Scalar::Type type() const; inline size_t bytesPerElement() const; static Value bufferValue(TypedArrayObject* tarr) { return tarr->getFixedSlot(BUFFER_SLOT); } static Value byteOffsetValue(TypedArrayObject* tarr) { Value v = tarr->getFixedSlot(BYTEOFFSET_SLOT); MOZ_ASSERT(v.toInt32() >= 0); return v; } static Value byteLengthValue(TypedArrayObject* tarr) { return Int32Value(tarr->getFixedSlot(LENGTH_SLOT).toInt32() * tarr->bytesPerElement()); } static Value lengthValue(TypedArrayObject* tarr) { return tarr->getFixedSlot(LENGTH_SLOT); } static bool ensureHasBuffer(JSContext* cx, Handle tarray); bool hasBuffer() const { return bufferValue(const_cast(this)).isObject(); } JSObject* bufferObject() const { return bufferValue(const_cast(this)).toObjectOrNull(); } uint32_t byteOffset() const { return byteOffsetValue(const_cast(this)).toInt32(); } uint32_t byteLength() const { return byteLengthValue(const_cast(this)).toInt32(); } uint32_t length() const { return lengthValue(const_cast(this)).toInt32(); } bool hasInlineElements() const; void setInlineElements(); uint8_t* elementsRaw() const { return *(uint8_t **)((((char *)this) + this->dataOffset())); } uint8_t* elements() const { assertZeroLengthArrayData(); return elementsRaw(); } #ifdef DEBUG void assertZeroLengthArrayData() const; #else void assertZeroLengthArrayData() const {}; #endif Value getElement(uint32_t index); static void setElement(TypedArrayObject& obj, uint32_t index, double d); void notifyBufferDetached(JSContext* cx, void* newData); static bool GetTemplateObjectForNative(JSContext* cx, Native native, uint32_t len, MutableHandleObject res); /* * Byte length above which created typed arrays and data views will have * singleton types regardless of the context in which they are created. */ static const uint32_t SINGLETON_BYTE_LENGTH = 1024 * 1024 * 10; static bool isOriginalLengthGetter(Native native); ArrayBufferObject* bufferUnshared() const { MOZ_ASSERT(!isSharedMemory()); JSObject* obj = bufferValue(const_cast(this)).toObjectOrNull(); if (!obj) return nullptr; return &obj->as(); } SharedArrayBufferObject* bufferShared() const { MOZ_ASSERT(isSharedMemory()); JSObject* obj = bufferValue(const_cast(this)).toObjectOrNull(); if (!obj) return nullptr; return &obj->as(); } ArrayBufferObjectMaybeShared* bufferEither() const { JSObject* obj = bufferValue(const_cast(this)).toObjectOrNull(); if (!obj) return nullptr; if (isSharedMemory()) return &obj->as(); return &obj->as(); } SharedMem viewDataShared() const { return SharedMem::shared(viewDataEither_()); } SharedMem viewDataEither() const { if (isSharedMemory()) return SharedMem::shared(viewDataEither_()); return SharedMem::unshared(viewDataEither_()); } void initViewData(SharedMem viewData) { // Install a pointer to the buffer location that corresponds // to offset zero within the typed array. // // The following unwrap is safe because the DATA_SLOT is // accessed only from jitted code and from the // viewDataEither_() accessor below; in neither case does the // raw pointer escape untagged into C++ code. initPrivate(viewData.unwrap(/*safe - see above*/)); } void* viewDataUnshared() const { MOZ_ASSERT(!isSharedMemory()); return viewDataEither_(); } bool hasDetachedBuffer() const { // Shared buffers can't be detached. if (isSharedMemory()) return false; // A typed array with a null buffer has never had its buffer exposed to // become detached. ArrayBufferObject* buffer = bufferUnshared(); if (!buffer) return false; return buffer->isDetached(); } private: void* viewDataEither_() const { // Note, do not check whether shared or not // Keep synced with js::GetArrayLengthAndData in jsfriendapi.h! return static_cast(getPrivate(DATA_SLOT)); } public: static void trace(JSTracer* trc, JSObject* obj); static void finalize(FreeOp* fop, JSObject* obj); static void objectMoved(JSObject* obj, const JSObject* old); static size_t objectMovedDuringMinorGC(JSTracer* trc, JSObject* obj, const JSObject* old, gc::AllocKind allocKind); /* Initialization bits */ template static bool GetterImpl(JSContext* cx, const CallArgs& args) { MOZ_ASSERT(is(args.thisv())); args.rval().set(ValueGetter(&args.thisv().toObject().as())); return true; } // ValueGetter is a function that takes an unwrapped typed array object and // returns a Value. Given such a function, Getter<> is a native that // retrieves a given Value, probably from a slot on the object. template static bool Getter(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod>(cx, args); } static const JSFunctionSpec protoFunctions[]; static const JSPropertySpec protoAccessors[]; static const JSFunctionSpec staticFunctions[]; static const JSPropertySpec staticProperties[]; /* Accessors and functions */ static bool is(HandleValue v); static bool set(JSContext* cx, unsigned argc, Value* vp); }; MOZ_MUST_USE bool TypedArray_bufferGetter(JSContext* cx, unsigned argc, Value* vp); extern TypedArrayObject* TypedArrayCreateWithTemplate(JSContext* cx, HandleObject templateObj, int32_t len); inline bool IsTypedArrayClass(const Class* clasp) { return &TypedArrayObject::classes[0] <= clasp && clasp < &TypedArrayObject::classes[Scalar::MaxTypedArrayViewType]; } bool IsTypedArrayConstructor(HandleValue v, uint32_t type); inline Scalar::Type TypedArrayObject::type() const { MOZ_ASSERT(IsTypedArrayClass(getClass())); return static_cast(getClass() - &classes[0]); } inline size_t TypedArrayObject::bytesPerElement() const { return Scalar::byteSize(type()); } // Return value is whether the string is some integer. If the string is an // integer which is not representable as a uint64_t, the return value is true // and the resulting index is UINT64_MAX. template bool StringIsTypedArrayIndex(const CharT* s, size_t length, uint64_t* indexp); inline bool IsTypedArrayIndex(jsid id, uint64_t* indexp) { if (JSID_IS_INT(id)) { int32_t i = JSID_TO_INT(id); MOZ_ASSERT(i >= 0); *indexp = (double)i; return true; } if (MOZ_UNLIKELY(!JSID_IS_STRING(id))) return false; JS::AutoCheckCannotGC nogc; JSAtom* atom = JSID_TO_ATOM(id); size_t length = atom->length(); if (atom->hasLatin1Chars()) { const Latin1Char* s = atom->latin1Chars(nogc); if (!JS7_ISDEC(*s) && *s != '-') return false; return StringIsTypedArrayIndex(s, length, indexp); } const char16_t* s = atom->twoByteChars(nogc); if (!JS7_ISDEC(*s) && *s != '-') return false; return StringIsTypedArrayIndex(s, length, indexp); } /* * Implements [[DefineOwnProperty]] for TypedArrays when the property * key is a TypedArray index. */ bool DefineTypedArrayElement(JSContext* cx, HandleObject arr, uint64_t index, Handle desc, ObjectOpResult& result); static inline unsigned TypedArrayShift(Scalar::Type viewType) { switch (viewType) { case Scalar::Int8: case Scalar::Uint8: case Scalar::Uint8Clamped: return 0; case Scalar::Int16: case Scalar::Uint16: return 1; case Scalar::Int32: case Scalar::Uint32: case Scalar::Float32: return 2; case Scalar::Int64: case Scalar::Float64: return 3; case Scalar::Float32x4: case Scalar::Int8x16: case Scalar::Int16x8: case Scalar::Int32x4: return 4; default:; } MOZ_CRASH("Unexpected array type"); } static inline unsigned TypedArrayElemSize(Scalar::Type viewType) { return 1u << TypedArrayShift(viewType); } // Assign // // target[targetOffset] = unsafeSrcCrossCompartment[0] // ... // target[targetOffset + unsafeSrcCrossCompartment.length - 1] = // unsafeSrcCrossCompartment[unsafeSrcCrossCompartment.length - 1] // // where the source element range doesn't overlap the target element range in // memory. extern void SetDisjointTypedElements(TypedArrayObject* target, uint32_t targetOffset, TypedArrayObject* unsafeSrcCrossCompartment); extern JSObject* InitDataViewClass(JSContext* cx, HandleObject obj); class DataViewObject : public NativeObject { private: static const Class protoClass; static bool is(HandleValue v) { return v.isObject() && v.toObject().hasClass(&class_); } template static uint8_t* getDataPointer(JSContext* cx, Handle obj, double offset); template static bool getterImpl(JSContext* cx, const CallArgs& args); template static bool getter(JSContext* cx, unsigned argc, Value* vp); template static bool defineGetter(JSContext* cx, PropertyName* name, HandleNativeObject proto); static bool getAndCheckConstructorArgs(JSContext* cx, JSObject* bufobj, const CallArgs& args, uint32_t *byteOffset, uint32_t* byteLength); static bool constructSameCompartment(JSContext* cx, HandleObject bufobj, const CallArgs& args); static bool constructWrapped(JSContext* cx, HandleObject bufobj, const CallArgs& args); friend bool ArrayBufferObject::createDataViewForThisImpl(JSContext* cx, const CallArgs& args); static DataViewObject* create(JSContext* cx, uint32_t byteOffset, uint32_t byteLength, Handle arrayBuffer, JSObject* proto); public: static const Class class_; static Value byteOffsetValue(DataViewObject* view) { Value v = view->getFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT); MOZ_ASSERT(v.toInt32() >= 0); return v; } static Value byteLengthValue(DataViewObject* view) { Value v = view->getFixedSlot(TypedArrayObject::LENGTH_SLOT); MOZ_ASSERT(v.toInt32() >= 0); return v; } static Value bufferValue(DataViewObject* view) { return view->getFixedSlot(TypedArrayObject::BUFFER_SLOT); } uint32_t byteOffset() const { return byteOffsetValue(const_cast(this)).toInt32(); } uint32_t byteLength() const { return byteLengthValue(const_cast(this)).toInt32(); } ArrayBufferObject& arrayBuffer() const { return bufferValue(const_cast(this)).toObject().as(); } void* dataPointer() const { return getPrivate(); } static bool class_constructor(JSContext* cx, unsigned argc, Value* vp); static bool getInt8Impl(JSContext* cx, const CallArgs& args); static bool fun_getInt8(JSContext* cx, unsigned argc, Value* vp); static bool getUint8Impl(JSContext* cx, const CallArgs& args); static bool fun_getUint8(JSContext* cx, unsigned argc, Value* vp); static bool getInt16Impl(JSContext* cx, const CallArgs& args); static bool fun_getInt16(JSContext* cx, unsigned argc, Value* vp); static bool getUint16Impl(JSContext* cx, const CallArgs& args); static bool fun_getUint16(JSContext* cx, unsigned argc, Value* vp); static bool getInt32Impl(JSContext* cx, const CallArgs& args); static bool fun_getInt32(JSContext* cx, unsigned argc, Value* vp); static bool getUint32Impl(JSContext* cx, const CallArgs& args); static bool fun_getUint32(JSContext* cx, unsigned argc, Value* vp); static bool getFloat32Impl(JSContext* cx, const CallArgs& args); static bool fun_getFloat32(JSContext* cx, unsigned argc, Value* vp); static bool getFloat64Impl(JSContext* cx, const CallArgs& args); static bool fun_getFloat64(JSContext* cx, unsigned argc, Value* vp); static bool setInt8Impl(JSContext* cx, const CallArgs& args); static bool fun_setInt8(JSContext* cx, unsigned argc, Value* vp); static bool setUint8Impl(JSContext* cx, const CallArgs& args); static bool fun_setUint8(JSContext* cx, unsigned argc, Value* vp); static bool setInt16Impl(JSContext* cx, const CallArgs& args); static bool fun_setInt16(JSContext* cx, unsigned argc, Value* vp); static bool setUint16Impl(JSContext* cx, const CallArgs& args); static bool fun_setUint16(JSContext* cx, unsigned argc, Value* vp); static bool setInt32Impl(JSContext* cx, const CallArgs& args); static bool fun_setInt32(JSContext* cx, unsigned argc, Value* vp); static bool setUint32Impl(JSContext* cx, const CallArgs& args); static bool fun_setUint32(JSContext* cx, unsigned argc, Value* vp); static bool setFloat32Impl(JSContext* cx, const CallArgs& args); static bool fun_setFloat32(JSContext* cx, unsigned argc, Value* vp); static bool setFloat64Impl(JSContext* cx, const CallArgs& args); static bool fun_setFloat64(JSContext* cx, unsigned argc, Value* vp); static bool initClass(JSContext* cx); static void notifyBufferDetached(JSObject* view); template static bool read(JSContext* cx, Handle obj, const CallArgs& args, NativeType* val, const char* method); template static bool write(JSContext* cx, Handle obj, const CallArgs& args, const char* method); void notifyBufferDetached(void* newData); private: static const JSFunctionSpec jsfuncs[]; }; static inline int32_t ClampIntForUint8Array(int32_t x) { if (x < 0) return 0; if (x > 255) return 255; return x; } static inline bool IsAnyArrayBuffer(HandleObject obj) { return IsArrayBuffer(obj) || IsSharedArrayBuffer(obj); } static inline bool IsAnyArrayBuffer(JSObject* obj) { return IsArrayBuffer(obj) || IsSharedArrayBuffer(obj); } static inline bool IsAnyArrayBuffer(HandleValue v) { return v.isObject() && IsAnyArrayBuffer(&v.toObject()); } } // namespace js template <> inline bool JSObject::is() const { return js::IsTypedArrayClass(getClass()); } #endif /* vm_TypedArrayObject_h */