TypedArrayObject.h (16697B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef vm_TypedArrayObject_h 8 #define vm_TypedArrayObject_h 9 10 #include "mozilla/Maybe.h" 11 #include "mozilla/TextUtils.h" 12 13 #include "gc/AllocKind.h" 14 #include "gc/MaybeRooted.h" 15 #include "js/Class.h" 16 #include "js/experimental/TypedData.h" // js::detail::TypedArrayLengthSlot 17 #include "js/ScalarType.h" // js::Scalar::Type 18 #include "vm/ArrayBufferObject.h" 19 #include "vm/ArrayBufferViewObject.h" 20 #include "vm/JSObject.h" 21 #include "vm/SharedArrayObject.h" 22 23 namespace js { 24 25 enum class ArraySortResult : uint32_t; 26 27 namespace jit { 28 class TrampolineNativeFrameLayout; 29 } 30 31 /* 32 * TypedArrayObject 33 * 34 * The non-templated base class for the specific typed implementations. 35 * This class holds all the member variables that are used by 36 * the subclasses. 37 */ 38 39 class TypedArrayObject : public ArrayBufferViewObject { 40 public: 41 static_assert(js::detail::TypedArrayLengthSlot == LENGTH_SLOT, 42 "bad inlined constant in TypedData.h"); 43 static_assert(js::detail::TypedArrayDataSlot == DATA_SLOT, 44 "bad inlined constant in TypedData.h"); 45 46 static bool sameBuffer(const TypedArrayObject* a, const TypedArrayObject* b) { 47 // Inline buffers. 48 if (!a->hasBuffer() || !b->hasBuffer()) { 49 return a == b; 50 } 51 52 // Shared buffers. 53 if (a->isSharedMemory() && b->isSharedMemory()) { 54 return a->bufferShared()->globalID() == b->bufferShared()->globalID(); 55 } 56 57 return a->bufferEither() == b->bufferEither(); 58 } 59 60 static const JSClass anyClasses[3][Scalar::MaxTypedArrayViewType]; 61 static constexpr const JSClass ( 62 &fixedLengthClasses)[Scalar::MaxTypedArrayViewType] = 63 TypedArrayObject::anyClasses[0]; 64 static constexpr const JSClass ( 65 &immutableClasses)[Scalar::MaxTypedArrayViewType] = 66 TypedArrayObject::anyClasses[1]; 67 static constexpr const JSClass ( 68 &resizableClasses)[Scalar::MaxTypedArrayViewType] = 69 TypedArrayObject::anyClasses[2]; 70 static const JSClass protoClasses[Scalar::MaxTypedArrayViewType]; 71 static const JSClass sharedTypedArrayPrototypeClass; 72 73 static const JSClass* protoClassForType(Scalar::Type type) { 74 MOZ_ASSERT(type < Scalar::MaxTypedArrayViewType); 75 return &protoClasses[type]; 76 } 77 78 inline Scalar::Type type() const; 79 inline size_t bytesPerElement() const; 80 81 static bool ensureHasBuffer(JSContext* cx, 82 Handle<TypedArrayObject*> typedArray); 83 84 public: 85 /** 86 * Return the current length, or |Nothing| if the TypedArray is detached or 87 * out-of-bounds. 88 */ 89 mozilla::Maybe<size_t> length() const { 90 return ArrayBufferViewObject::length(); 91 } 92 93 /** 94 * Return the current byteLength, or |Nothing| if the TypedArray is detached 95 * or out-of-bounds. 96 */ 97 mozilla::Maybe<size_t> byteLength() const { 98 return length().map( 99 [this](size_t value) { return value * bytesPerElement(); }); 100 } 101 102 // Self-hosted TypedArraySubarray function needs to read [[ByteOffset]], even 103 // when it's currently out-of-bounds. 104 size_t byteOffsetMaybeOutOfBounds() const { 105 // dataPointerOffset() returns the [[ByteOffset]] spec value, except when 106 // the buffer is detached. (bug 1840991) 107 return ArrayBufferViewObject::dataPointerOffset(); 108 } 109 110 template <AllowGC allowGC> 111 bool getElement(JSContext* cx, size_t index, 112 typename MaybeRooted<Value, allowGC>::MutableHandleType val); 113 bool getElementPure(size_t index, Value* vp); 114 115 /* 116 * Copy |length| elements from this typed array to vp. vp must point to rooted 117 * memory. |length| must not exceed the typed array's current length. 118 */ 119 static bool getElements(JSContext* cx, Handle<TypedArrayObject*> tarray, 120 size_t length, Value* vp); 121 122 static bool GetTemplateObjectForLength(JSContext* cx, Scalar::Type type, 123 int32_t length, 124 MutableHandle<TypedArrayObject*> res); 125 126 static TypedArrayObject* GetTemplateObjectForBuffer( 127 JSContext* cx, Scalar::Type type, 128 Handle<ArrayBufferObjectMaybeShared*> buffer); 129 130 static TypedArrayObject* GetTemplateObjectForBufferView( 131 JSContext* cx, Handle<TypedArrayObject*> bufferView); 132 133 static TypedArrayObject* GetTemplateObjectForArrayLike( 134 JSContext* cx, Scalar::Type type, Handle<JSObject*> arrayLike); 135 136 // Maximum allowed byte length for any typed array. 137 static constexpr size_t ByteLengthLimit = ArrayBufferObject::ByteLengthLimit; 138 139 /* Accessors and functions */ 140 141 static bool sort(JSContext* cx, unsigned argc, Value* vp); 142 143 bool convertValue(JSContext* cx, HandleValue v, 144 MutableHandleValue result) const; 145 146 /* Initialization bits */ 147 148 static const JSFunctionSpec protoFunctions[]; 149 static const JSPropertySpec protoAccessors[]; 150 static const JSFunctionSpec staticFunctions[]; 151 static const JSPropertySpec staticProperties[]; 152 }; 153 154 class FixedLengthTypedArrayObject : public TypedArrayObject { 155 public: 156 static constexpr size_t FIXED_DATA_START = RESERVED_SLOTS; 157 158 // For typed arrays which can store their data inline, the array buffer 159 // object is created lazily. 160 static constexpr uint32_t INLINE_BUFFER_LIMIT = 161 (NativeObject::MAX_FIXED_SLOTS - FIXED_DATA_START) * sizeof(Value); 162 163 inline gc::AllocKind allocKindForTenure() const; 164 static inline gc::AllocKind AllocKindForLazyBuffer(size_t nbytes); 165 166 size_t byteOffset() const { 167 return ArrayBufferViewObject::byteOffsetSlotValue(); 168 } 169 170 size_t byteLength() const { return length() * bytesPerElement(); } 171 172 size_t length() const { return ArrayBufferViewObject::lengthSlotValue(); } 173 174 bool hasInlineElements() const; 175 void setInlineElements(); 176 uint8_t* elementsRaw() const { 177 return maybePtrFromReservedSlot<uint8_t>(DATA_SLOT); 178 } 179 uint8_t* elements() const { 180 assertZeroLengthArrayData(); 181 return elementsRaw(); 182 } 183 184 bool hasMallocedElements(JSContext* cx) const; 185 186 #ifdef DEBUG 187 void assertZeroLengthArrayData() const; 188 #else 189 void assertZeroLengthArrayData() const {}; 190 #endif 191 192 static void finalize(JS::GCContext* gcx, JSObject* obj); 193 static size_t objectMoved(JSObject* obj, JSObject* old); 194 }; 195 196 class ResizableTypedArrayObject : public TypedArrayObject { 197 public: 198 static const uint8_t RESERVED_SLOTS = RESIZABLE_RESERVED_SLOTS; 199 }; 200 201 class ImmutableTypedArrayObject : public TypedArrayObject {}; 202 203 extern TypedArrayObject* NewTypedArrayWithTemplateAndLength( 204 JSContext* cx, HandleObject templateObj, int32_t len); 205 206 extern TypedArrayObject* NewTypedArrayWithTemplateAndArray( 207 JSContext* cx, HandleObject templateObj, HandleObject array); 208 209 extern TypedArrayObject* NewTypedArrayWithTemplateAndBuffer( 210 JSContext* cx, HandleObject templateObj, HandleObject arrayBuffer, 211 HandleValue byteOffset, HandleValue length); 212 213 extern TypedArrayObject* NewUint8ArrayWithLength( 214 JSContext* cx, int32_t len, gc::Heap heap = gc::Heap::Default); 215 216 inline bool IsFixedLengthTypedArrayClass(const JSClass* clasp) { 217 return std::begin(TypedArrayObject::fixedLengthClasses) <= clasp && 218 clasp < std::end(TypedArrayObject::fixedLengthClasses); 219 } 220 221 inline bool IsResizableTypedArrayClass(const JSClass* clasp) { 222 return std::begin(TypedArrayObject::resizableClasses) <= clasp && 223 clasp < std::end(TypedArrayObject::resizableClasses); 224 } 225 226 inline bool IsImmutableTypedArrayClass(const JSClass* clasp) { 227 return std::begin(TypedArrayObject::immutableClasses) <= clasp && 228 clasp < std::end(TypedArrayObject::immutableClasses); 229 } 230 231 inline bool IsTypedArrayClass(const JSClass* clasp) { 232 MOZ_ASSERT(std::end(TypedArrayObject::fixedLengthClasses) == 233 std::begin(TypedArrayObject::immutableClasses) && 234 std::end(TypedArrayObject::immutableClasses) == 235 std::begin(TypedArrayObject::resizableClasses), 236 "TypedArray classes are in contiguous memory"); 237 return std::begin(TypedArrayObject::fixedLengthClasses) <= clasp && 238 clasp < std::end(TypedArrayObject::resizableClasses); 239 } 240 241 inline Scalar::Type GetTypedArrayClassType(const JSClass* clasp) { 242 MOZ_ASSERT(IsTypedArrayClass(clasp)); 243 if (clasp < std::end(TypedArrayObject::fixedLengthClasses)) { 244 return static_cast<Scalar::Type>(clasp - 245 &TypedArrayObject::fixedLengthClasses[0]); 246 } 247 if (clasp < std::end(TypedArrayObject::immutableClasses)) { 248 return static_cast<Scalar::Type>(clasp - 249 &TypedArrayObject::immutableClasses[0]); 250 } 251 return static_cast<Scalar::Type>(clasp - 252 &TypedArrayObject::resizableClasses[0]); 253 } 254 255 bool IsTypedArrayConstructor(const JSObject* obj); 256 257 bool IsTypedArrayConstructor(HandleValue v, Scalar::Type type); 258 259 JSNative TypedArrayConstructorNative(Scalar::Type type); 260 261 Scalar::Type TypedArrayConstructorType(const JSFunction* fun); 262 263 // In WebIDL terminology, a BufferSource is either an ArrayBuffer or a typed 264 // array view. In either case, extract the dataPointer/byteLength. 265 // 266 // If [AllowShared] is true, then the buffer may be backed by a shared array 267 // buffer. 268 // If [AllowResizable] is true, then the buffer may be backed by a resizable 269 // or growable array buffer. 270 bool IsBufferSource(JSContext* cx, JSObject* object, bool allowShared, 271 bool allowResizable, SharedMem<uint8_t*>* dataPointer, 272 size_t* byteLength); 273 274 inline Scalar::Type TypedArrayObject::type() const { 275 return GetTypedArrayClassType(getClass()); 276 } 277 278 inline size_t TypedArrayObject::bytesPerElement() const { 279 return Scalar::byteSize(type()); 280 } 281 282 // ES2020 draft rev a5375bdad264c8aa264d9c44f57408087761069e 283 // 7.1.16 CanonicalNumericIndexString 284 // 285 // Checks whether or not the string is a canonical numeric index string. If the 286 // string is a canonical numeric index which is not representable as a uint64_t, 287 // the returned index is UINT64_MAX. 288 template <typename CharT> 289 mozilla::Maybe<uint64_t> StringToTypedArrayIndex(mozilla::Range<const CharT> s); 290 291 // A string |s| is a TypedArray index (or: canonical numeric index string) iff 292 // |s| is "-0" or |SameValue(ToString(ToNumber(s)), s)| is true. So check for 293 // any characters which can start the string representation of a number, 294 // including "NaN" and "Infinity". 295 template <typename CharT> 296 inline bool CanStartTypedArrayIndex(CharT ch) { 297 return mozilla::IsAsciiDigit(ch) || ch == '-' || ch == 'N' || ch == 'I'; 298 } 299 300 [[nodiscard]] inline mozilla::Maybe<uint64_t> ToTypedArrayIndex(jsid id) { 301 if (id.isInt()) { 302 int32_t i = id.toInt(); 303 MOZ_ASSERT(i >= 0); 304 return mozilla::Some(i); 305 } 306 307 if (MOZ_UNLIKELY(!id.isString())) { 308 return mozilla::Nothing(); 309 } 310 311 JS::AutoCheckCannotGC nogc; 312 JSAtom* atom = id.toAtom(); 313 314 if (atom->empty() || !CanStartTypedArrayIndex(atom->latin1OrTwoByteChar(0))) { 315 return mozilla::Nothing(); 316 } 317 318 if (atom->hasLatin1Chars()) { 319 mozilla::Range<const Latin1Char> chars = atom->latin1Range(nogc); 320 return StringToTypedArrayIndex(chars); 321 } 322 323 mozilla::Range<const char16_t> chars = atom->twoByteRange(nogc); 324 return StringToTypedArrayIndex(chars); 325 } 326 327 bool SetTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj, 328 uint64_t index, HandleValue v, 329 ObjectOpResult& result); 330 331 /* 332 * Implements [[DefineOwnProperty]] for TypedArrays when the property 333 * key is a TypedArray index. 334 */ 335 bool DefineTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj, 336 uint64_t index, Handle<PropertyDescriptor> desc, 337 ObjectOpResult& result); 338 339 void TypedArrayFillInt32(TypedArrayObject* obj, int32_t fillValue, 340 intptr_t start, intptr_t end); 341 342 void TypedArrayFillInt64(TypedArrayObject* obj, int64_t fillValue, 343 intptr_t start, intptr_t end); 344 345 void TypedArrayFillDouble(TypedArrayObject* obj, double fillValue, 346 intptr_t start, intptr_t end); 347 348 void TypedArrayFillFloat32(TypedArrayObject* obj, float fillValue, 349 intptr_t start, intptr_t end); 350 351 void TypedArrayFillBigInt(TypedArrayObject* obj, BigInt* fillValue, 352 intptr_t start, intptr_t end); 353 354 bool TypedArraySet(JSContext* cx, TypedArrayObject* target, 355 TypedArrayObject* source, intptr_t offset); 356 357 void TypedArraySetInfallible(TypedArrayObject* target, TypedArrayObject* source, 358 intptr_t offset); 359 360 bool TypedArraySetFromSubarray(JSContext* cx, TypedArrayObject* target, 361 TypedArrayObject* source, intptr_t offset, 362 intptr_t sourceOffset, intptr_t sourceLength); 363 364 void TypedArraySetFromSubarrayInfallible(TypedArrayObject* target, 365 TypedArrayObject* source, 366 intptr_t offset, intptr_t sourceOffset, 367 intptr_t sourceLength); 368 369 TypedArrayObject* TypedArraySubarray(JSContext* cx, 370 Handle<TypedArrayObject*> obj, 371 intptr_t start, intptr_t end); 372 373 TypedArrayObject* TypedArraySubarrayWithLength(JSContext* cx, 374 Handle<TypedArrayObject*> obj, 375 intptr_t start, intptr_t length); 376 377 TypedArrayObject* TypedArraySubarrayRecover(JSContext* cx, 378 Handle<TypedArrayObject*> obj, 379 intptr_t start, intptr_t length); 380 381 static inline constexpr unsigned TypedArrayShift(Scalar::Type viewType) { 382 switch (viewType) { 383 case Scalar::Int8: 384 case Scalar::Uint8: 385 case Scalar::Uint8Clamped: 386 return 0; 387 case Scalar::Int16: 388 case Scalar::Uint16: 389 case Scalar::Float16: 390 return 1; 391 case Scalar::Int32: 392 case Scalar::Uint32: 393 case Scalar::Float32: 394 return 2; 395 case Scalar::BigInt64: 396 case Scalar::BigUint64: 397 case Scalar::Int64: 398 case Scalar::Float64: 399 return 3; 400 default: 401 MOZ_CRASH("Unexpected array type"); 402 } 403 } 404 405 static inline constexpr unsigned TypedArrayElemSize(Scalar::Type viewType) { 406 return 1u << TypedArrayShift(viewType); 407 } 408 409 /** 410 * Check if |targetType| and |sourceType| have compatible bit-level 411 * representations to allow bitwise copying. 412 */ 413 constexpr bool CanUseBitwiseCopy(Scalar::Type targetType, 414 Scalar::Type sourceType) { 415 switch (targetType) { 416 case Scalar::Int8: 417 case Scalar::Uint8: 418 return sourceType == Scalar::Int8 || sourceType == Scalar::Uint8 || 419 sourceType == Scalar::Uint8Clamped; 420 421 case Scalar::Uint8Clamped: 422 return sourceType == Scalar::Uint8 || sourceType == Scalar::Uint8Clamped; 423 424 case Scalar::Int16: 425 case Scalar::Uint16: 426 return sourceType == Scalar::Int16 || sourceType == Scalar::Uint16; 427 428 case Scalar::Int32: 429 case Scalar::Uint32: 430 return sourceType == Scalar::Int32 || sourceType == Scalar::Uint32; 431 432 case Scalar::Float16: 433 return sourceType == Scalar::Float16; 434 435 case Scalar::Float32: 436 return sourceType == Scalar::Float32; 437 438 case Scalar::Float64: 439 return sourceType == Scalar::Float64; 440 441 case Scalar::BigInt64: 442 case Scalar::BigUint64: 443 return sourceType == Scalar::BigInt64 || sourceType == Scalar::BigUint64; 444 445 case Scalar::MaxTypedArrayViewType: 446 case Scalar::Int64: 447 case Scalar::Simd128: 448 // GCC8 doesn't like MOZ_CRASH in constexpr functions, so we can't use it 449 // here to catch invalid typed array types. 450 break; 451 } 452 return false; 453 } 454 455 extern ArraySortResult TypedArraySortFromJit( 456 JSContext* cx, jit::TrampolineNativeFrameLayout* frame); 457 458 } // namespace js 459 460 template <> 461 inline bool JSObject::is<js::TypedArrayObject>() const { 462 return js::IsTypedArrayClass(getClass()); 463 } 464 465 template <> 466 inline bool JSObject::is<js::FixedLengthTypedArrayObject>() const { 467 return js::IsFixedLengthTypedArrayClass(getClass()); 468 } 469 470 template <> 471 inline bool JSObject::is<js::ResizableTypedArrayObject>() const { 472 return js::IsResizableTypedArrayClass(getClass()); 473 } 474 475 template <> 476 inline bool JSObject::is<js::ImmutableTypedArrayObject>() const { 477 return js::IsImmutableTypedArrayClass(getClass()); 478 } 479 480 #endif /* vm_TypedArrayObject_h */