diff options
Diffstat (limited to 'js/src/vm/TypedArrayObject.h')
-rw-r--r-- | js/src/vm/TypedArrayObject.h | 607 |
1 files changed, 607 insertions, 0 deletions
diff --git a/js/src/vm/TypedArrayObject.h b/js/src/vm/TypedArrayObject.h new file mode 100644 index 000000000..775a9a0af --- /dev/null +++ b/js/src/vm/TypedArrayObject.h @@ -0,0 +1,607 @@ +/* -*- 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<typename T> struct OfType; + + static bool sameBuffer(Handle<TypedArrayObject*> a, Handle<TypedArrayObject*> b) { + // Inline buffers. + if (!a->hasBuffer() || !b->hasBuffer()) + return a.get() == b.get(); + + // Shared buffers. + if (a->isSharedMemory() && b->isSharedMemory()) { + return (a->bufferObject()->as<SharedArrayBufferObject>().globalID() == + b->bufferObject()->as<SharedArrayBufferObject>().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<TypedArrayObject*> tarray); + + bool hasBuffer() const { + return bufferValue(const_cast<TypedArrayObject*>(this)).isObject(); + } + JSObject* bufferObject() const { + return bufferValue(const_cast<TypedArrayObject*>(this)).toObjectOrNull(); + } + uint32_t byteOffset() const { + return byteOffsetValue(const_cast<TypedArrayObject*>(this)).toInt32(); + } + uint32_t byteLength() const { + return byteLengthValue(const_cast<TypedArrayObject*>(this)).toInt32(); + } + uint32_t length() const { + return lengthValue(const_cast<TypedArrayObject*>(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<TypedArrayObject*>(this)).toObjectOrNull(); + if (!obj) + return nullptr; + return &obj->as<ArrayBufferObject>(); + } + SharedArrayBufferObject* bufferShared() const { + MOZ_ASSERT(isSharedMemory()); + JSObject* obj = bufferValue(const_cast<TypedArrayObject*>(this)).toObjectOrNull(); + if (!obj) + return nullptr; + return &obj->as<SharedArrayBufferObject>(); + } + ArrayBufferObjectMaybeShared* bufferEither() const { + JSObject* obj = bufferValue(const_cast<TypedArrayObject*>(this)).toObjectOrNull(); + if (!obj) + return nullptr; + if (isSharedMemory()) + return &obj->as<SharedArrayBufferObject>(); + return &obj->as<ArrayBufferObject>(); + } + + SharedMem<void*> viewDataShared() const { + return SharedMem<void*>::shared(viewDataEither_()); + } + SharedMem<void*> viewDataEither() const { + if (isSharedMemory()) + return SharedMem<void*>::shared(viewDataEither_()); + return SharedMem<void*>::unshared(viewDataEither_()); + } + void initViewData(SharedMem<uint8_t*> 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::Get<Type>ArrayLengthAndData in jsfriendapi.h! + return static_cast<void*>(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<Value ValueGetter(TypedArrayObject* tarr)> + static bool + GetterImpl(JSContext* cx, const CallArgs& args) + { + MOZ_ASSERT(is(args.thisv())); + args.rval().set(ValueGetter(&args.thisv().toObject().as<TypedArrayObject>())); + 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<Value ValueGetter(TypedArrayObject* tarr)> + static bool + Getter(JSContext* cx, unsigned argc, Value* vp) + { + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<is, GetterImpl<ValueGetter>>(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<Scalar::Type>(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 <typename CharT> +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<PropertyDescriptor> 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 <typename NativeType> + static uint8_t* + getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, double offset); + + template<Value ValueGetter(DataViewObject* view)> + static bool + getterImpl(JSContext* cx, const CallArgs& args); + + template<Value ValueGetter(DataViewObject* view)> + static bool + getter(JSContext* cx, unsigned argc, Value* vp); + + template<Value ValueGetter(DataViewObject* view)> + 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<ArrayBufferObject*> 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<DataViewObject*>(this)).toInt32(); + } + + uint32_t byteLength() const { + return byteLengthValue(const_cast<DataViewObject*>(this)).toInt32(); + } + + ArrayBufferObject& arrayBuffer() const { + return bufferValue(const_cast<DataViewObject*>(this)).toObject().as<ArrayBufferObject>(); + } + + 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<typename NativeType> + static bool read(JSContext* cx, Handle<DataViewObject*> obj, + const CallArgs& args, NativeType* val, const char* method); + template<typename NativeType> + static bool write(JSContext* cx, Handle<DataViewObject*> 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<js::TypedArrayObject>() const +{ + return js::IsTypedArrayClass(getClass()); +} + +#endif /* vm_TypedArrayObject_h */ |