/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * 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/Maybe.h" #include "mozilla/TextUtils.h" #include "gc/AllocKind.h" #include "gc/MaybeRooted.h" #include "js/Class.h" #include "js/experimental/TypedData.h" // js::detail::TypedArrayLengthSlot #include "js/ScalarType.h" // js::Scalar::Type #include "vm/ArrayBufferObject.h" #include "vm/ArrayBufferViewObject.h" #include "vm/JSObject.h" #include "vm/SharedArrayObject.h" namespace js { enum class ArraySortResult : uint32_t; namespace jit { class TrampolineNativeFrameLayout; } /* * 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 ArrayBufferViewObject { public: static_assert(js::detail::TypedArrayLengthSlot == LENGTH_SLOT, "bad inlined constant in TypedData.h"); static_assert(js::detail::TypedArrayDataSlot == DATA_SLOT, "bad inlined constant in TypedData.h"); static bool sameBuffer(const TypedArrayObject* a, const TypedArrayObject* b) { // Inline buffers. if (!a->hasBuffer() || !b->hasBuffer()) { return a == b; } // Shared buffers. if (a->isSharedMemory() && b->isSharedMemory()) { return a->bufferShared()->globalID() == b->bufferShared()->globalID(); } return a->bufferEither() == b->bufferEither(); } static const JSClass anyClasses[3][Scalar::MaxTypedArrayViewType]; static constexpr const JSClass ( &fixedLengthClasses)[Scalar::MaxTypedArrayViewType] = TypedArrayObject::anyClasses[0]; static constexpr const JSClass ( &immutableClasses)[Scalar::MaxTypedArrayViewType] = TypedArrayObject::anyClasses[1]; static constexpr const JSClass ( &resizableClasses)[Scalar::MaxTypedArrayViewType] = TypedArrayObject::anyClasses[2]; static const JSClass protoClasses[Scalar::MaxTypedArrayViewType]; static const JSClass sharedTypedArrayPrototypeClass; static const JSClass* protoClassForType(Scalar::Type type) { MOZ_ASSERT(type < Scalar::MaxTypedArrayViewType); return &protoClasses[type]; } inline Scalar::Type type() const; inline size_t bytesPerElement() const; static bool ensureHasBuffer(JSContext* cx, Handle typedArray); public: /** * Return the current length, or |Nothing| if the TypedArray is detached or * out-of-bounds. */ mozilla::Maybe length() const { return ArrayBufferViewObject::length(); } /** * Return the current byteLength, or |Nothing| if the TypedArray is detached * or out-of-bounds. */ mozilla::Maybe byteLength() const { return length().map( [this](size_t value) { return value * bytesPerElement(); }); } // Self-hosted TypedArraySubarray function needs to read [[ByteOffset]], even // when it's currently out-of-bounds. size_t byteOffsetMaybeOutOfBounds() const { // dataPointerOffset() returns the [[ByteOffset]] spec value, except when // the buffer is detached. (bug 1840991) return ArrayBufferViewObject::dataPointerOffset(); } template bool getElement(JSContext* cx, size_t index, typename MaybeRooted::MutableHandleType val); bool getElementPure(size_t index, Value* vp); /* * Copy |length| elements from this typed array to vp. vp must point to rooted * memory. |length| must not exceed the typed array's current length. */ static bool getElements(JSContext* cx, Handle tarray, size_t length, Value* vp); static bool GetTemplateObjectForLength(JSContext* cx, Scalar::Type type, int32_t length, MutableHandle res); static TypedArrayObject* GetTemplateObjectForBuffer( JSContext* cx, Scalar::Type type, Handle buffer); static TypedArrayObject* GetTemplateObjectForBufferView( JSContext* cx, Handle bufferView); static TypedArrayObject* GetTemplateObjectForArrayLike( JSContext* cx, Scalar::Type type, Handle arrayLike); // Maximum allowed byte length for any typed array. static constexpr size_t ByteLengthLimit = ArrayBufferObject::ByteLengthLimit; /* Accessors and functions */ static bool sort(JSContext* cx, unsigned argc, Value* vp); bool convertValue(JSContext* cx, HandleValue v, MutableHandleValue result) const; /* Initialization bits */ static const JSFunctionSpec protoFunctions[]; static const JSPropertySpec protoAccessors[]; static const JSFunctionSpec staticFunctions[]; static const JSPropertySpec staticProperties[]; }; class FixedLengthTypedArrayObject : public TypedArrayObject { public: static constexpr size_t FIXED_DATA_START = RESERVED_SLOTS; // For typed arrays which can store their data inline, the array buffer // object is created lazily. static constexpr uint32_t INLINE_BUFFER_LIMIT = (NativeObject::MAX_FIXED_SLOTS - FIXED_DATA_START) * sizeof(Value); inline gc::AllocKind allocKindForTenure() const; static inline gc::AllocKind AllocKindForLazyBuffer(size_t nbytes); size_t byteOffset() const { return ArrayBufferViewObject::byteOffsetSlotValue(); } size_t byteLength() const { return length() * bytesPerElement(); } size_t length() const { return ArrayBufferViewObject::lengthSlotValue(); } bool hasInlineElements() const; void setInlineElements(); uint8_t* elementsRaw() const { return maybePtrFromReservedSlot(DATA_SLOT); } uint8_t* elements() const { assertZeroLengthArrayData(); return elementsRaw(); } bool hasMallocedElements(JSContext* cx) const; #ifdef DEBUG void assertZeroLengthArrayData() const; #else void assertZeroLengthArrayData() const {}; #endif static void finalize(JS::GCContext* gcx, JSObject* obj); static size_t objectMoved(JSObject* obj, JSObject* old); }; class ResizableTypedArrayObject : public TypedArrayObject { public: static const uint8_t RESERVED_SLOTS = RESIZABLE_RESERVED_SLOTS; }; class ImmutableTypedArrayObject : public TypedArrayObject {}; extern TypedArrayObject* NewTypedArrayWithTemplateAndLength( JSContext* cx, HandleObject templateObj, int32_t len); extern TypedArrayObject* NewTypedArrayWithTemplateAndArray( JSContext* cx, HandleObject templateObj, HandleObject array); extern TypedArrayObject* NewTypedArrayWithTemplateAndBuffer( JSContext* cx, HandleObject templateObj, HandleObject arrayBuffer, HandleValue byteOffset, HandleValue length); extern TypedArrayObject* NewUint8ArrayWithLength( JSContext* cx, int32_t len, gc::Heap heap = gc::Heap::Default); inline bool IsFixedLengthTypedArrayClass(const JSClass* clasp) { return std::begin(TypedArrayObject::fixedLengthClasses) <= clasp && clasp < std::end(TypedArrayObject::fixedLengthClasses); } inline bool IsResizableTypedArrayClass(const JSClass* clasp) { return std::begin(TypedArrayObject::resizableClasses) <= clasp && clasp < std::end(TypedArrayObject::resizableClasses); } inline bool IsImmutableTypedArrayClass(const JSClass* clasp) { return std::begin(TypedArrayObject::immutableClasses) <= clasp && clasp < std::end(TypedArrayObject::immutableClasses); } inline bool IsTypedArrayClass(const JSClass* clasp) { MOZ_ASSERT(std::end(TypedArrayObject::fixedLengthClasses) == std::begin(TypedArrayObject::immutableClasses) && std::end(TypedArrayObject::immutableClasses) == std::begin(TypedArrayObject::resizableClasses), "TypedArray classes are in contiguous memory"); return std::begin(TypedArrayObject::fixedLengthClasses) <= clasp && clasp < std::end(TypedArrayObject::resizableClasses); } inline Scalar::Type GetTypedArrayClassType(const JSClass* clasp) { MOZ_ASSERT(IsTypedArrayClass(clasp)); if (clasp < std::end(TypedArrayObject::fixedLengthClasses)) { return static_cast(clasp - &TypedArrayObject::fixedLengthClasses[0]); } if (clasp < std::end(TypedArrayObject::immutableClasses)) { return static_cast(clasp - &TypedArrayObject::immutableClasses[0]); } return static_cast(clasp - &TypedArrayObject::resizableClasses[0]); } bool IsTypedArrayConstructor(const JSObject* obj); bool IsTypedArrayConstructor(HandleValue v, Scalar::Type type); JSNative TypedArrayConstructorNative(Scalar::Type type); Scalar::Type TypedArrayConstructorType(const JSFunction* fun); // In WebIDL terminology, a BufferSource is either an ArrayBuffer or a typed // array view. In either case, extract the dataPointer/byteLength. // // If [AllowShared] is true, then the buffer may be backed by a shared array // buffer. // If [AllowResizable] is true, then the buffer may be backed by a resizable // or growable array buffer. bool IsBufferSource(JSContext* cx, JSObject* object, bool allowShared, bool allowResizable, SharedMem* dataPointer, size_t* byteLength); inline Scalar::Type TypedArrayObject::type() const { return GetTypedArrayClassType(getClass()); } inline size_t TypedArrayObject::bytesPerElement() const { return Scalar::byteSize(type()); } // ES2020 draft rev a5375bdad264c8aa264d9c44f57408087761069e // 7.1.16 CanonicalNumericIndexString // // Checks whether or not the string is a canonical numeric index string. If the // string is a canonical numeric index which is not representable as a uint64_t, // the returned index is UINT64_MAX. template mozilla::Maybe StringToTypedArrayIndex(mozilla::Range s); // A string |s| is a TypedArray index (or: canonical numeric index string) iff // |s| is "-0" or |SameValue(ToString(ToNumber(s)), s)| is true. So check for // any characters which can start the string representation of a number, // including "NaN" and "Infinity". template inline bool CanStartTypedArrayIndex(CharT ch) { return mozilla::IsAsciiDigit(ch) || ch == '-' || ch == 'N' || ch == 'I'; } [[nodiscard]] inline mozilla::Maybe ToTypedArrayIndex(jsid id) { if (id.isInt()) { int32_t i = id.toInt(); MOZ_ASSERT(i >= 0); return mozilla::Some(i); } if (MOZ_UNLIKELY(!id.isString())) { return mozilla::Nothing(); } JS::AutoCheckCannotGC nogc; JSAtom* atom = id.toAtom(); if (atom->empty() || !CanStartTypedArrayIndex(atom->latin1OrTwoByteChar(0))) { return mozilla::Nothing(); } if (atom->hasLatin1Chars()) { mozilla::Range chars = atom->latin1Range(nogc); return StringToTypedArrayIndex(chars); } mozilla::Range chars = atom->twoByteRange(nogc); return StringToTypedArrayIndex(chars); } bool SetTypedArrayElement(JSContext* cx, Handle obj, uint64_t index, HandleValue v, ObjectOpResult& result); /* * Implements [[DefineOwnProperty]] for TypedArrays when the property * key is a TypedArray index. */ bool DefineTypedArrayElement(JSContext* cx, Handle obj, uint64_t index, Handle desc, ObjectOpResult& result); void TypedArrayFillInt32(TypedArrayObject* obj, int32_t fillValue, intptr_t start, intptr_t end); void TypedArrayFillInt64(TypedArrayObject* obj, int64_t fillValue, intptr_t start, intptr_t end); void TypedArrayFillDouble(TypedArrayObject* obj, double fillValue, intptr_t start, intptr_t end); void TypedArrayFillFloat32(TypedArrayObject* obj, float fillValue, intptr_t start, intptr_t end); void TypedArrayFillBigInt(TypedArrayObject* obj, BigInt* fillValue, intptr_t start, intptr_t end); bool TypedArraySet(JSContext* cx, TypedArrayObject* target, TypedArrayObject* source, intptr_t offset); void TypedArraySetInfallible(TypedArrayObject* target, TypedArrayObject* source, intptr_t offset); bool TypedArraySetFromSubarray(JSContext* cx, TypedArrayObject* target, TypedArrayObject* source, intptr_t offset, intptr_t sourceOffset, intptr_t sourceLength); void TypedArraySetFromSubarrayInfallible(TypedArrayObject* target, TypedArrayObject* source, intptr_t offset, intptr_t sourceOffset, intptr_t sourceLength); TypedArrayObject* TypedArraySubarray(JSContext* cx, Handle obj, intptr_t start, intptr_t end); TypedArrayObject* TypedArraySubarrayWithLength(JSContext* cx, Handle obj, intptr_t start, intptr_t length); TypedArrayObject* TypedArraySubarrayRecover(JSContext* cx, Handle obj, intptr_t start, intptr_t length); static inline constexpr unsigned TypedArrayShift(Scalar::Type viewType) { switch (viewType) { case Scalar::Int8: case Scalar::Uint8: case Scalar::Uint8Clamped: return 0; case Scalar::Int16: case Scalar::Uint16: case Scalar::Float16: return 1; case Scalar::Int32: case Scalar::Uint32: case Scalar::Float32: return 2; case Scalar::BigInt64: case Scalar::BigUint64: case Scalar::Int64: case Scalar::Float64: return 3; default: MOZ_CRASH("Unexpected array type"); } } static inline constexpr unsigned TypedArrayElemSize(Scalar::Type viewType) { return 1u << TypedArrayShift(viewType); } /** * Check if |targetType| and |sourceType| have compatible bit-level * representations to allow bitwise copying. */ constexpr bool CanUseBitwiseCopy(Scalar::Type targetType, Scalar::Type sourceType) { switch (targetType) { case Scalar::Int8: case Scalar::Uint8: return sourceType == Scalar::Int8 || sourceType == Scalar::Uint8 || sourceType == Scalar::Uint8Clamped; case Scalar::Uint8Clamped: return sourceType == Scalar::Uint8 || sourceType == Scalar::Uint8Clamped; case Scalar::Int16: case Scalar::Uint16: return sourceType == Scalar::Int16 || sourceType == Scalar::Uint16; case Scalar::Int32: case Scalar::Uint32: return sourceType == Scalar::Int32 || sourceType == Scalar::Uint32; case Scalar::Float16: return sourceType == Scalar::Float16; case Scalar::Float32: return sourceType == Scalar::Float32; case Scalar::Float64: return sourceType == Scalar::Float64; case Scalar::BigInt64: case Scalar::BigUint64: return sourceType == Scalar::BigInt64 || sourceType == Scalar::BigUint64; case Scalar::MaxTypedArrayViewType: case Scalar::Int64: case Scalar::Simd128: // GCC8 doesn't like MOZ_CRASH in constexpr functions, so we can't use it // here to catch invalid typed array types. break; } return false; } extern ArraySortResult TypedArraySortFromJit( JSContext* cx, jit::TrampolineNativeFrameLayout* frame); } // namespace js template <> inline bool JSObject::is() const { return js::IsTypedArrayClass(getClass()); } template <> inline bool JSObject::is() const { return js::IsFixedLengthTypedArrayClass(getClass()); } template <> inline bool JSObject::is() const { return js::IsResizableTypedArrayClass(getClass()); } template <> inline bool JSObject::is() const { return js::IsImmutableTypedArrayClass(getClass()); } #endif /* vm_TypedArrayObject_h */