Bug 1885337 - Part 1: Implement to/from hex methods. r=dminor
[gecko.git] / js / src / vm / TypedArrayObject.cpp
blob6d9f14dd2ab0714708f95e6d458558af3cdfd7b5
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/. */
7 #include "vm/TypedArrayObject-inl.h"
8 #include "vm/TypedArrayObject.h"
10 #include "mozilla/FloatingPoint.h"
11 #include "mozilla/IntegerTypeTraits.h"
12 #include "mozilla/Likely.h"
13 #include "mozilla/PodOperations.h"
14 #include "mozilla/TextUtils.h"
16 #include <algorithm>
17 #include <iterator>
18 #include <limits>
19 #include <numeric>
20 #include <string.h>
21 #include <string_view>
22 #if !defined(XP_WIN) && !defined(__wasi__)
23 # include <sys/mman.h>
24 #endif
25 #include <type_traits>
27 #include "jsnum.h"
28 #include "jstypes.h"
30 #include "builtin/Array.h"
31 #include "builtin/DataViewObject.h"
32 #include "gc/Barrier.h"
33 #include "gc/MaybeRooted.h"
34 #include "jit/InlinableNatives.h"
35 #include "js/Conversions.h"
36 #include "js/experimental/TypedData.h" // JS_GetArrayBufferViewType, JS_GetTypedArray{Length,ByteOffset,ByteLength}, JS_IsTypedArrayObject
37 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
38 #include "js/PropertySpec.h"
39 #include "js/ScalarType.h" // JS::Scalar::Type
40 #include "js/UniquePtr.h"
41 #include "js/Wrapper.h"
42 #include "util/DifferentialTesting.h"
43 #include "util/StringBuffer.h"
44 #include "util/Text.h"
45 #include "util/WindowsWrapper.h"
46 #include "vm/ArrayBufferObject.h"
47 #include "vm/FunctionFlags.h" // js::FunctionFlags
48 #include "vm/GlobalObject.h"
49 #include "vm/JSContext.h"
50 #include "vm/JSObject.h"
51 #include "vm/PIC.h"
52 #include "vm/SelfHosting.h"
53 #include "vm/SharedMem.h"
54 #include "vm/Uint8Clamped.h"
55 #include "vm/WrapperObject.h"
57 #include "gc/Nursery-inl.h"
58 #include "vm/ArrayBufferObject-inl.h"
59 #include "vm/Compartment-inl.h"
60 #include "vm/GeckoProfiler-inl.h"
61 #include "vm/NativeObject-inl.h"
63 using namespace js;
65 using JS::CanonicalizeNaN;
66 using JS::ToInt32;
67 using JS::ToUint32;
68 using mozilla::IsAsciiDigit;
71 * TypedArrayObject
73 * The non-templated base class for the specific typed implementations.
74 * This class holds all the member variables that are used by
75 * the subclasses.
78 bool TypedArrayObject::convertValue(JSContext* cx, HandleValue v,
79 MutableHandleValue result) const {
80 switch (type()) {
81 case Scalar::BigInt64:
82 case Scalar::BigUint64: {
83 BigInt* bi = ToBigInt(cx, v);
84 if (!bi) {
85 return false;
87 result.setBigInt(bi);
88 return true;
90 case Scalar::Int8:
91 case Scalar::Uint8:
92 case Scalar::Int16:
93 case Scalar::Uint16:
94 case Scalar::Int32:
95 case Scalar::Uint32:
96 case Scalar::Float32:
97 case Scalar::Float64:
98 case Scalar::Uint8Clamped: {
99 double num;
100 if (!ToNumber(cx, v, &num)) {
101 return false;
103 result.setNumber(num);
104 return true;
106 case Scalar::MaxTypedArrayViewType:
107 case Scalar::Int64:
108 case Scalar::Simd128:
109 MOZ_CRASH("Unsupported TypedArray type");
111 MOZ_ASSERT_UNREACHABLE("Invalid scalar type");
112 return false;
115 static bool IsTypedArrayObject(HandleValue v) {
116 return v.isObject() && v.toObject().is<TypedArrayObject>();
119 static bool IsUint8ArrayObject(HandleValue v) {
120 return IsTypedArrayObject(v) &&
121 v.toObject().as<TypedArrayObject>().type() == Scalar::Uint8;
124 /* static */
125 bool TypedArrayObject::ensureHasBuffer(JSContext* cx,
126 Handle<TypedArrayObject*> typedArray) {
127 if (typedArray->hasBuffer()) {
128 return true;
131 MOZ_ASSERT(typedArray->is<FixedLengthTypedArrayObject>(),
132 "Resizable TypedArrays always use an ArrayBuffer");
134 Rooted<FixedLengthTypedArrayObject*> tarray(
135 cx, &typedArray->as<FixedLengthTypedArrayObject>());
137 size_t byteLength = tarray->byteLength();
139 AutoRealm ar(cx, tarray);
140 Rooted<ArrayBufferObject*> buffer(
141 cx, ArrayBufferObject::createZeroed(cx, tarray->byteLength()));
142 if (!buffer) {
143 return false;
146 buffer->pinLength(tarray->isLengthPinned());
148 // Attaching the first view to an array buffer is infallible.
149 MOZ_ALWAYS_TRUE(buffer->addView(cx, tarray));
151 // tarray is not shared, because if it were it would have a buffer.
152 memcpy(buffer->dataPointer(), tarray->dataPointerUnshared(), byteLength);
154 // If the object is in the nursery, the buffer will be freed by the next
155 // nursery GC. Free the data slot pointer if the object has no inline data.
156 size_t nbytes = RoundUp(byteLength, sizeof(Value));
157 Nursery& nursery = cx->nursery();
158 if (tarray->isTenured() && !tarray->hasInlineElements() &&
159 !nursery.isInside(tarray->elements())) {
160 js_free(tarray->elements());
161 RemoveCellMemory(tarray, nbytes, MemoryUse::TypedArrayElements);
164 tarray->setFixedSlot(TypedArrayObject::DATA_SLOT,
165 PrivateValue(buffer->dataPointer()));
166 tarray->setFixedSlot(TypedArrayObject::BUFFER_SLOT, ObjectValue(*buffer));
168 return true;
171 #ifdef DEBUG
172 void FixedLengthTypedArrayObject::assertZeroLengthArrayData() const {
173 if (length() == 0 && !hasBuffer()) {
174 uint8_t* end = fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START);
175 MOZ_ASSERT(end[0] == ZeroLengthArrayData);
178 #endif
180 void FixedLengthTypedArrayObject::finalize(JS::GCContext* gcx, JSObject* obj) {
181 MOZ_ASSERT(!IsInsideNursery(obj));
182 auto* curObj = &obj->as<FixedLengthTypedArrayObject>();
184 // Template objects or discarded objects (which didn't have enough room
185 // for inner elements) don't have anything to free.
186 if (!curObj->elementsRaw()) {
187 return;
190 curObj->assertZeroLengthArrayData();
192 // Typed arrays with a buffer object do not need to be free'd
193 if (curObj->hasBuffer()) {
194 return;
197 // Free the data slot pointer if it does not point into the old JSObject.
198 if (!curObj->hasInlineElements()) {
199 size_t nbytes = RoundUp(curObj->byteLength(), sizeof(Value));
200 gcx->free_(obj, curObj->elements(), nbytes, MemoryUse::TypedArrayElements);
204 /* static */
205 size_t FixedLengthTypedArrayObject::objectMoved(JSObject* obj, JSObject* old) {
206 auto* newObj = &obj->as<FixedLengthTypedArrayObject>();
207 const auto* oldObj = &old->as<FixedLengthTypedArrayObject>();
208 MOZ_ASSERT(newObj->elementsRaw() == oldObj->elementsRaw());
210 // Typed arrays with a buffer object do not need an update.
211 if (oldObj->hasBuffer()) {
212 return 0;
215 if (!IsInsideNursery(old)) {
216 // Update the data slot pointer if it points to the old JSObject.
217 if (oldObj->hasInlineElements()) {
218 newObj->setInlineElements();
221 return 0;
224 void* buf = oldObj->elements();
226 // Discarded objects (which didn't have enough room for inner elements) don't
227 // have any data to move.
228 if (!buf) {
229 return 0;
232 Nursery& nursery = obj->runtimeFromMainThread()->gc.nursery();
234 // Determine if we can use inline data for the target array. If this is
235 // possible, the nursery will have picked an allocation size that is large
236 // enough.
237 size_t nbytes = oldObj->byteLength();
238 bool canUseDirectForward = nbytes >= sizeof(uintptr_t);
240 constexpr size_t headerSize = dataOffset() + sizeof(HeapSlot);
242 gc::AllocKind allocKind = oldObj->allocKindForTenure();
243 MOZ_ASSERT_IF(obj->isTenured(), obj->asTenured().getAllocKind() == allocKind);
244 MOZ_ASSERT_IF(nbytes == 0,
245 headerSize + sizeof(uint8_t) <= GetGCKindBytes(allocKind));
247 if (nursery.isInside(buf) &&
248 headerSize + nbytes <= GetGCKindBytes(allocKind)) {
249 MOZ_ASSERT(oldObj->hasInlineElements());
250 #ifdef DEBUG
251 if (nbytes == 0) {
252 uint8_t* output =
253 newObj->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START);
254 output[0] = ZeroLengthArrayData;
256 #endif
257 newObj->setInlineElements();
258 mozilla::PodCopy(newObj->elements(), oldObj->elements(), nbytes);
260 // Set a forwarding pointer for the element buffers in case they were
261 // preserved on the stack by Ion.
262 nursery.setForwardingPointerWhileTenuring(
263 oldObj->elements(), newObj->elements(), canUseDirectForward);
265 return 0;
268 // Non-inline allocations are rounded up.
269 nbytes = RoundUp(nbytes, sizeof(Value));
271 Nursery::WasBufferMoved result = nursery.maybeMoveBufferOnPromotion(
272 &buf, newObj, nbytes, MemoryUse::TypedArrayElements,
273 ArrayBufferContentsArena);
274 if (result == Nursery::BufferMoved) {
275 newObj->setReservedSlot(DATA_SLOT, PrivateValue(buf));
277 // Set a forwarding pointer for the element buffers in case they were
278 // preserved on the stack by Ion.
279 nursery.setForwardingPointerWhileTenuring(
280 oldObj->elements(), newObj->elements(), canUseDirectForward);
282 return nbytes;
285 return 0;
288 bool FixedLengthTypedArrayObject::hasInlineElements() const {
289 return elements() ==
290 this->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START) &&
291 byteLength() <= FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT;
294 void FixedLengthTypedArrayObject::setInlineElements() {
295 char* dataSlot = reinterpret_cast<char*>(this) + dataOffset();
296 *reinterpret_cast<void**>(dataSlot) =
297 this->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START);
300 /* Helper clamped uint8_t type */
302 uint32_t js::ClampDoubleToUint8(const double x) {
303 // Not < so that NaN coerces to 0
304 if (!(x >= 0)) {
305 return 0;
308 if (x > 255) {
309 return 255;
312 double toTruncate = x + 0.5;
313 uint8_t y = uint8_t(toTruncate);
316 * now val is rounded to nearest, ties rounded up. We want
317 * rounded to nearest ties to even, so check whether we had a
318 * tie.
320 if (y == toTruncate) {
322 * It was a tie (since adding 0.5 gave us the exact integer
323 * we want). Since we rounded up, we either already have an
324 * even number or we have an odd number but the number we
325 * want is one less. So just unconditionally masking out the
326 * ones bit should do the trick to get us the value we
327 * want.
329 return y & ~1;
332 return y;
335 static void ReportOutOfBounds(JSContext* cx, TypedArrayObject* typedArray) {
336 if (typedArray->hasDetachedBuffer()) {
337 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
338 JSMSG_TYPED_ARRAY_DETACHED);
339 } else {
340 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
341 JSMSG_TYPED_ARRAY_RESIZED_BOUNDS);
345 namespace {
347 template <class TypedArrayType>
348 static TypedArrayType* NewTypedArrayObject(JSContext* cx, const JSClass* clasp,
349 HandleObject proto,
350 gc::AllocKind allocKind,
351 gc::Heap heap) {
352 MOZ_ASSERT(proto);
354 MOZ_ASSERT(CanChangeToBackgroundAllocKind(allocKind, clasp));
355 allocKind = ForegroundToBackgroundAllocKind(allocKind);
357 static_assert(std::is_same_v<TypedArrayType, FixedLengthTypedArrayObject> ||
358 std::is_same_v<TypedArrayType, ResizableTypedArrayObject>);
360 // Fixed length typed arrays can store data inline so we only use fixed slots
361 // to cover the reserved slots, ignoring the AllocKind.
362 MOZ_ASSERT(ClassCanHaveFixedData(clasp));
363 constexpr size_t nfixed = TypedArrayType::RESERVED_SLOTS;
364 static_assert(nfixed <= NativeObject::MAX_FIXED_SLOTS);
365 static_assert(!std::is_same_v<TypedArrayType, FixedLengthTypedArrayObject> ||
366 nfixed == FixedLengthTypedArrayObject::FIXED_DATA_START);
368 Rooted<SharedShape*> shape(
370 SharedShape::getInitialShape(cx, clasp, cx->realm(), AsTaggedProto(proto),
371 nfixed, ObjectFlags()));
372 if (!shape) {
373 return nullptr;
376 return NativeObject::create<TypedArrayType>(cx, allocKind, heap, shape);
379 template <typename NativeType>
380 class FixedLengthTypedArrayObjectTemplate;
382 template <typename NativeType>
383 class ResizableTypedArrayObjectTemplate;
385 template <typename NativeType>
386 class TypedArrayObjectTemplate {
387 friend class js::TypedArrayObject;
389 using FixedLengthTypedArray = FixedLengthTypedArrayObjectTemplate<NativeType>;
390 using ResizableTypedArray = ResizableTypedArrayObjectTemplate<NativeType>;
391 using AutoLength = ArrayBufferViewObject::AutoLength;
393 static constexpr auto ByteLengthLimit = TypedArrayObject::ByteLengthLimit;
394 static constexpr auto INLINE_BUFFER_LIMIT =
395 FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT;
397 public:
398 static constexpr Scalar::Type ArrayTypeID() {
399 return TypeIDOfType<NativeType>::id;
401 static constexpr JSProtoKey protoKey() {
402 return TypeIDOfType<NativeType>::protoKey;
405 static constexpr bool ArrayTypeIsUnsigned() {
406 return TypeIsUnsigned<NativeType>();
408 static constexpr bool ArrayTypeIsFloatingPoint() {
409 return TypeIsFloatingPoint<NativeType>();
412 static constexpr size_t BYTES_PER_ELEMENT = sizeof(NativeType);
414 static JSObject* createPrototype(JSContext* cx, JSProtoKey key) {
415 Handle<GlobalObject*> global = cx->global();
416 RootedObject typedArrayProto(
417 cx, GlobalObject::getOrCreateTypedArrayPrototype(cx, global));
418 if (!typedArrayProto) {
419 return nullptr;
422 const JSClass* clasp = TypedArrayObject::protoClassForType(ArrayTypeID());
423 return GlobalObject::createBlankPrototypeInheriting(cx, clasp,
424 typedArrayProto);
427 static JSObject* createConstructor(JSContext* cx, JSProtoKey key) {
428 Handle<GlobalObject*> global = cx->global();
429 RootedFunction ctorProto(
430 cx, GlobalObject::getOrCreateTypedArrayConstructor(cx, global));
431 if (!ctorProto) {
432 return nullptr;
435 JSFunction* fun = NewFunctionWithProto(
436 cx, class_constructor, 3, FunctionFlags::NATIVE_CTOR, nullptr,
437 ClassName(key, cx), ctorProto, gc::AllocKind::FUNCTION, TenuredObject);
439 if (fun) {
440 fun->setJitInfo(&jit::JitInfo_TypedArrayConstructor);
443 return fun;
446 static bool convertValue(JSContext* cx, HandleValue v, NativeType* result);
448 static TypedArrayObject* makeTypedArrayWithTemplate(
449 JSContext* cx, TypedArrayObject* templateObj, HandleObject array) {
450 MOZ_ASSERT(!IsWrapper(array));
451 MOZ_ASSERT(!array->is<ArrayBufferObjectMaybeShared>());
453 return fromArray(cx, array);
456 static TypedArrayObject* makeTypedArrayWithTemplate(
457 JSContext* cx, TypedArrayObject* templateObj, HandleObject arrayBuffer,
458 HandleValue byteOffsetValue, HandleValue lengthValue) {
459 MOZ_ASSERT(!IsWrapper(arrayBuffer));
460 MOZ_ASSERT(arrayBuffer->is<ArrayBufferObjectMaybeShared>());
462 uint64_t byteOffset, length;
463 if (!byteOffsetAndLength(cx, byteOffsetValue, lengthValue, &byteOffset,
464 &length)) {
465 return nullptr;
468 return fromBufferSameCompartment(
469 cx, arrayBuffer.as<ArrayBufferObjectMaybeShared>(), byteOffset, length,
470 nullptr);
473 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
474 // 23.2.5.1 TypedArray ( ...args )
475 static bool class_constructor(JSContext* cx, unsigned argc, Value* vp) {
476 AutoJSConstructorProfilerEntry pseudoFrame(cx, "[TypedArray]");
477 CallArgs args = CallArgsFromVp(argc, vp);
479 // Step 1.
480 if (!ThrowIfNotConstructing(cx, args, "typed array")) {
481 return false;
484 // Steps 2-6.
485 JSObject* obj = create(cx, args);
486 if (!obj) {
487 return false;
489 args.rval().setObject(*obj);
490 return true;
493 private:
494 static JSObject* create(JSContext* cx, const CallArgs& args) {
495 MOZ_ASSERT(args.isConstructing());
497 // Steps 5 and 6.c.
498 if (args.length() == 0 || !args[0].isObject()) {
499 // Step 6.c.ii.
500 uint64_t len;
501 if (!ToIndex(cx, args.get(0), JSMSG_BAD_ARRAY_LENGTH, &len)) {
502 return nullptr;
505 // Steps 5.a and 6.c.iii.
506 RootedObject proto(cx);
507 if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey(), &proto)) {
508 return nullptr;
511 return fromLength(cx, len, proto);
514 RootedObject dataObj(cx, &args[0].toObject());
516 // Step 6.b.i.
517 // 23.2.5.1.1 AllocateTypedArray, step 1.
518 RootedObject proto(cx);
519 if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey(), &proto)) {
520 return nullptr;
523 // Steps 6.b.ii and 6.b.iv.
524 if (!UncheckedUnwrap(dataObj)->is<ArrayBufferObjectMaybeShared>()) {
525 return fromArray(cx, dataObj, proto);
528 // Steps 6.b.iii.1-2.
529 // 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer, steps 2 and 4.
530 uint64_t byteOffset, length;
531 if (!byteOffsetAndLength(cx, args.get(1), args.get(2), &byteOffset,
532 &length)) {
533 return nullptr;
536 // Step 6.b.iii.3.
537 if (dataObj->is<ArrayBufferObjectMaybeShared>()) {
538 auto buffer = dataObj.as<ArrayBufferObjectMaybeShared>();
539 return fromBufferSameCompartment(cx, buffer, byteOffset, length, proto);
541 return fromBufferWrapped(cx, dataObj, byteOffset, length, proto);
544 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
545 // 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset,
546 // length ) Steps 2 and 4.
547 static bool byteOffsetAndLength(JSContext* cx, HandleValue byteOffsetValue,
548 HandleValue lengthValue, uint64_t* byteOffset,
549 uint64_t* length) {
550 // Step 2.
551 *byteOffset = 0;
552 if (!byteOffsetValue.isUndefined()) {
553 if (!ToIndex(cx, byteOffsetValue, byteOffset)) {
554 return false;
557 // Step 7.
558 if (*byteOffset % BYTES_PER_ELEMENT != 0) {
559 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
560 JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_BOUNDS,
561 Scalar::name(ArrayTypeID()),
562 Scalar::byteSizeString(ArrayTypeID()));
563 return false;
567 // Step 4.
568 *length = UINT64_MAX;
569 if (!lengthValue.isUndefined()) {
570 if (!ToIndex(cx, lengthValue, length)) {
571 return false;
575 return true;
578 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
579 // 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset,
580 // length ) Steps 5-8.
581 static bool computeAndCheckLength(
582 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> bufferMaybeUnwrapped,
583 uint64_t byteOffset, uint64_t lengthIndex, size_t* length,
584 AutoLength* autoLength) {
585 MOZ_ASSERT(byteOffset % BYTES_PER_ELEMENT == 0);
586 MOZ_ASSERT(byteOffset < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT));
587 MOZ_ASSERT_IF(lengthIndex != UINT64_MAX,
588 lengthIndex < uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT));
590 // Step 5.
591 if (bufferMaybeUnwrapped->isDetached()) {
592 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
593 JSMSG_TYPED_ARRAY_DETACHED);
594 return false;
597 // Step 6.
598 size_t bufferByteLength = bufferMaybeUnwrapped->byteLength();
599 MOZ_ASSERT(bufferByteLength <= ByteLengthLimit);
601 size_t len;
602 if (lengthIndex == UINT64_MAX) {
603 // Check if |byteOffset| valid.
604 if (byteOffset > bufferByteLength) {
605 JS_ReportErrorNumberASCII(
606 cx, GetErrorMessage, nullptr,
607 JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_LENGTH_BOUNDS,
608 Scalar::name(ArrayTypeID()));
609 return false;
612 // Resizable buffers without an explicit length are auto-length.
613 if (bufferMaybeUnwrapped->isResizable()) {
614 *length = 0;
615 *autoLength = AutoLength::Yes;
616 return true;
619 // Steps 7.a and 7.c.
620 if (bufferByteLength % BYTES_PER_ELEMENT != 0) {
621 // The given byte array doesn't map exactly to
622 // |BYTES_PER_ELEMENT * N|
623 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
624 JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_MISALIGNED,
625 Scalar::name(ArrayTypeID()),
626 Scalar::byteSizeString(ArrayTypeID()));
627 return false;
630 // Step 7.b.
631 size_t newByteLength = bufferByteLength - size_t(byteOffset);
632 len = newByteLength / BYTES_PER_ELEMENT;
633 } else {
634 // Step 8.a.
635 uint64_t newByteLength = lengthIndex * BYTES_PER_ELEMENT;
637 // Step 8.b.
638 if (byteOffset + newByteLength > bufferByteLength) {
639 // |byteOffset + newByteLength| is too big for the arraybuffer
640 JS_ReportErrorNumberASCII(
641 cx, GetErrorMessage, nullptr,
642 JSMSG_TYPED_ARRAY_CONSTRUCT_ARRAY_LENGTH_BOUNDS,
643 Scalar::name(ArrayTypeID()));
644 return false;
647 len = size_t(lengthIndex);
650 MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT);
651 *length = len;
652 *autoLength = AutoLength::No;
653 return true;
656 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
657 // 23.2.5.1.3 InitializeTypedArrayFromArrayBuffer ( O, buffer, byteOffset,
658 // length ) Steps 5-13.
659 static TypedArrayObject* fromBufferSameCompartment(
660 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
661 uint64_t byteOffset, uint64_t lengthIndex, HandleObject proto) {
662 // Steps 5-8.
663 size_t length = 0;
664 auto autoLength = AutoLength::No;
665 if (!computeAndCheckLength(cx, buffer, byteOffset, lengthIndex, &length,
666 &autoLength)) {
667 return nullptr;
670 if (!buffer->isResizable()) {
671 // Steps 9-13.
672 return FixedLengthTypedArray::makeInstance(cx, buffer, byteOffset, length,
673 proto);
676 return ResizableTypedArray::makeInstance(cx, buffer, byteOffset, length,
677 autoLength, proto);
680 // Create a TypedArray object in another compartment.
682 // ES6 supports creating a TypedArray in global A (using global A's
683 // TypedArray constructor) backed by an ArrayBuffer created in global B.
685 // Our TypedArrayObject implementation doesn't support a TypedArray in
686 // compartment A backed by an ArrayBuffer in compartment B. So in this
687 // case, we create the TypedArray in B (!) and return a cross-compartment
688 // wrapper.
690 // Extra twist: the spec says the new TypedArray's [[Prototype]] must be
691 // A's TypedArray.prototype. So even though we're creating the TypedArray
692 // in B, its [[Prototype]] must be (a cross-compartment wrapper for) the
693 // TypedArray.prototype in A.
694 static JSObject* fromBufferWrapped(JSContext* cx, HandleObject bufobj,
695 uint64_t byteOffset, uint64_t lengthIndex,
696 HandleObject proto) {
697 JSObject* unwrapped = CheckedUnwrapStatic(bufobj);
698 if (!unwrapped) {
699 ReportAccessDenied(cx);
700 return nullptr;
703 if (!unwrapped->is<ArrayBufferObjectMaybeShared>()) {
704 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
705 JSMSG_TYPED_ARRAY_BAD_ARGS);
706 return nullptr;
709 Rooted<ArrayBufferObjectMaybeShared*> unwrappedBuffer(cx);
710 unwrappedBuffer = &unwrapped->as<ArrayBufferObjectMaybeShared>();
712 size_t length = 0;
713 auto autoLength = AutoLength::No;
714 if (!computeAndCheckLength(cx, unwrappedBuffer, byteOffset, lengthIndex,
715 &length, &autoLength)) {
716 return nullptr;
719 // Make sure to get the [[Prototype]] for the created typed array from
720 // this compartment.
721 RootedObject protoRoot(cx, proto);
722 if (!protoRoot) {
723 protoRoot = GlobalObject::getOrCreatePrototype(cx, protoKey());
724 if (!protoRoot) {
725 return nullptr;
729 RootedObject typedArray(cx);
731 JSAutoRealm ar(cx, unwrappedBuffer);
733 RootedObject wrappedProto(cx, protoRoot);
734 if (!cx->compartment()->wrap(cx, &wrappedProto)) {
735 return nullptr;
738 if (!unwrappedBuffer->isResizable()) {
739 typedArray = FixedLengthTypedArray::makeInstance(
740 cx, unwrappedBuffer, byteOffset, length, wrappedProto);
741 } else {
742 typedArray = ResizableTypedArray::makeInstance(
743 cx, unwrappedBuffer, byteOffset, length, autoLength, wrappedProto);
745 if (!typedArray) {
746 return nullptr;
750 if (!cx->compartment()->wrap(cx, &typedArray)) {
751 return nullptr;
754 return typedArray;
757 public:
758 static JSObject* fromBuffer(JSContext* cx, HandleObject bufobj,
759 size_t byteOffset, int64_t lengthInt) {
760 if (byteOffset % BYTES_PER_ELEMENT != 0) {
761 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
762 JSMSG_TYPED_ARRAY_CONSTRUCT_OFFSET_BOUNDS,
763 Scalar::name(ArrayTypeID()),
764 Scalar::byteSizeString(ArrayTypeID()));
765 return nullptr; // invalid byteOffset
768 uint64_t lengthIndex = lengthInt >= 0 ? uint64_t(lengthInt) : UINT64_MAX;
769 if (bufobj->is<ArrayBufferObjectMaybeShared>()) {
770 auto buffer = bufobj.as<ArrayBufferObjectMaybeShared>();
771 return fromBufferSameCompartment(cx, buffer, byteOffset, lengthIndex,
772 nullptr);
774 return fromBufferWrapped(cx, bufobj, byteOffset, lengthIndex, nullptr);
777 static bool maybeCreateArrayBuffer(JSContext* cx, uint64_t count,
778 MutableHandle<ArrayBufferObject*> buffer) {
779 if (count > ByteLengthLimit / BYTES_PER_ELEMENT) {
780 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
781 JSMSG_BAD_ARRAY_LENGTH);
782 return false;
784 size_t byteLength = count * BYTES_PER_ELEMENT;
786 MOZ_ASSERT(byteLength <= ByteLengthLimit);
787 static_assert(INLINE_BUFFER_LIMIT % BYTES_PER_ELEMENT == 0,
788 "ArrayBuffer inline storage shouldn't waste any space");
790 if (byteLength <= INLINE_BUFFER_LIMIT) {
791 // The array's data can be inline, and the buffer created lazily.
792 return true;
795 ArrayBufferObject* buf = ArrayBufferObject::createZeroed(cx, byteLength);
796 if (!buf) {
797 return false;
800 buffer.set(buf);
801 return true;
804 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
805 // 23.2.5.1.1 AllocateTypedArray ( constructorName, newTarget, defaultProto [
806 // , length ] )
807 static TypedArrayObject* fromLength(JSContext* cx, uint64_t nelements,
808 HandleObject proto = nullptr,
809 gc::Heap heap = gc::Heap::Default) {
810 Rooted<ArrayBufferObject*> buffer(cx);
811 if (!maybeCreateArrayBuffer(cx, nelements, &buffer)) {
812 return nullptr;
815 return FixedLengthTypedArray::makeInstance(cx, buffer, 0, nelements, proto,
816 heap);
819 static TypedArrayObject* fromArray(JSContext* cx, HandleObject other,
820 HandleObject proto = nullptr);
822 static TypedArrayObject* fromTypedArray(JSContext* cx, HandleObject other,
823 bool isWrapped, HandleObject proto);
825 static TypedArrayObject* fromObject(JSContext* cx, HandleObject other,
826 HandleObject proto);
828 static const NativeType getIndex(TypedArrayObject* tarray, size_t index) {
829 MOZ_ASSERT(index < tarray->length().valueOr(0));
830 return jit::AtomicOperations::loadSafeWhenRacy(
831 tarray->dataPointerEither().cast<NativeType*>() + index);
834 static void setIndex(TypedArrayObject& tarray, size_t index, NativeType val) {
835 MOZ_ASSERT(index < tarray.length().valueOr(0));
836 jit::AtomicOperations::storeSafeWhenRacy(
837 tarray.dataPointerEither().cast<NativeType*>() + index, val);
840 static bool getElement(JSContext* cx, TypedArrayObject* tarray, size_t index,
841 MutableHandleValue val);
842 static bool getElementPure(TypedArrayObject* tarray, size_t index, Value* vp);
844 static bool setElement(JSContext* cx, Handle<TypedArrayObject*> obj,
845 uint64_t index, HandleValue v, ObjectOpResult& result);
848 template <typename NativeType>
849 class FixedLengthTypedArrayObjectTemplate
850 : public FixedLengthTypedArrayObject,
851 public TypedArrayObjectTemplate<NativeType> {
852 friend class js::TypedArrayObject;
854 using TypedArrayTemplate = TypedArrayObjectTemplate<NativeType>;
856 public:
857 using TypedArrayTemplate::ArrayTypeID;
858 using TypedArrayTemplate::BYTES_PER_ELEMENT;
859 using TypedArrayTemplate::protoKey;
861 static inline const JSClass* instanceClass() {
862 static_assert(ArrayTypeID() <
863 std::size(TypedArrayObject::fixedLengthClasses));
864 return &TypedArrayObject::fixedLengthClasses[ArrayTypeID()];
867 static FixedLengthTypedArrayObject* newBuiltinClassInstance(
868 JSContext* cx, gc::AllocKind allocKind, gc::Heap heap) {
869 RootedObject proto(cx, GlobalObject::getOrCreatePrototype(cx, protoKey()));
870 if (!proto) {
871 return nullptr;
873 return NewTypedArrayObject<FixedLengthTypedArrayObject>(
874 cx, instanceClass(), proto, allocKind, heap);
877 static FixedLengthTypedArrayObject* makeProtoInstance(
878 JSContext* cx, HandleObject proto, gc::AllocKind allocKind) {
879 MOZ_ASSERT(proto);
880 return NewTypedArrayObject<FixedLengthTypedArrayObject>(
881 cx, instanceClass(), proto, allocKind, gc::Heap::Default);
884 static FixedLengthTypedArrayObject* makeInstance(
885 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
886 size_t byteOffset, size_t len, HandleObject proto,
887 gc::Heap heap = gc::Heap::Default) {
888 MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT);
890 gc::AllocKind allocKind =
891 buffer ? gc::GetGCObjectKind(instanceClass())
892 : AllocKindForLazyBuffer(len * BYTES_PER_ELEMENT);
894 AutoSetNewObjectMetadata metadata(cx);
895 FixedLengthTypedArrayObject* obj;
896 if (proto) {
897 obj = makeProtoInstance(cx, proto, allocKind);
898 } else {
899 obj = newBuiltinClassInstance(cx, allocKind, heap);
901 if (!obj || !obj->init(cx, buffer, byteOffset, len, BYTES_PER_ELEMENT)) {
902 return nullptr;
905 return obj;
908 static FixedLengthTypedArrayObject* makeTemplateObject(JSContext* cx,
909 int32_t len) {
910 MOZ_ASSERT(len >= 0);
911 size_t nbytes;
912 MOZ_ALWAYS_TRUE(CalculateAllocSize<NativeType>(len, &nbytes));
913 bool fitsInline = nbytes <= INLINE_BUFFER_LIMIT;
914 gc::AllocKind allocKind = !fitsInline ? gc::GetGCObjectKind(instanceClass())
915 : AllocKindForLazyBuffer(nbytes);
916 MOZ_ASSERT(allocKind >= gc::GetGCObjectKind(instanceClass()));
918 AutoSetNewObjectMetadata metadata(cx);
920 auto* tarray = newBuiltinClassInstance(cx, allocKind, gc::Heap::Tenured);
921 if (!tarray) {
922 return nullptr;
925 initTypedArraySlots(tarray, len);
927 // Template objects don't need memory for their elements, since there
928 // won't be any elements to store.
929 MOZ_ASSERT(tarray->getReservedSlot(DATA_SLOT).isUndefined());
931 return tarray;
934 static void initTypedArraySlots(FixedLengthTypedArrayObject* tarray,
935 int32_t len) {
936 MOZ_ASSERT(len >= 0);
937 tarray->initFixedSlot(TypedArrayObject::BUFFER_SLOT, JS::FalseValue());
938 tarray->initFixedSlot(TypedArrayObject::LENGTH_SLOT, PrivateValue(len));
939 tarray->initFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT,
940 PrivateValue(size_t(0)));
942 #ifdef DEBUG
943 if (len == 0) {
944 uint8_t* output =
945 tarray->fixedData(FixedLengthTypedArrayObject::FIXED_DATA_START);
946 output[0] = TypedArrayObject::ZeroLengthArrayData;
948 #endif
951 static void initTypedArrayData(FixedLengthTypedArrayObject* tarray, void* buf,
952 size_t nbytes, gc::AllocKind allocKind) {
953 if (buf) {
954 InitReservedSlot(tarray, TypedArrayObject::DATA_SLOT, buf, nbytes,
955 MemoryUse::TypedArrayElements);
956 } else {
957 #ifdef DEBUG
958 constexpr size_t dataOffset = ArrayBufferViewObject::dataOffset();
959 constexpr size_t offset = dataOffset + sizeof(HeapSlot);
960 MOZ_ASSERT(offset + nbytes <= GetGCKindBytes(allocKind));
961 #endif
963 void* data = tarray->fixedData(FIXED_DATA_START);
964 tarray->initReservedSlot(DATA_SLOT, PrivateValue(data));
965 memset(data, 0, nbytes);
969 static FixedLengthTypedArrayObject* makeTypedArrayWithTemplate(
970 JSContext* cx, TypedArrayObject* templateObj, int32_t len) {
971 if (len < 0 || size_t(len) > ByteLengthLimit / BYTES_PER_ELEMENT) {
972 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
973 JSMSG_BAD_ARRAY_LENGTH);
974 return nullptr;
977 size_t nbytes = size_t(len) * BYTES_PER_ELEMENT;
978 MOZ_ASSERT(nbytes <= ByteLengthLimit);
980 bool fitsInline = nbytes <= INLINE_BUFFER_LIMIT;
982 AutoSetNewObjectMetadata metadata(cx);
984 gc::AllocKind allocKind = !fitsInline ? gc::GetGCObjectKind(instanceClass())
985 : AllocKindForLazyBuffer(nbytes);
986 MOZ_ASSERT(templateObj->getClass() == instanceClass());
988 RootedObject proto(cx, templateObj->staticPrototype());
989 auto* obj = makeProtoInstance(cx, proto, allocKind);
990 if (!obj) {
991 return nullptr;
994 initTypedArraySlots(obj, len);
996 void* buf = nullptr;
997 if (!fitsInline) {
998 MOZ_ASSERT(len > 0);
1000 nbytes = RoundUp(nbytes, sizeof(Value));
1001 buf = cx->nursery().allocateZeroedBuffer(obj, nbytes,
1002 js::ArrayBufferContentsArena);
1003 if (!buf) {
1004 ReportOutOfMemory(cx);
1005 return nullptr;
1009 initTypedArrayData(obj, buf, nbytes, allocKind);
1011 return obj;
1015 template <typename NativeType>
1016 class ResizableTypedArrayObjectTemplate
1017 : public ResizableTypedArrayObject,
1018 public TypedArrayObjectTemplate<NativeType> {
1019 friend class js::TypedArrayObject;
1021 using TypedArrayTemplate = TypedArrayObjectTemplate<NativeType>;
1023 public:
1024 using TypedArrayTemplate::ArrayTypeID;
1025 using TypedArrayTemplate::BYTES_PER_ELEMENT;
1026 using TypedArrayTemplate::protoKey;
1028 static inline const JSClass* instanceClass() {
1029 static_assert(ArrayTypeID() <
1030 std::size(TypedArrayObject::resizableClasses));
1031 return &TypedArrayObject::resizableClasses[ArrayTypeID()];
1034 static ResizableTypedArrayObject* newBuiltinClassInstance(
1035 JSContext* cx, gc::AllocKind allocKind, gc::Heap heap) {
1036 RootedObject proto(cx, GlobalObject::getOrCreatePrototype(cx, protoKey()));
1037 if (!proto) {
1038 return nullptr;
1040 return NewTypedArrayObject<ResizableTypedArrayObject>(
1041 cx, instanceClass(), proto, allocKind, heap);
1044 static ResizableTypedArrayObject* makeProtoInstance(JSContext* cx,
1045 HandleObject proto,
1046 gc::AllocKind allocKind) {
1047 MOZ_ASSERT(proto);
1048 return NewTypedArrayObject<ResizableTypedArrayObject>(
1049 cx, instanceClass(), proto, allocKind, gc::Heap::Default);
1052 static ResizableTypedArrayObject* makeInstance(
1053 JSContext* cx, Handle<ArrayBufferObjectMaybeShared*> buffer,
1054 size_t byteOffset, size_t len, AutoLength autoLength,
1055 HandleObject proto) {
1056 MOZ_ASSERT(buffer);
1057 MOZ_ASSERT(buffer->isResizable());
1058 MOZ_ASSERT(!buffer->isDetached());
1059 MOZ_ASSERT(autoLength == AutoLength::No || len == 0,
1060 "length is zero for 'auto' length views");
1061 MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT);
1063 gc::AllocKind allocKind = gc::GetGCObjectKind(instanceClass());
1065 AutoSetNewObjectMetadata metadata(cx);
1066 ResizableTypedArrayObject* obj;
1067 if (proto) {
1068 obj = makeProtoInstance(cx, proto, allocKind);
1069 } else {
1070 obj = newBuiltinClassInstance(cx, allocKind, gc::Heap::Default);
1072 if (!obj || !obj->initResizable(cx, buffer, byteOffset, len,
1073 BYTES_PER_ELEMENT, autoLength)) {
1074 return nullptr;
1077 return obj;
1080 static ResizableTypedArrayObject* makeTemplateObject(JSContext* cx) {
1081 gc::AllocKind allocKind = gc::GetGCObjectKind(instanceClass());
1083 AutoSetNewObjectMetadata metadata(cx);
1085 auto* tarray = newBuiltinClassInstance(cx, allocKind, gc::Heap::Tenured);
1086 if (!tarray) {
1087 return nullptr;
1090 tarray->initFixedSlot(TypedArrayObject::BUFFER_SLOT, JS::FalseValue());
1091 tarray->initFixedSlot(TypedArrayObject::LENGTH_SLOT,
1092 PrivateValue(size_t(0)));
1093 tarray->initFixedSlot(TypedArrayObject::BYTEOFFSET_SLOT,
1094 PrivateValue(size_t(0)));
1095 tarray->initFixedSlot(AUTO_LENGTH_SLOT, BooleanValue(false));
1096 tarray->initFixedSlot(ResizableTypedArrayObject::INITIAL_LENGTH_SLOT,
1097 PrivateValue(size_t(0)));
1098 tarray->initFixedSlot(ResizableTypedArrayObject::INITIAL_BYTE_OFFSET_SLOT,
1099 PrivateValue(size_t(0)));
1101 // Template objects don't need memory for their elements, since there
1102 // won't be any elements to store.
1103 MOZ_ASSERT(tarray->getReservedSlot(DATA_SLOT).isUndefined());
1105 return tarray;
1109 template <typename NativeType>
1110 bool TypedArrayObjectTemplate<NativeType>::convertValue(JSContext* cx,
1111 HandleValue v,
1112 NativeType* result) {
1113 double d;
1114 if (!ToNumber(cx, v, &d)) {
1115 return false;
1118 if (js::SupportDifferentialTesting()) {
1119 // See the comment in ElementSpecific::doubleToNative.
1120 d = JS::CanonicalizeNaN(d);
1123 // Assign based on characteristics of the destination type
1124 if constexpr (ArrayTypeIsFloatingPoint()) {
1125 *result = NativeType(d);
1126 } else if constexpr (ArrayTypeIsUnsigned()) {
1127 static_assert(sizeof(NativeType) <= 4);
1128 uint32_t n = ToUint32(d);
1129 *result = NativeType(n);
1130 } else if constexpr (ArrayTypeID() == Scalar::Uint8Clamped) {
1131 // The uint8_clamped type has a special rounding converter
1132 // for doubles.
1133 *result = NativeType(d);
1134 } else {
1135 static_assert(sizeof(NativeType) <= 4);
1136 int32_t n = ToInt32(d);
1137 *result = NativeType(n);
1139 return true;
1142 template <>
1143 bool TypedArrayObjectTemplate<int64_t>::convertValue(JSContext* cx,
1144 HandleValue v,
1145 int64_t* result) {
1146 JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigInt64(cx, v));
1147 return true;
1150 template <>
1151 bool TypedArrayObjectTemplate<uint64_t>::convertValue(JSContext* cx,
1152 HandleValue v,
1153 uint64_t* result) {
1154 JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigUint64(cx, v));
1155 return true;
1158 // https://tc39.github.io/proposal-bigint/#sec-integerindexedelementset
1159 // 9.4.5.11 IntegerIndexedElementSet ( O, index, value )
1160 template <typename NativeType>
1161 /* static */ bool TypedArrayObjectTemplate<NativeType>::setElement(
1162 JSContext* cx, Handle<TypedArrayObject*> obj, uint64_t index, HandleValue v,
1163 ObjectOpResult& result) {
1164 MOZ_ASSERT(!obj->hasDetachedBuffer());
1165 MOZ_ASSERT(index < obj->length().valueOr(0));
1167 // Step 1 is enforced by the caller.
1169 // Steps 2-3.
1170 NativeType nativeValue;
1171 if (!convertValue(cx, v, &nativeValue)) {
1172 return false;
1175 // Step 4.
1176 if (index < obj->length().valueOr(0)) {
1177 MOZ_ASSERT(!obj->hasDetachedBuffer(),
1178 "detaching an array buffer sets the length to zero");
1179 TypedArrayObjectTemplate<NativeType>::setIndex(*obj, index, nativeValue);
1182 // Step 5.
1183 return result.succeed();
1186 } /* anonymous namespace */
1188 TypedArrayObject* js::NewTypedArrayWithTemplateAndLength(
1189 JSContext* cx, HandleObject templateObj, int32_t len) {
1190 MOZ_ASSERT(templateObj->is<TypedArrayObject>());
1191 TypedArrayObject* tobj = &templateObj->as<TypedArrayObject>();
1193 switch (tobj->type()) {
1194 #define CREATE_TYPED_ARRAY(_, T, N) \
1195 case Scalar::N: \
1196 return FixedLengthTypedArrayObjectTemplate<T>::makeTypedArrayWithTemplate( \
1197 cx, tobj, len);
1198 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY)
1199 #undef CREATE_TYPED_ARRAY
1200 default:
1201 MOZ_CRASH("Unsupported TypedArray type");
1205 TypedArrayObject* js::NewTypedArrayWithTemplateAndArray(
1206 JSContext* cx, HandleObject templateObj, HandleObject array) {
1207 MOZ_ASSERT(templateObj->is<TypedArrayObject>());
1208 TypedArrayObject* tobj = &templateObj->as<TypedArrayObject>();
1210 switch (tobj->type()) {
1211 #define CREATE_TYPED_ARRAY(_, T, N) \
1212 case Scalar::N: \
1213 return TypedArrayObjectTemplate<T>::makeTypedArrayWithTemplate(cx, tobj, \
1214 array);
1215 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY)
1216 #undef CREATE_TYPED_ARRAY
1217 default:
1218 MOZ_CRASH("Unsupported TypedArray type");
1222 TypedArrayObject* js::NewTypedArrayWithTemplateAndBuffer(
1223 JSContext* cx, HandleObject templateObj, HandleObject arrayBuffer,
1224 HandleValue byteOffset, HandleValue length) {
1225 MOZ_ASSERT(templateObj->is<TypedArrayObject>());
1226 TypedArrayObject* tobj = &templateObj->as<TypedArrayObject>();
1228 switch (tobj->type()) {
1229 #define CREATE_TYPED_ARRAY(_, T, N) \
1230 case Scalar::N: \
1231 return TypedArrayObjectTemplate<T>::makeTypedArrayWithTemplate( \
1232 cx, tobj, arrayBuffer, byteOffset, length);
1233 JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPED_ARRAY)
1234 #undef CREATE_TYPED_ARRAY
1235 default:
1236 MOZ_CRASH("Unsupported TypedArray type");
1240 TypedArrayObject* js::NewUint8ArrayWithLength(JSContext* cx, int32_t len,
1241 gc::Heap heap) {
1242 return TypedArrayObjectTemplate<uint8_t>::fromLength(cx, len, nullptr, heap);
1245 template <typename T>
1246 /* static */ TypedArrayObject* TypedArrayObjectTemplate<T>::fromArray(
1247 JSContext* cx, HandleObject other, HandleObject proto /* = nullptr */) {
1248 // Allow nullptr proto for FriendAPI methods, which don't care about
1249 // subclassing.
1250 if (other->is<TypedArrayObject>()) {
1251 return fromTypedArray(cx, other, /* wrapped= */ false, proto);
1254 if (other->is<WrapperObject>() &&
1255 UncheckedUnwrap(other)->is<TypedArrayObject>()) {
1256 return fromTypedArray(cx, other, /* wrapped= */ true, proto);
1259 return fromObject(cx, other, proto);
1262 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
1263 // 23.2.5.1 TypedArray ( ...args )
1264 // 23.2.5.1.2 InitializeTypedArrayFromTypedArray ( O, srcArray )
1265 template <typename T>
1266 /* static */ TypedArrayObject* TypedArrayObjectTemplate<T>::fromTypedArray(
1267 JSContext* cx, HandleObject other, bool isWrapped, HandleObject proto) {
1268 MOZ_ASSERT_IF(!isWrapped, other->is<TypedArrayObject>());
1269 MOZ_ASSERT_IF(isWrapped, other->is<WrapperObject>() &&
1270 UncheckedUnwrap(other)->is<TypedArrayObject>());
1272 Rooted<TypedArrayObject*> srcArray(cx);
1273 if (!isWrapped) {
1274 srcArray = &other->as<TypedArrayObject>();
1275 } else {
1276 srcArray = other->maybeUnwrapAs<TypedArrayObject>();
1277 if (!srcArray) {
1278 ReportAccessDenied(cx);
1279 return nullptr;
1283 // InitializeTypedArrayFromTypedArray, step 1. (Skipped)
1285 // InitializeTypedArrayFromTypedArray, step 2.
1286 auto srcLength = srcArray->length();
1287 if (!srcLength) {
1288 ReportOutOfBounds(cx, srcArray);
1289 return nullptr;
1292 // InitializeTypedArrayFromTypedArray, steps 3-7. (Skipped)
1294 // InitializeTypedArrayFromTypedArray, step 8.
1295 size_t elementLength = *srcLength;
1297 // InitializeTypedArrayFromTypedArray, step 9. (Skipped)
1299 // InitializeTypedArrayFromTypedArray, step 10.a. (Partial)
1300 // InitializeTypedArrayFromTypedArray, step 11.a.
1301 Rooted<ArrayBufferObject*> buffer(cx);
1302 if (!maybeCreateArrayBuffer(cx, elementLength, &buffer)) {
1303 return nullptr;
1306 // InitializeTypedArrayFromTypedArray, step 11.b.
1307 if (Scalar::isBigIntType(ArrayTypeID()) !=
1308 Scalar::isBigIntType(srcArray->type())) {
1309 JS_ReportErrorNumberASCII(
1310 cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_NOT_COMPATIBLE,
1311 srcArray->getClass()->name,
1312 TypedArrayObject::fixedLengthClasses[ArrayTypeID()].name);
1313 return nullptr;
1316 // Step 6.b.i.
1317 // InitializeTypedArrayFromTypedArray, steps 12-15.
1318 Rooted<TypedArrayObject*> obj(cx, FixedLengthTypedArray::makeInstance(
1319 cx, buffer, 0, elementLength, proto));
1320 if (!obj) {
1321 return nullptr;
1324 MOZ_RELEASE_ASSERT(!srcArray->hasDetachedBuffer());
1326 // InitializeTypedArrayFromTypedArray, steps 10.a. (Remaining parts)
1327 // InitializeTypedArrayFromTypedArray, steps 11.c-f.
1328 MOZ_ASSERT(!obj->isSharedMemory());
1329 if (srcArray->isSharedMemory()) {
1330 if (!ElementSpecific<T, SharedOps>::setFromTypedArray(
1331 obj, elementLength, srcArray, elementLength, 0)) {
1332 MOZ_ASSERT_UNREACHABLE(
1333 "setFromTypedArray can only fail for overlapping buffers");
1334 return nullptr;
1336 } else {
1337 if (!ElementSpecific<T, UnsharedOps>::setFromTypedArray(
1338 obj, elementLength, srcArray, elementLength, 0)) {
1339 MOZ_ASSERT_UNREACHABLE(
1340 "setFromTypedArray can only fail for overlapping buffers");
1341 return nullptr;
1345 // Step 6.b.v.
1346 return obj;
1349 static MOZ_ALWAYS_INLINE bool IsOptimizableInit(JSContext* cx,
1350 HandleObject iterable,
1351 bool* optimized) {
1352 MOZ_ASSERT(!*optimized);
1354 if (!IsPackedArray(iterable)) {
1355 return true;
1358 ForOfPIC::Chain* stubChain = ForOfPIC::getOrCreate(cx);
1359 if (!stubChain) {
1360 return false;
1363 return stubChain->tryOptimizeArray(cx, iterable.as<ArrayObject>(), optimized);
1366 // ES2023 draft rev cf86f1cdc28e809170733d74ea64fd0f3dd79f78
1367 // 23.2.5.1 TypedArray ( ...args )
1368 // 23.2.5.1.4 InitializeTypedArrayFromList ( O, values )
1369 // 23.2.5.1.5 InitializeTypedArrayFromArrayLike ( O, arrayLike )
1370 template <typename T>
1371 /* static */ TypedArrayObject* TypedArrayObjectTemplate<T>::fromObject(
1372 JSContext* cx, HandleObject other, HandleObject proto) {
1373 // Steps 1-4 and 6.a (Already performed in caller).
1375 // Steps 6.b.i (Allocation deferred until later).
1377 // Steps 6.b.ii-iii. (Not applicable)
1379 // Step 6.b.iv.
1381 bool optimized = false;
1382 if (!IsOptimizableInit(cx, other, &optimized)) {
1383 return nullptr;
1386 // Fast path when iterable is a packed array using the default iterator.
1387 if (optimized) {
1388 // Steps 6.b.iv.2-3. (We don't need to call IterableToList for the fast
1389 // path).
1390 Handle<ArrayObject*> array = other.as<ArrayObject>();
1392 // InitializeTypedArrayFromList, step 1.
1393 size_t len = array->getDenseInitializedLength();
1395 // InitializeTypedArrayFromList, step 2.
1396 Rooted<ArrayBufferObject*> buffer(cx);
1397 if (!maybeCreateArrayBuffer(cx, len, &buffer)) {
1398 return nullptr;
1401 // Steps 6.b.i.
1402 Rooted<FixedLengthTypedArrayObject*> obj(
1403 cx, FixedLengthTypedArray::makeInstance(cx, buffer, 0, len, proto));
1404 if (!obj) {
1405 return nullptr;
1408 // InitializeTypedArrayFromList, steps 3-4.
1409 MOZ_ASSERT(!obj->isSharedMemory());
1410 if (!ElementSpecific<T, UnsharedOps>::initFromIterablePackedArray(cx, obj,
1411 array)) {
1412 return nullptr;
1415 // InitializeTypedArrayFromList, step 5. (The assertion isn't applicable for
1416 // the fast path).
1418 // Step 6.b.v.
1419 return obj;
1422 // Step 6.b.iv.1 (Assertion; implicit in our implementation).
1424 // Step 6.b.iv.2.
1425 RootedValue callee(cx);
1426 RootedId iteratorId(cx, PropertyKey::Symbol(cx->wellKnownSymbols().iterator));
1427 if (!GetProperty(cx, other, other, iteratorId, &callee)) {
1428 return nullptr;
1431 // Steps 6.b.iv.3-4.
1432 RootedObject arrayLike(cx);
1433 if (!callee.isNullOrUndefined()) {
1434 // Throw if other[Symbol.iterator] isn't callable.
1435 if (!callee.isObject() || !callee.toObject().isCallable()) {
1436 RootedValue otherVal(cx, ObjectValue(*other));
1437 UniqueChars bytes =
1438 DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, otherVal, nullptr);
1439 if (!bytes) {
1440 return nullptr;
1442 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
1443 bytes.get());
1444 return nullptr;
1447 FixedInvokeArgs<2> args2(cx);
1448 args2[0].setObject(*other);
1449 args2[1].set(callee);
1451 // Step 6.b.iv.3.a.
1452 RootedValue rval(cx);
1453 if (!CallSelfHostedFunction(cx, cx->names().IterableToList,
1454 UndefinedHandleValue, args2, &rval)) {
1455 return nullptr;
1458 // Step 6.b.iv.3.b (Implemented below).
1459 arrayLike = &rval.toObject();
1460 } else {
1461 // Step 4.a is an assertion: object is not an Iterator. Testing this is
1462 // literally the very last thing we did, so we don't assert here.
1464 // Step 4.b (Implemented below).
1465 arrayLike = other;
1468 // We implement InitializeTypedArrayFromList in terms of
1469 // InitializeTypedArrayFromArrayLike.
1471 // InitializeTypedArrayFromArrayLike, step 1.
1472 uint64_t len;
1473 if (!GetLengthProperty(cx, arrayLike, &len)) {
1474 return nullptr;
1477 // InitializeTypedArrayFromArrayLike, step 2.
1478 Rooted<ArrayBufferObject*> buffer(cx);
1479 if (!maybeCreateArrayBuffer(cx, len, &buffer)) {
1480 return nullptr;
1483 MOZ_ASSERT(len <= ByteLengthLimit / BYTES_PER_ELEMENT);
1485 // Steps 6.b.i.
1486 Rooted<TypedArrayObject*> obj(
1487 cx, FixedLengthTypedArray::makeInstance(cx, buffer, 0, len, proto));
1488 if (!obj) {
1489 return nullptr;
1492 // InitializeTypedArrayFromArrayLike, steps 3-4.
1493 MOZ_ASSERT(!obj->isSharedMemory());
1494 if (!ElementSpecific<T, UnsharedOps>::setFromNonTypedArray(cx, obj, arrayLike,
1495 len)) {
1496 return nullptr;
1499 // Step 6.b.v.
1500 return obj;
1503 static bool TypedArrayConstructor(JSContext* cx, unsigned argc, Value* vp) {
1504 CallArgs args = CallArgsFromVp(argc, vp);
1505 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1506 JSMSG_TYPED_ARRAY_CALL_OR_CONSTRUCT,
1507 args.isConstructing() ? "construct" : "call");
1508 return false;
1511 template <typename T>
1512 static bool GetTemplateObjectForNative(JSContext* cx,
1513 const JS::HandleValueArray args,
1514 MutableHandleObject res) {
1515 if (args.length() == 0) {
1516 return true;
1519 HandleValue arg = args[0];
1520 if (arg.isInt32()) {
1521 uint32_t len = 0;
1522 if (arg.toInt32() >= 0) {
1523 len = arg.toInt32();
1526 size_t nbytes;
1527 if (!js::CalculateAllocSize<T>(len, &nbytes) ||
1528 nbytes > TypedArrayObject::ByteLengthLimit) {
1529 return true;
1532 res.set(
1533 FixedLengthTypedArrayObjectTemplate<T>::makeTemplateObject(cx, len));
1534 return !!res;
1537 if (!arg.isObject()) {
1538 return true;
1540 auto* obj = &arg.toObject();
1542 // We don't support wrappers, because of the complicated interaction between
1543 // wrapped ArrayBuffers and TypedArrays, see |fromBufferWrapped()|.
1544 if (IsWrapper(obj)) {
1545 return true;
1548 // We don't use the template's length in the object case, so we can create
1549 // the template typed array with an initial length of zero.
1550 uint32_t len = 0;
1552 if (!obj->is<ArrayBufferObjectMaybeShared>() ||
1553 !obj->as<ArrayBufferObjectMaybeShared>().isResizable()) {
1554 res.set(
1555 FixedLengthTypedArrayObjectTemplate<T>::makeTemplateObject(cx, len));
1556 } else {
1557 res.set(ResizableTypedArrayObjectTemplate<T>::makeTemplateObject(cx));
1559 return !!res;
1562 /* static */ bool TypedArrayObject::GetTemplateObjectForNative(
1563 JSContext* cx, Native native, const JS::HandleValueArray args,
1564 MutableHandleObject res) {
1565 MOZ_ASSERT(!res);
1566 #define CHECK_TYPED_ARRAY_CONSTRUCTOR(_, T, N) \
1567 if (native == &TypedArrayObjectTemplate<T>::class_constructor) { \
1568 return ::GetTemplateObjectForNative<T>(cx, args, res); \
1570 JS_FOR_EACH_TYPED_ARRAY(CHECK_TYPED_ARRAY_CONSTRUCTOR)
1571 #undef CHECK_TYPED_ARRAY_CONSTRUCTOR
1572 return true;
1575 static bool LengthGetterImpl(JSContext* cx, const CallArgs& args) {
1576 auto* tarr = &args.thisv().toObject().as<TypedArrayObject>();
1577 args.rval().setNumber(tarr->length().valueOr(0));
1578 return true;
1581 static bool TypedArray_lengthGetter(JSContext* cx, unsigned argc, Value* vp) {
1582 CallArgs args = CallArgsFromVp(argc, vp);
1583 return CallNonGenericMethod<IsTypedArrayObject, LengthGetterImpl>(cx, args);
1586 static bool ByteOffsetGetterImpl(JSContext* cx, const CallArgs& args) {
1587 auto* tarr = &args.thisv().toObject().as<TypedArrayObject>();
1588 args.rval().setNumber(tarr->byteOffset().valueOr(0));
1589 return true;
1592 static bool TypedArray_byteOffsetGetter(JSContext* cx, unsigned argc,
1593 Value* vp) {
1594 CallArgs args = CallArgsFromVp(argc, vp);
1595 return CallNonGenericMethod<IsTypedArrayObject, ByteOffsetGetterImpl>(cx,
1596 args);
1599 static bool ByteLengthGetterImpl(JSContext* cx, const CallArgs& args) {
1600 auto* tarr = &args.thisv().toObject().as<TypedArrayObject>();
1601 args.rval().setNumber(tarr->byteLength().valueOr(0));
1602 return true;
1605 static bool TypedArray_byteLengthGetter(JSContext* cx, unsigned argc,
1606 Value* vp) {
1607 CallArgs args = CallArgsFromVp(argc, vp);
1608 return CallNonGenericMethod<IsTypedArrayObject, ByteLengthGetterImpl>(cx,
1609 args);
1612 static bool BufferGetterImpl(JSContext* cx, const CallArgs& args) {
1613 MOZ_ASSERT(IsTypedArrayObject(args.thisv()));
1614 Rooted<TypedArrayObject*> tarray(
1615 cx, &args.thisv().toObject().as<TypedArrayObject>());
1616 if (!TypedArrayObject::ensureHasBuffer(cx, tarray)) {
1617 return false;
1619 args.rval().set(tarray->bufferValue());
1620 return true;
1623 static bool TypedArray_bufferGetter(JSContext* cx, unsigned argc, Value* vp) {
1624 CallArgs args = CallArgsFromVp(argc, vp);
1625 return CallNonGenericMethod<IsTypedArrayObject, BufferGetterImpl>(cx, args);
1628 // ES2019 draft rev fc9ecdcd74294d0ca3146d4b274e2a8e79565dc3
1629 // 22.2.3.32 get %TypedArray%.prototype [ @@toStringTag ]
1630 static bool TypedArray_toStringTagGetter(JSContext* cx, unsigned argc,
1631 Value* vp) {
1632 CallArgs args = CallArgsFromVp(argc, vp);
1634 // Steps 1-2.
1635 if (!args.thisv().isObject()) {
1636 args.rval().setUndefined();
1637 return true;
1640 JSObject* obj = CheckedUnwrapStatic(&args.thisv().toObject());
1641 if (!obj) {
1642 ReportAccessDenied(cx);
1643 return false;
1646 // Step 3.
1647 if (!obj->is<TypedArrayObject>()) {
1648 args.rval().setUndefined();
1649 return true;
1652 // Steps 4-6.
1653 JSProtoKey protoKey = StandardProtoKeyOrNull(obj);
1654 MOZ_ASSERT(protoKey);
1656 args.rval().setString(ClassName(protoKey, cx));
1657 return true;
1660 /* static */ const JSPropertySpec TypedArrayObject::protoAccessors[] = {
1661 JS_PSG("length", TypedArray_lengthGetter, 0),
1662 JS_PSG("buffer", TypedArray_bufferGetter, 0),
1663 JS_PSG("byteLength", TypedArray_byteLengthGetter, 0),
1664 JS_PSG("byteOffset", TypedArray_byteOffsetGetter, 0),
1665 JS_SYM_GET(toStringTag, TypedArray_toStringTagGetter, 0),
1666 JS_PS_END};
1668 template <typename T>
1669 static inline bool SetFromTypedArray(Handle<TypedArrayObject*> target,
1670 size_t targetLength,
1671 Handle<TypedArrayObject*> source,
1672 size_t sourceLength, size_t offset) {
1673 // WARNING: |source| may be an unwrapped typed array from a different
1674 // compartment. Proceed with caution!
1676 if (target->isSharedMemory() || source->isSharedMemory()) {
1677 return ElementSpecific<T, SharedOps>::setFromTypedArray(
1678 target, targetLength, source, sourceLength, offset);
1680 return ElementSpecific<T, UnsharedOps>::setFromTypedArray(
1681 target, targetLength, source, sourceLength, offset);
1684 template <typename T>
1685 static inline bool SetFromNonTypedArray(JSContext* cx,
1686 Handle<TypedArrayObject*> target,
1687 HandleObject source, size_t len,
1688 size_t offset) {
1689 MOZ_ASSERT(!source->is<TypedArrayObject>(), "use SetFromTypedArray");
1691 if (target->isSharedMemory()) {
1692 return ElementSpecific<T, SharedOps>::setFromNonTypedArray(
1693 cx, target, source, len, offset);
1695 return ElementSpecific<T, UnsharedOps>::setFromNonTypedArray(
1696 cx, target, source, len, offset);
1699 // ES2023 draft rev 22cc56ab08fcab92a865978c0aa5c6f1d8ce250f
1700 // 23.2.3.24.1 SetTypedArrayFromTypedArray ( target, targetOffset, source )
1701 static bool SetTypedArrayFromTypedArray(JSContext* cx,
1702 Handle<TypedArrayObject*> target,
1703 double targetOffset,
1704 size_t targetLength,
1705 Handle<TypedArrayObject*> source) {
1706 // WARNING: |source| may be an unwrapped typed array from a different
1707 // compartment. Proceed with caution!
1709 MOZ_ASSERT(targetOffset >= 0);
1711 // Steps 1-3. (Performed in caller.)
1712 MOZ_ASSERT(!target->hasDetachedBuffer());
1714 // Steps 4-5.
1715 auto sourceLength = source->length();
1716 if (!sourceLength) {
1717 ReportOutOfBounds(cx, source);
1718 return false;
1721 // Steps 13-14 (Split into two checks to provide better error messages).
1722 if (targetOffset > targetLength) {
1723 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
1724 return false;
1727 // Step 14 (Cont'd).
1728 size_t offset = size_t(targetOffset);
1729 if (*sourceLength > targetLength - offset) {
1730 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1731 JSMSG_SOURCE_ARRAY_TOO_LONG);
1732 return false;
1735 // Step 15.
1736 if (Scalar::isBigIntType(target->type()) !=
1737 Scalar::isBigIntType(source->type())) {
1738 JS_ReportErrorNumberASCII(
1739 cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_NOT_COMPATIBLE,
1740 source->getClass()->name, target->getClass()->name);
1741 return false;
1744 // Steps 6-12, 16-24.
1745 switch (target->type()) {
1746 #define SET_FROM_TYPED_ARRAY(_, T, N) \
1747 case Scalar::N: \
1748 if (!SetFromTypedArray<T>(target, targetLength, source, *sourceLength, \
1749 offset)) { \
1750 ReportOutOfMemory(cx); \
1751 return false; \
1753 break;
1754 JS_FOR_EACH_TYPED_ARRAY(SET_FROM_TYPED_ARRAY)
1755 #undef SET_FROM_TYPED_ARRAY
1756 default:
1757 MOZ_CRASH("Unsupported TypedArray type");
1760 return true;
1763 // ES2023 draft rev 22cc56ab08fcab92a865978c0aa5c6f1d8ce250f
1764 // 23.2.3.24.1 SetTypedArrayFromArrayLike ( target, targetOffset, source )
1765 static bool SetTypedArrayFromArrayLike(JSContext* cx,
1766 Handle<TypedArrayObject*> target,
1767 double targetOffset, size_t targetLength,
1768 HandleObject src) {
1769 MOZ_ASSERT(targetOffset >= 0);
1771 // Steps 1-2. (Performed in caller.)
1772 MOZ_ASSERT(target->length().isSome());
1774 // Steps 3-4. (Performed in caller.)
1776 // Step 5.
1777 uint64_t srcLength;
1778 if (!GetLengthProperty(cx, src, &srcLength)) {
1779 return false;
1782 // Steps 6-7 (Split into two checks to provide better error messages).
1783 if (targetOffset > targetLength) {
1784 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
1785 return false;
1788 // Step 7 (Cont'd).
1789 size_t offset = size_t(targetOffset);
1790 if (srcLength > targetLength - offset) {
1791 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
1792 JSMSG_SOURCE_ARRAY_TOO_LONG);
1793 return false;
1796 MOZ_ASSERT(srcLength <= targetLength);
1798 // Steps 8-9.
1799 if (srcLength > 0) {
1800 switch (target->type()) {
1801 #define SET_FROM_NON_TYPED_ARRAY(_, T, N) \
1802 case Scalar::N: \
1803 if (!SetFromNonTypedArray<T>(cx, target, src, srcLength, offset)) \
1804 return false; \
1805 break;
1806 JS_FOR_EACH_TYPED_ARRAY(SET_FROM_NON_TYPED_ARRAY)
1807 #undef SET_FROM_NON_TYPED_ARRAY
1808 default:
1809 MOZ_CRASH("Unsupported TypedArray type");
1813 // Step 10.
1814 return true;
1817 // ES2023 draft rev 22cc56ab08fcab92a865978c0aa5c6f1d8ce250f
1818 // 23.2.3.24 %TypedArray%.prototype.set ( source [ , offset ] )
1819 // 23.2.3.24.1 SetTypedArrayFromTypedArray ( target, targetOffset, source )
1820 // 23.2.3.24.2 SetTypedArrayFromArrayLike ( target, targetOffset, source )
1821 /* static */
1822 bool TypedArrayObject::set_impl(JSContext* cx, const CallArgs& args) {
1823 MOZ_ASSERT(IsTypedArrayObject(args.thisv()));
1825 // Steps 1-3 (Validation performed as part of CallNonGenericMethod).
1826 Rooted<TypedArrayObject*> target(
1827 cx, &args.thisv().toObject().as<TypedArrayObject>());
1829 // Steps 4-5.
1830 double targetOffset = 0;
1831 if (args.length() > 1) {
1832 // Step 4.
1833 if (!ToInteger(cx, args[1], &targetOffset)) {
1834 return false;
1837 // Step 5.
1838 if (targetOffset < 0) {
1839 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
1840 return false;
1844 // 23.2.3.24.1, steps 1-2.
1845 // 23.2.3.24.2, steps 1-2.
1846 auto targetLength = target->length();
1847 if (!targetLength) {
1848 ReportOutOfBounds(cx, target);
1849 return false;
1852 // 23.2.3.24.2, step 4. (23.2.3.24.1 only applies if args[0] is a typed
1853 // array, so it doesn't make a difference there to apply ToObject here.)
1854 RootedObject src(cx, ToObject(cx, args.get(0)));
1855 if (!src) {
1856 return false;
1859 Rooted<TypedArrayObject*> srcTypedArray(cx);
1861 JSObject* obj = CheckedUnwrapStatic(src);
1862 if (!obj) {
1863 ReportAccessDenied(cx);
1864 return false;
1867 if (obj->is<TypedArrayObject>()) {
1868 srcTypedArray = &obj->as<TypedArrayObject>();
1872 // Steps 6-7.
1873 if (srcTypedArray) {
1874 if (!SetTypedArrayFromTypedArray(cx, target, targetOffset, *targetLength,
1875 srcTypedArray)) {
1876 return false;
1878 } else {
1879 if (!SetTypedArrayFromArrayLike(cx, target, targetOffset, *targetLength,
1880 src)) {
1881 return false;
1885 // Step 8.
1886 args.rval().setUndefined();
1887 return true;
1890 /* static */
1891 bool TypedArrayObject::set(JSContext* cx, unsigned argc, Value* vp) {
1892 CallArgs args = CallArgsFromVp(argc, vp);
1893 return CallNonGenericMethod<IsTypedArrayObject, TypedArrayObject::set_impl>(
1894 cx, args);
1897 // ES2020 draft rev dc1e21c454bd316810be1c0e7af0131a2d7f38e9
1898 // 22.2.3.5 %TypedArray%.prototype.copyWithin ( target, start [ , end ] )
1899 /* static */
1900 bool TypedArrayObject::copyWithin_impl(JSContext* cx, const CallArgs& args) {
1901 MOZ_ASSERT(IsTypedArrayObject(args.thisv()));
1903 // Steps 1-2.
1904 Rooted<TypedArrayObject*> tarray(
1905 cx, &args.thisv().toObject().as<TypedArrayObject>());
1907 auto arrayLength = tarray->length();
1908 if (!arrayLength) {
1909 ReportOutOfBounds(cx, tarray);
1910 return false;
1913 // Step 3.
1914 size_t len = *arrayLength;
1916 // Step 4.
1917 double relativeTarget;
1918 if (!ToInteger(cx, args.get(0), &relativeTarget)) {
1919 return false;
1922 // Step 5.
1923 uint64_t to;
1924 if (relativeTarget < 0) {
1925 to = std::max(len + relativeTarget, 0.0);
1926 } else {
1927 to = std::min(relativeTarget, double(len));
1930 // Step 6.
1931 double relativeStart;
1932 if (!ToInteger(cx, args.get(1), &relativeStart)) {
1933 return false;
1936 // Step 7.
1937 uint64_t from;
1938 if (relativeStart < 0) {
1939 from = std::max(len + relativeStart, 0.0);
1940 } else {
1941 from = std::min(relativeStart, double(len));
1944 // Step 8.
1945 double relativeEnd;
1946 if (!args.hasDefined(2)) {
1947 relativeEnd = len;
1948 } else {
1949 if (!ToInteger(cx, args[2], &relativeEnd)) {
1950 return false;
1954 // Step 9.
1955 uint64_t final_;
1956 if (relativeEnd < 0) {
1957 final_ = std::max(len + relativeEnd, 0.0);
1958 } else {
1959 final_ = std::min(relativeEnd, double(len));
1962 // Step 10.
1963 MOZ_ASSERT(to <= len);
1964 uint64_t count;
1965 if (from <= final_) {
1966 count = std::min(final_ - from, len - to);
1967 } else {
1968 count = 0;
1971 // Step 11.
1973 // Note that this copies elements effectively by memmove, *not* in
1974 // step 11's specified order. This is unobservable, even when the underlying
1975 // buffer is a SharedArrayBuffer instance, because the access is unordered and
1976 // therefore is allowed to have data races.
1978 if (count == 0) {
1979 args.rval().setObject(*tarray);
1980 return true;
1983 // Reacquire the length because side-effects may have detached or resized the
1984 // array buffer.
1985 arrayLength = tarray->length();
1986 if (!arrayLength) {
1987 ReportOutOfBounds(cx, tarray);
1988 return false;
1991 // Recompute the bounds if the current length is smaller.
1992 if (*arrayLength < len) {
1993 MOZ_ASSERT(to + count <= len);
1994 MOZ_ASSERT(from + count <= len);
1996 len = *arrayLength;
1998 // Don't copy any bytes if either index is no longer in-bounds.
1999 if (to >= len || from >= len) {
2000 args.rval().setObject(*tarray);
2001 return true;
2004 // Restrict |count| to not copy any bytes after the end of the array.
2005 count = std::min(count, std::min(len - to, len - from));
2006 MOZ_ASSERT(count > 0);
2009 // Don't multiply by |tarray->bytesPerElement()| in case the compiler can't
2010 // strength-reduce multiplication by 1/2/4/8 into the equivalent shift.
2011 const size_t ElementShift = TypedArrayShift(tarray->type());
2013 MOZ_ASSERT((SIZE_MAX >> ElementShift) > to);
2014 size_t byteDest = to << ElementShift;
2016 MOZ_ASSERT((SIZE_MAX >> ElementShift) > from);
2017 size_t byteSrc = from << ElementShift;
2019 MOZ_ASSERT((SIZE_MAX >> ElementShift) >= count);
2020 size_t byteSize = count << ElementShift;
2022 #ifdef DEBUG
2024 size_t viewByteLength = len << ElementShift;
2025 MOZ_ASSERT(byteSize <= viewByteLength);
2026 MOZ_ASSERT(byteDest < viewByteLength);
2027 MOZ_ASSERT(byteSrc < viewByteLength);
2028 MOZ_ASSERT(byteDest <= viewByteLength - byteSize);
2029 MOZ_ASSERT(byteSrc <= viewByteLength - byteSize);
2031 #endif
2033 SharedMem<uint8_t*> data = tarray->dataPointerEither().cast<uint8_t*>();
2034 if (tarray->isSharedMemory()) {
2035 jit::AtomicOperations::memmoveSafeWhenRacy(data + byteDest, data + byteSrc,
2036 byteSize);
2037 } else {
2038 memmove(data.unwrapUnshared() + byteDest, data.unwrapUnshared() + byteSrc,
2039 byteSize);
2042 args.rval().setObject(*tarray);
2043 return true;
2046 /* static */
2047 bool TypedArrayObject::copyWithin(JSContext* cx, unsigned argc, Value* vp) {
2048 AutoJSMethodProfilerEntry pseudoFrame(cx, "[TypedArray].prototype",
2049 "copyWithin");
2050 CallArgs args = CallArgsFromVp(argc, vp);
2051 return CallNonGenericMethod<IsTypedArrayObject,
2052 TypedArrayObject::copyWithin_impl>(cx, args);
2055 // Byte vector with large enough inline storage to allow constructing small
2056 // typed arrays without extra heap allocations.
2057 using ByteVector =
2058 js::Vector<uint8_t, FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT>;
2060 static UniqueChars QuoteString(JSContext* cx, char16_t ch) {
2061 Sprinter sprinter(cx);
2062 if (!sprinter.init()) {
2063 return nullptr;
2066 StringEscape esc{};
2067 js::EscapePrinter ep(sprinter, esc);
2068 ep.putChar(ch);
2070 return sprinter.release();
2074 * FromHex ( string [ , maxLength ] )
2076 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-fromhex
2078 static bool FromHex(JSContext* cx, Handle<JSString*> string, size_t maxLength,
2079 ByteVector& bytes, size_t* readLength) {
2080 // Step 1. (Not applicable in our implementation.)
2082 // Step 2.
2083 size_t length = string->length();
2085 // Step 3.
2086 if (length % 2 != 0) {
2087 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2088 JSMSG_TYPED_ARRAY_BAD_HEX_STRING_LENGTH);
2089 return false;
2092 JSLinearString* linear = string->ensureLinear(cx);
2093 if (!linear) {
2094 return false;
2097 // Step 4. (Not applicable in our implementation.)
2098 MOZ_ASSERT(bytes.empty());
2100 // Step 5.
2101 size_t index = 0;
2103 // Step 6.
2104 while (index < length && bytes.length() < maxLength) {
2105 // Step 6.a.
2106 char16_t c0 = linear->latin1OrTwoByteChar(index);
2107 char16_t c1 = linear->latin1OrTwoByteChar(index + 1);
2109 // Step 6.b.
2110 if (MOZ_UNLIKELY(!mozilla::IsAsciiHexDigit(c0) ||
2111 !mozilla::IsAsciiHexDigit(c1))) {
2112 char16_t ch = !mozilla::IsAsciiHexDigit(c0) ? c0 : c1;
2113 if (auto str = QuoteString(cx, ch)) {
2114 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2115 JSMSG_TYPED_ARRAY_BAD_HEX_DIGIT, str.get());
2117 return false;
2120 // Step 6.c.
2121 index += 2;
2123 // Step 6.d.
2124 uint8_t byte = (mozilla::AsciiAlphanumericToNumber(c0) << 4) +
2125 mozilla::AsciiAlphanumericToNumber(c1);
2127 // Step 6.e.
2128 if (!bytes.append(byte)) {
2129 return false;
2133 // Step 7.
2134 *readLength = index;
2135 return true;
2139 * Uint8Array.fromHex ( string )
2141 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.fromhex
2143 static bool uint8array_fromHex(JSContext* cx, unsigned argc, Value* vp) {
2144 CallArgs args = CallArgsFromVp(argc, vp);
2146 // Step 1.
2147 if (!args.get(0).isString()) {
2148 return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK,
2149 args.get(0), nullptr, "not a string");
2151 Rooted<JSString*> string(cx, args[0].toString());
2153 // Step 2.
2154 constexpr size_t maxLength = std::numeric_limits<size_t>::max();
2155 ByteVector bytes(cx);
2156 size_t unusedReadLength;
2157 if (!FromHex(cx, string, maxLength, bytes, &unusedReadLength)) {
2158 return false;
2161 // Step 3.
2162 size_t resultLength = bytes.length();
2164 // Step 4.
2165 auto* tarray =
2166 TypedArrayObjectTemplate<uint8_t>::fromLength(cx, resultLength);
2167 if (!tarray) {
2168 return false;
2171 // Step 5.
2172 auto target = SharedMem<uint8_t*>::unshared(tarray->dataPointerUnshared());
2173 auto source = SharedMem<uint8_t*>::unshared(bytes.begin());
2174 UnsharedOps::podCopy(target, source, resultLength);
2176 // Step 6.
2177 args.rval().setObject(*tarray);
2178 return true;
2182 * Uint8Array.prototype.setFromHex ( string )
2184 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfromhex
2186 static bool uint8array_setFromHex(JSContext* cx, const CallArgs& args) {
2187 Rooted<TypedArrayObject*> tarray(
2188 cx, &args.thisv().toObject().as<TypedArrayObject>());
2190 // Step 3.
2191 if (!args.get(0).isString()) {
2192 return ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK,
2193 args.get(0), nullptr, "not a string");
2195 Rooted<JSString*> string(cx, args[0].toString());
2197 // Steps 4-6.
2198 auto length = tarray->length();
2199 if (!length) {
2200 ReportOutOfBounds(cx, tarray);
2201 return false;
2204 // Step 7.
2205 size_t maxLength = *length;
2207 // Steps 8-9.
2208 ByteVector bytes(cx);
2209 size_t readLength;
2210 if (!FromHex(cx, string, maxLength, bytes, &readLength)) {
2211 return false;
2214 // Step 10.
2215 size_t written = bytes.length();
2217 // Step 11.
2219 // The underlying buffer has neither been detached nor shrunk. (It may have
2220 // been grown when it's a growable shared buffer and a concurrent thread
2221 // resized the buffer.)
2222 MOZ_ASSERT(!tarray->hasDetachedBuffer());
2223 MOZ_ASSERT(tarray->length().valueOr(0) >= *length);
2225 // Step 12.
2226 MOZ_ASSERT(written <= *length);
2228 // Step 13. (Inlined SetUint8ArrayBytes)
2229 auto target = tarray->dataPointerEither().cast<uint8_t*>();
2230 auto source = SharedMem<uint8_t*>::unshared(bytes.begin());
2231 if (tarray->isSharedMemory()) {
2232 SharedOps::podCopy(target, source, written);
2233 } else {
2234 UnsharedOps::podCopy(target, source, written);
2237 // Step 14.
2238 Rooted<PlainObject*> result(cx, NewPlainObject(cx));
2239 if (!result) {
2240 return false;
2243 // Step 15.
2244 Rooted<Value> readValue(cx, NumberValue(readLength));
2245 if (!DefineDataProperty(cx, result, cx->names().read, readValue)) {
2246 return false;
2249 // Step 16.
2250 Rooted<Value> writtenValue(cx, NumberValue(written));
2251 if (!DefineDataProperty(cx, result, cx->names().written, writtenValue)) {
2252 return false;
2255 // Step 17.
2256 args.rval().setObject(*result);
2257 return true;
2261 * Uint8Array.prototype.setFromHex ( string )
2263 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.setfromhex
2265 static bool uint8array_setFromHex(JSContext* cx, unsigned argc, Value* vp) {
2266 CallArgs args = CallArgsFromVp(argc, vp);
2268 // Steps 1-2.
2269 return CallNonGenericMethod<IsUint8ArrayObject, uint8array_setFromHex>(cx,
2270 args);
2274 * Uint8Array.prototype.toHex ( )
2276 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tohex
2278 static bool uint8array_toHex(JSContext* cx, const CallArgs& args) {
2279 Rooted<TypedArrayObject*> tarray(
2280 cx, &args.thisv().toObject().as<TypedArrayObject>());
2282 // Step 3. (Partial)
2283 auto length = tarray->length();
2284 if (!length) {
2285 ReportOutOfBounds(cx, tarray);
2286 return false;
2289 // |length| is limited by |ByteLengthLimit|, which ensures that multiplying it
2290 // by two won't overflow.
2291 static_assert(TypedArrayObject::ByteLengthLimit <=
2292 std::numeric_limits<size_t>::max() / 2);
2293 MOZ_ASSERT(*length <= TypedArrayObject::ByteLengthLimit);
2295 // Compute the output string length. Each byte is encoded as two characters,
2296 // so the output length is exactly twice as large as |length|.
2297 size_t outLength = *length * 2;
2298 if (outLength > JSString::MAX_LENGTH) {
2299 ReportAllocationOverflow(cx);
2300 return false;
2303 // Step 4.
2304 JSStringBuilder sb(cx);
2305 if (!sb.reserve(outLength)) {
2306 return false;
2309 // NB: Lower case hex digits.
2310 static constexpr char HexDigits[] = "0123456789abcdef";
2311 static_assert(std::char_traits<char>::length(HexDigits) == 16);
2313 // Steps 3 and 5.
2315 // Our implementation directly converts the bytes to their string
2316 // representation instead of first collecting them into an intermediate list.
2317 auto data = tarray->dataPointerEither().cast<uint8_t*>();
2318 for (size_t index = 0; index < *length; index++) {
2319 auto byte = jit::AtomicOperations::loadSafeWhenRacy(data + index);
2321 sb.infallibleAppend(HexDigits[byte >> 4]);
2322 sb.infallibleAppend(HexDigits[byte & 0xf]);
2325 MOZ_ASSERT(sb.length() == outLength, "all characters were written");
2327 // Step 6.
2328 auto* str = sb.finishString();
2329 if (!str) {
2330 return false;
2333 args.rval().setString(str);
2334 return true;
2338 * Uint8Array.prototype.toHex ( )
2340 * https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.prototype.tohex
2342 static bool uint8array_toHex(JSContext* cx, unsigned argc, Value* vp) {
2343 CallArgs args = CallArgsFromVp(argc, vp);
2345 // Steps 1-2.
2346 return CallNonGenericMethod<IsUint8ArrayObject, uint8array_toHex>(cx, args);
2349 /* static */ const JSFunctionSpec TypedArrayObject::protoFunctions[] = {
2350 JS_SELF_HOSTED_FN("subarray", "TypedArraySubarray", 2, 0),
2351 JS_FN("set", TypedArrayObject::set, 1, 0),
2352 JS_FN("copyWithin", TypedArrayObject::copyWithin, 2, 0),
2353 JS_SELF_HOSTED_FN("every", "TypedArrayEvery", 1, 0),
2354 JS_SELF_HOSTED_FN("fill", "TypedArrayFill", 3, 0),
2355 JS_SELF_HOSTED_FN("filter", "TypedArrayFilter", 1, 0),
2356 JS_SELF_HOSTED_FN("find", "TypedArrayFind", 1, 0),
2357 JS_SELF_HOSTED_FN("findIndex", "TypedArrayFindIndex", 1, 0),
2358 JS_SELF_HOSTED_FN("findLast", "TypedArrayFindLast", 1, 0),
2359 JS_SELF_HOSTED_FN("findLastIndex", "TypedArrayFindLastIndex", 1, 0),
2360 JS_SELF_HOSTED_FN("forEach", "TypedArrayForEach", 1, 0),
2361 JS_SELF_HOSTED_FN("indexOf", "TypedArrayIndexOf", 2, 0),
2362 JS_SELF_HOSTED_FN("join", "TypedArrayJoin", 1, 0),
2363 JS_SELF_HOSTED_FN("lastIndexOf", "TypedArrayLastIndexOf", 1, 0),
2364 JS_SELF_HOSTED_FN("map", "TypedArrayMap", 1, 0),
2365 JS_SELF_HOSTED_FN("reduce", "TypedArrayReduce", 1, 0),
2366 JS_SELF_HOSTED_FN("reduceRight", "TypedArrayReduceRight", 1, 0),
2367 JS_SELF_HOSTED_FN("reverse", "TypedArrayReverse", 0, 0),
2368 JS_SELF_HOSTED_FN("slice", "TypedArraySlice", 2, 0),
2369 JS_SELF_HOSTED_FN("some", "TypedArraySome", 1, 0),
2370 JS_SELF_HOSTED_FN("sort", "TypedArraySort", 1, 0),
2371 JS_SELF_HOSTED_FN("entries", "TypedArrayEntries", 0, 0),
2372 JS_SELF_HOSTED_FN("keys", "TypedArrayKeys", 0, 0),
2373 JS_SELF_HOSTED_FN("values", "$TypedArrayValues", 0, 0),
2374 JS_SELF_HOSTED_SYM_FN(iterator, "$TypedArrayValues", 0, 0),
2375 JS_SELF_HOSTED_FN("includes", "TypedArrayIncludes", 2, 0),
2376 JS_SELF_HOSTED_FN("toString", "ArrayToString", 0, 0),
2377 JS_SELF_HOSTED_FN("toLocaleString", "TypedArrayToLocaleString", 2, 0),
2378 JS_SELF_HOSTED_FN("at", "TypedArrayAt", 1, 0),
2379 JS_SELF_HOSTED_FN("toReversed", "TypedArrayToReversed", 0, 0),
2380 JS_SELF_HOSTED_FN("toSorted", "TypedArrayToSorted", 1, 0),
2381 JS_SELF_HOSTED_FN("with", "TypedArrayWith", 2, 0),
2382 JS_FS_END,
2385 /* static */ const JSFunctionSpec TypedArrayObject::staticFunctions[] = {
2386 JS_SELF_HOSTED_FN("from", "TypedArrayStaticFrom", 3, 0),
2387 JS_SELF_HOSTED_FN("of", "TypedArrayStaticOf", 0, 0),
2388 JS_FS_END,
2391 /* static */ const JSPropertySpec TypedArrayObject::staticProperties[] = {
2392 JS_SELF_HOSTED_SYM_GET(species, "$TypedArraySpecies", 0),
2393 JS_PS_END,
2396 static JSObject* CreateSharedTypedArrayPrototype(JSContext* cx,
2397 JSProtoKey key) {
2398 return GlobalObject::createBlankPrototype(
2399 cx, cx->global(), &TypedArrayObject::sharedTypedArrayPrototypeClass);
2402 static const ClassSpec TypedArrayObjectSharedTypedArrayPrototypeClassSpec = {
2403 GenericCreateConstructor<TypedArrayConstructor, 0, gc::AllocKind::FUNCTION>,
2404 CreateSharedTypedArrayPrototype,
2405 TypedArrayObject::staticFunctions,
2406 TypedArrayObject::staticProperties,
2407 TypedArrayObject::protoFunctions,
2408 TypedArrayObject::protoAccessors,
2409 nullptr,
2410 ClassSpec::DontDefineConstructor,
2413 /* static */ const JSClass TypedArrayObject::sharedTypedArrayPrototypeClass = {
2414 "TypedArrayPrototype",
2415 JSCLASS_HAS_CACHED_PROTO(JSProto_TypedArray),
2416 JS_NULL_CLASS_OPS,
2417 &TypedArrayObjectSharedTypedArrayPrototypeClassSpec,
2420 namespace {
2422 // This default implementation is only valid for integer types less
2423 // than 32-bits in size.
2424 template <typename NativeType>
2425 bool TypedArrayObjectTemplate<NativeType>::getElementPure(
2426 TypedArrayObject* tarray, size_t index, Value* vp) {
2427 static_assert(sizeof(NativeType) < 4,
2428 "this method must only handle NativeType values that are "
2429 "always exact int32_t values");
2431 *vp = Int32Value(getIndex(tarray, index));
2432 return true;
2435 // We need to specialize for floats and other integer types.
2436 template <>
2437 bool TypedArrayObjectTemplate<int32_t>::getElementPure(TypedArrayObject* tarray,
2438 size_t index,
2439 Value* vp) {
2440 *vp = Int32Value(getIndex(tarray, index));
2441 return true;
2444 template <>
2445 bool TypedArrayObjectTemplate<uint32_t>::getElementPure(
2446 TypedArrayObject* tarray, size_t index, Value* vp) {
2447 uint32_t val = getIndex(tarray, index);
2448 *vp = NumberValue(val);
2449 return true;
2452 template <>
2453 bool TypedArrayObjectTemplate<float>::getElementPure(TypedArrayObject* tarray,
2454 size_t index, Value* vp) {
2455 float val = getIndex(tarray, index);
2456 double dval = val;
2459 * Doubles in typed arrays could be typed-punned arrays of integers. This
2460 * could allow user code to break the engine-wide invariant that only
2461 * canonical nans are stored into jsvals, which means user code could
2462 * confuse the engine into interpreting a double-typed jsval as an
2463 * object-typed jsval.
2465 * This could be removed for platforms/compilers known to convert a 32-bit
2466 * non-canonical nan to a 64-bit canonical nan.
2468 *vp = JS::CanonicalizedDoubleValue(dval);
2469 return true;
2472 template <>
2473 bool TypedArrayObjectTemplate<double>::getElementPure(TypedArrayObject* tarray,
2474 size_t index, Value* vp) {
2475 double val = getIndex(tarray, index);
2478 * Doubles in typed arrays could be typed-punned arrays of integers. This
2479 * could allow user code to break the engine-wide invariant that only
2480 * canonical nans are stored into jsvals, which means user code could
2481 * confuse the engine into interpreting a double-typed jsval as an
2482 * object-typed jsval.
2484 *vp = JS::CanonicalizedDoubleValue(val);
2485 return true;
2488 template <>
2489 bool TypedArrayObjectTemplate<int64_t>::getElementPure(TypedArrayObject* tarray,
2490 size_t index,
2491 Value* vp) {
2492 return false;
2495 template <>
2496 bool TypedArrayObjectTemplate<uint64_t>::getElementPure(
2497 TypedArrayObject* tarray, size_t index, Value* vp) {
2498 return false;
2500 } /* anonymous namespace */
2502 namespace {
2504 template <typename NativeType>
2505 bool TypedArrayObjectTemplate<NativeType>::getElement(JSContext* cx,
2506 TypedArrayObject* tarray,
2507 size_t index,
2508 MutableHandleValue val) {
2509 MOZ_ALWAYS_TRUE(getElementPure(tarray, index, val.address()));
2510 return true;
2513 template <>
2514 bool TypedArrayObjectTemplate<int64_t>::getElement(JSContext* cx,
2515 TypedArrayObject* tarray,
2516 size_t index,
2517 MutableHandleValue val) {
2518 int64_t n = getIndex(tarray, index);
2519 BigInt* res = BigInt::createFromInt64(cx, n);
2520 if (!res) {
2521 return false;
2523 val.setBigInt(res);
2524 return true;
2527 template <>
2528 bool TypedArrayObjectTemplate<uint64_t>::getElement(JSContext* cx,
2529 TypedArrayObject* tarray,
2530 size_t index,
2531 MutableHandleValue val) {
2532 uint64_t n = getIndex(tarray, index);
2533 BigInt* res = BigInt::createFromUint64(cx, n);
2534 if (!res) {
2535 return false;
2537 val.setBigInt(res);
2538 return true;
2540 } /* anonymous namespace */
2542 namespace js {
2544 template <>
2545 bool TypedArrayObject::getElement<CanGC>(JSContext* cx, size_t index,
2546 MutableHandleValue val) {
2547 switch (type()) {
2548 #define GET_ELEMENT(_, T, N) \
2549 case Scalar::N: \
2550 return TypedArrayObjectTemplate<T>::getElement(cx, this, index, val);
2551 JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT)
2552 #undef GET_ELEMENT
2553 case Scalar::MaxTypedArrayViewType:
2554 case Scalar::Int64:
2555 case Scalar::Simd128:
2556 break;
2559 MOZ_CRASH("Unknown TypedArray type");
2562 template <>
2563 bool TypedArrayObject::getElement<NoGC>(
2564 JSContext* cx, size_t index,
2565 typename MaybeRooted<Value, NoGC>::MutableHandleType vp) {
2566 return getElementPure(index, vp.address());
2569 } // namespace js
2571 bool TypedArrayObject::getElementPure(size_t index, Value* vp) {
2572 switch (type()) {
2573 #define GET_ELEMENT_PURE(_, T, N) \
2574 case Scalar::N: \
2575 return TypedArrayObjectTemplate<T>::getElementPure(this, index, vp);
2576 JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT_PURE)
2577 #undef GET_ELEMENT
2578 case Scalar::MaxTypedArrayViewType:
2579 case Scalar::Int64:
2580 case Scalar::Simd128:
2581 break;
2584 MOZ_CRASH("Unknown TypedArray type");
2587 /* static */
2588 bool TypedArrayObject::getElements(JSContext* cx,
2589 Handle<TypedArrayObject*> tarray,
2590 Value* vp) {
2591 size_t length = tarray->length().valueOr(0);
2592 MOZ_ASSERT_IF(length > 0, !tarray->hasDetachedBuffer());
2594 switch (tarray->type()) {
2595 #define GET_ELEMENTS(_, T, N) \
2596 case Scalar::N: \
2597 for (size_t i = 0; i < length; ++i, ++vp) { \
2598 if (!TypedArrayObjectTemplate<T>::getElement( \
2599 cx, tarray, i, MutableHandleValue::fromMarkedLocation(vp))) { \
2600 return false; \
2603 return true;
2604 JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENTS)
2605 #undef GET_ELEMENTS
2606 case Scalar::MaxTypedArrayViewType:
2607 case Scalar::Int64:
2608 case Scalar::Simd128:
2609 break;
2612 MOZ_CRASH("Unknown TypedArray type");
2615 /***
2616 *** JS impl
2617 ***/
2620 * TypedArrayObject boilerplate
2623 static const JSClassOps TypedArrayClassOps = {
2624 nullptr, // addProperty
2625 nullptr, // delProperty
2626 nullptr, // enumerate
2627 nullptr, // newEnumerate
2628 nullptr, // resolve
2629 nullptr, // mayResolve
2630 FixedLengthTypedArrayObject::finalize, // finalize
2631 nullptr, // call
2632 nullptr, // construct
2633 ArrayBufferViewObject::trace, // trace
2636 static const JSClassOps ResizableTypedArrayClassOps = {
2637 nullptr, // addProperty
2638 nullptr, // delProperty
2639 nullptr, // enumerate
2640 nullptr, // newEnumerate
2641 nullptr, // resolve
2642 nullptr, // mayResolve
2643 nullptr, // finalize
2644 nullptr, // call
2645 nullptr, // construct
2646 ArrayBufferViewObject::trace, // trace
2649 static const ClassExtension TypedArrayClassExtension = {
2650 FixedLengthTypedArrayObject::objectMoved, // objectMovedOp
2653 static const JSPropertySpec
2654 static_prototype_properties[Scalar::MaxTypedArrayViewType][2] = {
2655 #define IMPL_TYPED_ARRAY_PROPERTIES(ExternalType, NativeType, Name) \
2657 JS_INT32_PS("BYTES_PER_ELEMENT", \
2658 TypedArrayObjectTemplate<NativeType>::BYTES_PER_ELEMENT, \
2659 JSPROP_READONLY | JSPROP_PERMANENT), \
2660 JS_PS_END, \
2663 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_PROPERTIES)
2664 #undef IMPL_TYPED_ARRAY_PROPERTIES
2667 static const JSFunctionSpec uint8array_static_methods[] = {
2668 JS_FN("fromHex", uint8array_fromHex, 1, 0),
2669 JS_FS_END,
2672 static const JSFunctionSpec uint8array_methods[] = {
2673 JS_FN("setFromHex", uint8array_setFromHex, 1, 0),
2674 JS_FN("toHex", uint8array_toHex, 0, 0),
2675 JS_FS_END,
2678 static constexpr const JSFunctionSpec* TypedArrayStaticMethods(
2679 Scalar::Type type) {
2680 if (type == Scalar::Uint8) {
2681 return uint8array_static_methods;
2683 return nullptr;
2686 static constexpr const JSFunctionSpec* TypedArrayMethods(Scalar::Type type) {
2687 if (type == Scalar::Uint8) {
2688 return uint8array_methods;
2690 return nullptr;
2693 static const ClassSpec
2694 TypedArrayObjectClassSpecs[Scalar::MaxTypedArrayViewType] = {
2695 #define IMPL_TYPED_ARRAY_CLASS_SPEC(ExternalType, NativeType, Name) \
2697 TypedArrayObjectTemplate<NativeType>::createConstructor, \
2698 TypedArrayObjectTemplate<NativeType>::createPrototype, \
2699 TypedArrayStaticMethods(Scalar::Type::Name), \
2700 static_prototype_properties[Scalar::Type::Name], \
2701 TypedArrayMethods(Scalar::Type::Name), \
2702 static_prototype_properties[Scalar::Type::Name], \
2703 nullptr, \
2704 JSProto_TypedArray, \
2707 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS_SPEC)
2708 #undef IMPL_TYPED_ARRAY_CLASS_SPEC
2711 // Class definitions for fixed length and resizable typed arrays. Stored into a
2712 // 2-dimensional array to ensure the classes are in contiguous memory.
2713 const JSClass TypedArrayObject::anyClasses[2][Scalar::MaxTypedArrayViewType] = {
2714 // Class definitions for fixed length typed arrays.
2716 #define IMPL_TYPED_ARRAY_CLASS(ExternalType, NativeType, Name) \
2718 #Name "Array", \
2719 JSCLASS_HAS_RESERVED_SLOTS(TypedArrayObject::RESERVED_SLOTS) | \
2720 JSCLASS_HAS_CACHED_PROTO(JSProto_##Name##Array) | \
2721 JSCLASS_DELAY_METADATA_BUILDER | JSCLASS_SKIP_NURSERY_FINALIZE | \
2722 JSCLASS_BACKGROUND_FINALIZE, \
2723 &TypedArrayClassOps, \
2724 &TypedArrayObjectClassSpecs[Scalar::Type::Name], \
2725 &TypedArrayClassExtension, \
2728 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS)
2729 #undef IMPL_TYPED_ARRAY_CLASS
2732 // Class definitions for resizable typed arrays.
2734 #define IMPL_TYPED_ARRAY_CLASS(ExternalType, NativeType, Name) \
2736 #Name "Array", \
2737 JSCLASS_HAS_RESERVED_SLOTS(ResizableTypedArrayObject::RESERVED_SLOTS) | \
2738 JSCLASS_HAS_CACHED_PROTO(JSProto_##Name##Array) | \
2739 JSCLASS_DELAY_METADATA_BUILDER, \
2740 &ResizableTypedArrayClassOps, \
2741 &TypedArrayObjectClassSpecs[Scalar::Type::Name], \
2742 JS_NULL_CLASS_EXT, \
2745 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_CLASS)
2746 #undef IMPL_TYPED_ARRAY_CLASS
2750 const JSClass (
2751 &TypedArrayObject::fixedLengthClasses)[Scalar::MaxTypedArrayViewType] =
2752 TypedArrayObject::anyClasses[0];
2754 const JSClass (
2755 &TypedArrayObject::resizableClasses)[Scalar::MaxTypedArrayViewType] =
2756 TypedArrayObject::anyClasses[1];
2758 // The various typed array prototypes are supposed to 1) be normal objects,
2759 // 2) stringify to "[object <name of constructor>]", and 3) (Gecko-specific)
2760 // be xrayable. The first and second requirements mandate (in the absence of
2761 // @@toStringTag) a custom class. The third requirement mandates that each
2762 // prototype's class have the relevant typed array's cached JSProtoKey in them.
2763 // Thus we need one class with cached prototype per kind of typed array, with a
2764 // delegated ClassSpec.
2766 // Actually ({}).toString.call(Uint8Array.prototype) should throw, because
2767 // Uint8Array.prototype lacks the the typed array internal slots. (Same as
2768 // with %TypedArray%.prototype.) It's not clear this is desirable (see
2769 // above), but it's what we've always done, so keep doing it till we
2770 // implement @@toStringTag or ES6 changes.
2771 const JSClass TypedArrayObject::protoClasses[Scalar::MaxTypedArrayViewType] = {
2772 #define IMPL_TYPED_ARRAY_PROTO_CLASS(ExternalType, NativeType, Name) \
2774 #Name "Array.prototype", \
2775 JSCLASS_HAS_CACHED_PROTO(JSProto_##Name##Array), \
2776 JS_NULL_CLASS_OPS, \
2777 &TypedArrayObjectClassSpecs[Scalar::Type::Name], \
2780 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_PROTO_CLASS)
2781 #undef IMPL_TYPED_ARRAY_PROTO_CLASS
2784 /* static */
2785 bool TypedArrayObject::isOriginalLengthGetter(Native native) {
2786 return native == TypedArray_lengthGetter;
2789 /* static */
2790 bool TypedArrayObject::isOriginalByteOffsetGetter(Native native) {
2791 return native == TypedArray_byteOffsetGetter;
2794 /* static */
2795 bool TypedArrayObject::isOriginalByteLengthGetter(Native native) {
2796 return native == TypedArray_byteLengthGetter;
2799 bool js::IsTypedArrayConstructor(const JSObject* obj) {
2800 #define CHECK_TYPED_ARRAY_CONSTRUCTOR(_, T, N) \
2801 if (IsNativeFunction(obj, TypedArrayObjectTemplate<T>::class_constructor)) { \
2802 return true; \
2804 JS_FOR_EACH_TYPED_ARRAY(CHECK_TYPED_ARRAY_CONSTRUCTOR)
2805 #undef CHECK_TYPED_ARRAY_CONSTRUCTOR
2806 return false;
2809 bool js::IsTypedArrayConstructor(HandleValue v, Scalar::Type type) {
2810 return IsNativeFunction(v, TypedArrayConstructorNative(type));
2813 JSNative js::TypedArrayConstructorNative(Scalar::Type type) {
2814 #define TYPED_ARRAY_CONSTRUCTOR_NATIVE(_, T, N) \
2815 if (type == Scalar::N) { \
2816 return TypedArrayObjectTemplate<T>::class_constructor; \
2818 JS_FOR_EACH_TYPED_ARRAY(TYPED_ARRAY_CONSTRUCTOR_NATIVE)
2819 #undef TYPED_ARRAY_CONSTRUCTOR_NATIVE
2821 MOZ_CRASH("unexpected typed array type");
2824 bool js::IsBufferSource(JSObject* object, SharedMem<uint8_t*>* dataPointer,
2825 size_t* byteLength) {
2826 if (object->is<TypedArrayObject>()) {
2827 TypedArrayObject& view = object->as<TypedArrayObject>();
2828 *dataPointer = view.dataPointerEither().cast<uint8_t*>();
2829 *byteLength = view.byteLength().valueOr(0);
2830 return true;
2833 if (object->is<DataViewObject>()) {
2834 DataViewObject& view = object->as<DataViewObject>();
2835 *dataPointer = view.dataPointerEither().cast<uint8_t*>();
2836 *byteLength = view.byteLength().valueOr(0);
2837 return true;
2840 if (object->is<ArrayBufferObject>()) {
2841 ArrayBufferObject& buffer = object->as<ArrayBufferObject>();
2842 *dataPointer = buffer.dataPointerShared();
2843 *byteLength = buffer.byteLength();
2844 return true;
2847 if (object->is<SharedArrayBufferObject>()) {
2848 SharedArrayBufferObject& buffer = object->as<SharedArrayBufferObject>();
2849 *dataPointer = buffer.dataPointerShared();
2850 *byteLength = buffer.byteLength();
2851 return true;
2854 return false;
2857 template <typename CharT>
2858 static inline bool StringIsInfinity(mozilla::Range<const CharT> s) {
2859 static constexpr std::string_view Infinity = "Infinity";
2861 // Compilers optimize this to one |cmp| instruction on x64 resp. two for x86,
2862 // when the input is a Latin-1 string, because the string "Infinity" is
2863 // exactly eight characters long, so it can be represented as a single uint64
2864 // value.
2865 return s.length() == Infinity.length() &&
2866 EqualChars(s.begin().get(), Infinity.data(), Infinity.length());
2869 template <typename CharT>
2870 static inline bool StringIsNaN(mozilla::Range<const CharT> s) {
2871 static constexpr std::string_view NaN = "NaN";
2873 // "NaN" is not as nicely optimizable as "Infinity", but oh well.
2874 return s.length() == NaN.length() &&
2875 EqualChars(s.begin().get(), NaN.data(), NaN.length());
2878 template <typename CharT>
2879 static mozilla::Maybe<uint64_t> StringToTypedArrayIndexSlow(
2880 mozilla::Range<const CharT> s) {
2881 const mozilla::RangedPtr<const CharT> start = s.begin();
2882 const mozilla::RangedPtr<const CharT> end = s.end();
2884 const CharT* actualEnd;
2885 double result = js_strtod(start.get(), end.get(), &actualEnd);
2887 // The complete string must have been parsed.
2888 if (actualEnd != end.get()) {
2889 return mozilla::Nothing();
2892 // Now convert it back to a string.
2893 ToCStringBuf cbuf;
2894 size_t cstrlen;
2895 const char* cstr = js::NumberToCString(&cbuf, result, &cstrlen);
2896 MOZ_ASSERT(cstr);
2898 // Both strings must be equal for a canonical numeric index string.
2899 if (s.length() != cstrlen || !EqualChars(start.get(), cstr, cstrlen)) {
2900 return mozilla::Nothing();
2903 // Directly perform IsInteger() check and encode negative and non-integer
2904 // indices as OOB.
2905 // See 9.4.5.2 [[HasProperty]], steps 3.b.iii and 3.b.v.
2906 // See 9.4.5.3 [[DefineOwnProperty]], steps 3.b.i and 3.b.iii.
2907 // See 9.4.5.8 IntegerIndexedElementGet, steps 5 and 8.
2908 // See 9.4.5.9 IntegerIndexedElementSet, steps 6 and 9.
2909 if (result < 0 || !IsInteger(result)) {
2910 return mozilla::Some(UINT64_MAX);
2913 // Anything equals-or-larger than 2^53 is definitely OOB, encode it
2914 // accordingly so that the cast to uint64_t is well defined.
2915 if (result >= DOUBLE_INTEGRAL_PRECISION_LIMIT) {
2916 return mozilla::Some(UINT64_MAX);
2919 // The string is an actual canonical numeric index.
2920 return mozilla::Some(result);
2923 template <typename CharT>
2924 mozilla::Maybe<uint64_t> js::StringToTypedArrayIndex(
2925 mozilla::Range<const CharT> s) {
2926 mozilla::RangedPtr<const CharT> cp = s.begin();
2927 const mozilla::RangedPtr<const CharT> end = s.end();
2929 MOZ_ASSERT(cp < end, "caller must check for empty strings");
2931 bool negative = false;
2932 if (*cp == '-') {
2933 negative = true;
2934 if (++cp == end) {
2935 return mozilla::Nothing();
2939 if (!IsAsciiDigit(*cp)) {
2940 // Check for "NaN", "Infinity", or "-Infinity".
2941 if ((!negative && StringIsNaN<CharT>({cp, end})) ||
2942 StringIsInfinity<CharT>({cp, end})) {
2943 return mozilla::Some(UINT64_MAX);
2945 return mozilla::Nothing();
2948 uint32_t digit = AsciiDigitToNumber(*cp++);
2950 // Don't allow leading zeros.
2951 if (digit == 0 && cp != end) {
2952 // The string may be of the form "0.xyz". The exponent form isn't possible
2953 // when the string starts with "0".
2954 if (*cp == '.') {
2955 return StringToTypedArrayIndexSlow(s);
2957 return mozilla::Nothing();
2960 uint64_t index = digit;
2962 for (; cp < end; cp++) {
2963 if (!IsAsciiDigit(*cp)) {
2964 // Take the slow path when the string has fractional parts or an exponent.
2965 if (*cp == '.' || *cp == 'e') {
2966 return StringToTypedArrayIndexSlow(s);
2968 return mozilla::Nothing();
2971 digit = AsciiDigitToNumber(*cp);
2973 static_assert(
2974 uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT) < (UINT64_MAX - 10) / 10,
2975 "2^53 is way below UINT64_MAX, so |10 * index + digit| can't overflow");
2977 index = 10 * index + digit;
2979 // Also take the slow path when the string is larger-or-equals 2^53.
2980 if (index >= uint64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)) {
2981 return StringToTypedArrayIndexSlow(s);
2985 if (negative) {
2986 return mozilla::Some(UINT64_MAX);
2988 return mozilla::Some(index);
2991 template mozilla::Maybe<uint64_t> js::StringToTypedArrayIndex(
2992 mozilla::Range<const char16_t> s);
2994 template mozilla::Maybe<uint64_t> js::StringToTypedArrayIndex(
2995 mozilla::Range<const Latin1Char> s);
2997 bool js::SetTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj,
2998 uint64_t index, HandleValue v,
2999 ObjectOpResult& result) {
3000 switch (obj->type()) {
3001 #define SET_TYPED_ARRAY_ELEMENT(_, T, N) \
3002 case Scalar::N: \
3003 return TypedArrayObjectTemplate<T>::setElement(cx, obj, index, v, result);
3004 JS_FOR_EACH_TYPED_ARRAY(SET_TYPED_ARRAY_ELEMENT)
3005 #undef SET_TYPED_ARRAY_ELEMENT
3006 case Scalar::MaxTypedArrayViewType:
3007 case Scalar::Int64:
3008 case Scalar::Simd128:
3009 break;
3012 MOZ_CRASH("Unsupported TypedArray type");
3015 bool js::SetTypedArrayElementOutOfBounds(JSContext* cx,
3016 Handle<TypedArrayObject*> obj,
3017 uint64_t index, HandleValue v,
3018 ObjectOpResult& result) {
3019 // This method is only called for non-existent properties, which means any
3020 // absent indexed properties must be out of range. Unless the typed array is
3021 // backed by a growable SharedArrayBuffer, in which case another thread may
3022 // have grown the buffer.
3023 MOZ_ASSERT(index >= obj->length().valueOr(0) ||
3024 (obj->isSharedMemory() && obj->bufferShared()->isGrowable()));
3026 // The following steps refer to 10.4.5.16 TypedArraySetElement.
3028 // Steps 1-2.
3029 RootedValue converted(cx);
3030 if (!obj->convertValue(cx, v, &converted)) {
3031 return false;
3034 // Step 3.
3035 if (index < obj->length().valueOr(0)) {
3036 // Side-effects when converting the value may have put the index in-bounds
3037 // when the backing buffer is resizable.
3038 MOZ_ASSERT(obj->hasResizableBuffer());
3039 return SetTypedArrayElement(cx, obj, index, converted, result);
3042 // Step 4.
3043 return result.succeed();
3046 // ES2021 draft rev b3f9b5089bcc3ddd8486379015cd11eb1427a5eb
3047 // 9.4.5.3 [[DefineOwnProperty]], step 3.b.
3048 bool js::DefineTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj,
3049 uint64_t index,
3050 Handle<PropertyDescriptor> desc,
3051 ObjectOpResult& result) {
3052 // These are all substeps of 3.b.
3054 // Step i.
3055 if (index >= obj->length().valueOr(0)) {
3056 if (obj->hasDetachedBuffer()) {
3057 return result.fail(JSMSG_TYPED_ARRAY_DETACHED);
3059 return result.fail(JSMSG_DEFINE_BAD_INDEX);
3062 // Step ii.
3063 if (desc.isAccessorDescriptor()) {
3064 return result.fail(JSMSG_CANT_REDEFINE_PROP);
3067 // Step iii.
3068 if (desc.hasConfigurable() && !desc.configurable()) {
3069 return result.fail(JSMSG_CANT_REDEFINE_PROP);
3072 // Step iv.
3073 if (desc.hasEnumerable() && !desc.enumerable()) {
3074 return result.fail(JSMSG_CANT_REDEFINE_PROP);
3077 // Step v.
3078 if (desc.hasWritable() && !desc.writable()) {
3079 return result.fail(JSMSG_CANT_REDEFINE_PROP);
3082 // Step vi.
3083 if (desc.hasValue()) {
3084 switch (obj->type()) {
3085 #define DEFINE_TYPED_ARRAY_ELEMENT(_, T, N) \
3086 case Scalar::N: \
3087 return TypedArrayObjectTemplate<T>::setElement(cx, obj, index, \
3088 desc.value(), result);
3089 JS_FOR_EACH_TYPED_ARRAY(DEFINE_TYPED_ARRAY_ELEMENT)
3090 #undef DEFINE_TYPED_ARRAY_ELEMENT
3091 case Scalar::MaxTypedArrayViewType:
3092 case Scalar::Int64:
3093 case Scalar::Simd128:
3094 break;
3097 MOZ_CRASH("Unsupported TypedArray type");
3100 // Step vii.
3101 return result.succeed();
3104 template <typename T, typename U>
3105 static constexpr typename std::enable_if_t<std::is_unsigned_v<T>, U>
3106 UnsignedSortValue(U val) {
3107 return val;
3110 template <typename T, typename U>
3111 static constexpr
3112 typename std::enable_if_t<std::is_integral_v<T> && std::is_signed_v<T>, U>
3113 UnsignedSortValue(U val) {
3114 // Flip sign bit.
3115 return val ^ static_cast<U>(std::numeric_limits<T>::min());
3118 template <typename T, typename UnsignedT>
3119 static constexpr
3120 typename std::enable_if_t<std::is_floating_point_v<T>, UnsignedT>
3121 UnsignedSortValue(UnsignedT val) {
3122 // Flip sign bit for positive numbers; flip all bits for negative numbers,
3123 // except negative NaNs.
3124 using FloatingPoint = mozilla::FloatingPoint<T>;
3125 static_assert(std::is_same_v<typename FloatingPoint::Bits, UnsignedT>,
3126 "FloatingPoint::Bits matches the unsigned int representation");
3128 // FF80'0000 is negative infinity, (FF80'0000, FFFF'FFFF] are all NaNs with
3129 // the sign-bit set (and the equivalent holds for double values). So any value
3130 // larger than negative infinity is a negative NaN.
3131 constexpr UnsignedT NegativeInfinity =
3132 FloatingPoint::kSignBit | FloatingPoint::kExponentBits;
3133 if (val > NegativeInfinity) {
3134 return val;
3136 if (val & FloatingPoint::kSignBit) {
3137 return ~val;
3139 return val ^ FloatingPoint::kSignBit;
3142 template <typename T>
3143 static typename std::enable_if_t<std::is_integral_v<T> ||
3144 std::is_same_v<T, uint8_clamped>>
3145 TypedArrayStdSort(SharedMem<void*> data, size_t length) {
3146 T* unwrapped = data.cast<T*>().unwrapUnshared();
3147 std::sort(unwrapped, unwrapped + length);
3150 template <typename T>
3151 static typename std::enable_if_t<std::is_floating_point_v<T>> TypedArrayStdSort(
3152 SharedMem<void*> data, size_t length) {
3153 // Sort on the unsigned representation for performance reasons.
3154 using UnsignedT =
3155 typename mozilla::UnsignedStdintTypeForSize<sizeof(T)>::Type;
3156 UnsignedT* unwrapped = data.cast<UnsignedT*>().unwrapUnshared();
3157 std::sort(unwrapped, unwrapped + length, [](UnsignedT x, UnsignedT y) {
3158 constexpr auto SortValue = UnsignedSortValue<T, UnsignedT>;
3159 return SortValue(x) < SortValue(y);
3163 template <typename T, typename Ops>
3164 static typename std::enable_if_t<std::is_same_v<Ops, UnsharedOps>, bool>
3165 TypedArrayStdSort(JSContext* cx, TypedArrayObject* typedArray, size_t length) {
3166 TypedArrayStdSort<T>(typedArray->dataPointerEither(), length);
3167 return true;
3170 template <typename T, typename Ops>
3171 static typename std::enable_if_t<std::is_same_v<Ops, SharedOps>, bool>
3172 TypedArrayStdSort(JSContext* cx, TypedArrayObject* typedArray, size_t length) {
3173 // Always create a copy when sorting shared memory backed typed arrays to
3174 // ensure concurrent write accesses doesn't lead to UB when calling std::sort.
3175 auto ptr = cx->make_pod_array<T>(length);
3176 if (!ptr) {
3177 return false;
3179 SharedMem<T*> unshared = SharedMem<T*>::unshared(ptr.get());
3180 SharedMem<T*> data = typedArray->dataPointerShared().cast<T*>();
3182 Ops::podCopy(unshared, data, length);
3184 TypedArrayStdSort<T>(unshared.template cast<void*>(), length);
3186 Ops::podCopy(data, unshared, length);
3188 return true;
3191 template <typename T, typename Ops>
3192 static bool TypedArrayCountingSort(JSContext* cx, TypedArrayObject* typedArray,
3193 size_t length) {
3194 static_assert(std::is_integral_v<T> || std::is_same_v<T, uint8_clamped>,
3195 "Counting sort expects integral array elements");
3197 // Determined by performance testing.
3198 if (length <= 64) {
3199 return TypedArrayStdSort<T, Ops>(cx, typedArray, length);
3202 // Map signed values onto the unsigned range when storing in buffer.
3203 using UnsignedT =
3204 typename mozilla::UnsignedStdintTypeForSize<sizeof(T)>::Type;
3205 constexpr T min = std::numeric_limits<T>::min();
3207 constexpr size_t InlineStorage = sizeof(T) == 1 ? 256 : 0;
3208 Vector<size_t, InlineStorage> buffer(cx);
3209 if (!buffer.resize(size_t(std::numeric_limits<UnsignedT>::max()) + 1)) {
3210 return false;
3213 SharedMem<T*> data = typedArray->dataPointerEither().cast<T*>();
3215 // Populate the buffer.
3216 for (size_t i = 0; i < length; i++) {
3217 T val = Ops::load(data + i);
3218 buffer[UnsignedT(val - min)]++;
3221 // Traverse the buffer in order and write back elements to array.
3222 UnsignedT val = UnsignedT(-1); // intentional overflow on first increment
3223 for (size_t i = 0; i < length;) {
3224 // Invariant: sum(buffer[val:]) == length-i
3225 size_t j;
3226 do {
3227 j = buffer[++val];
3228 } while (j == 0);
3230 for (; j > 0; j--) {
3231 Ops::store(data + i++, T(val + min));
3235 return true;
3238 template <typename T, typename U, typename Ops>
3239 static void SortByColumn(SharedMem<U*> data, size_t length, SharedMem<U*> aux,
3240 uint8_t col) {
3241 static_assert(std::is_unsigned_v<U>, "SortByColumn sorts on unsigned values");
3242 static_assert(std::is_same_v<Ops, UnsharedOps>,
3243 "SortByColumn only works on unshared data");
3245 // |counts| is used to compute the starting index position for each key.
3246 // Letting counts[0] always be 0, simplifies the transform step below.
3247 // Example:
3249 // Computing frequency counts for the input [1 2 1] gives:
3250 // 0 1 2 3 ... (keys)
3251 // 0 0 2 1 (frequencies)
3253 // Transforming frequencies to indexes gives:
3254 // 0 1 2 3 ... (keys)
3255 // 0 0 2 3 (indexes)
3257 constexpr size_t R = 256;
3259 // Initialize all entries to zero.
3260 size_t counts[R + 1] = {};
3262 const auto ByteAtCol = [col](U x) {
3263 U y = UnsignedSortValue<T, U>(x);
3264 return static_cast<uint8_t>(y >> (col * 8));
3267 // Compute frequency counts.
3268 for (size_t i = 0; i < length; i++) {
3269 U val = Ops::load(data + i);
3270 uint8_t b = ByteAtCol(val);
3271 counts[b + 1]++;
3274 // Transform counts to indices.
3275 std::partial_sum(std::begin(counts), std::end(counts), std::begin(counts));
3277 // Distribute
3278 for (size_t i = 0; i < length; i++) {
3279 U val = Ops::load(data + i);
3280 uint8_t b = ByteAtCol(val);
3281 size_t j = counts[b]++;
3282 MOZ_ASSERT(j < length,
3283 "index is in bounds when |data| can't be modified concurrently");
3284 UnsharedOps::store(aux + j, val);
3287 // Copy back
3288 Ops::podCopy(data, aux, length);
3291 template <typename T, typename Ops>
3292 static bool TypedArrayRadixSort(JSContext* cx, TypedArrayObject* typedArray,
3293 size_t length) {
3294 // Determined by performance testing.
3295 constexpr size_t StdSortMinCutoff = sizeof(T) == 2 ? 64 : 256;
3297 // Radix sort uses O(n) additional space, limit this space to 64 MB.
3298 constexpr size_t StdSortMaxCutoff = (64 * 1024 * 1024) / sizeof(T);
3300 if (length <= StdSortMinCutoff || length >= StdSortMaxCutoff) {
3301 return TypedArrayStdSort<T, Ops>(cx, typedArray, length);
3304 if constexpr (sizeof(T) == 2) {
3305 // Radix sort uses O(n) additional space, so when |n| reaches 2^16, switch
3306 // over to counting sort to limit the additional space needed to 2^16.
3307 constexpr size_t CountingSortMaxCutoff = 65536;
3309 if (length >= CountingSortMaxCutoff) {
3310 return TypedArrayCountingSort<T, Ops>(cx, typedArray, length);
3314 using UnsignedT =
3315 typename mozilla::UnsignedStdintTypeForSize<sizeof(T)>::Type;
3317 auto ptr = cx->make_zeroed_pod_array<UnsignedT>(length);
3318 if (!ptr) {
3319 return false;
3321 SharedMem<UnsignedT*> aux = SharedMem<UnsignedT*>::unshared(ptr.get());
3323 SharedMem<UnsignedT*> data =
3324 typedArray->dataPointerEither().cast<UnsignedT*>();
3326 // Always create a copy when sorting shared memory backed typed arrays to
3327 // ensure concurrent write accesses don't lead to computing bad indices.
3328 SharedMem<UnsignedT*> unshared;
3329 SharedMem<UnsignedT*> shared;
3330 UniquePtr<UnsignedT[], JS::FreePolicy> ptrUnshared;
3331 if constexpr (std::is_same_v<Ops, SharedOps>) {
3332 ptrUnshared = cx->make_pod_array<UnsignedT>(length);
3333 if (!ptrUnshared) {
3334 return false;
3336 unshared = SharedMem<UnsignedT*>::unshared(ptrUnshared.get());
3337 shared = data;
3339 Ops::podCopy(unshared, shared, length);
3341 data = unshared;
3344 for (uint8_t col = 0; col < sizeof(UnsignedT); col++) {
3345 SortByColumn<T, UnsignedT, UnsharedOps>(data, length, aux, col);
3348 if constexpr (std::is_same_v<Ops, SharedOps>) {
3349 Ops::podCopy(shared, unshared, length);
3352 return true;
3355 using TypedArraySortFn = bool (*)(JSContext*, TypedArrayObject*, size_t length);
3357 template <typename T, typename Ops>
3358 static constexpr typename std::enable_if_t<sizeof(T) == 1, TypedArraySortFn>
3359 TypedArraySort() {
3360 return TypedArrayCountingSort<T, Ops>;
3363 template <typename T, typename Ops>
3364 static constexpr typename std::enable_if_t<sizeof(T) == 2 || sizeof(T) == 4,
3365 TypedArraySortFn>
3366 TypedArraySort() {
3367 return TypedArrayRadixSort<T, Ops>;
3370 template <typename T, typename Ops>
3371 static constexpr typename std::enable_if_t<sizeof(T) == 8, TypedArraySortFn>
3372 TypedArraySort() {
3373 return TypedArrayStdSort<T, Ops>;
3376 bool js::intrinsic_TypedArrayNativeSort(JSContext* cx, unsigned argc,
3377 Value* vp) {
3378 CallArgs args = CallArgsFromVp(argc, vp);
3379 MOZ_ASSERT(args.length() == 1);
3381 TypedArrayObject* typedArray =
3382 UnwrapAndDowncastValue<TypedArrayObject>(cx, args[0]);
3383 if (!typedArray) {
3384 return false;
3387 auto length = typedArray->length();
3388 MOZ_RELEASE_ASSERT(length,
3389 "TypedArray is neither detached nor out-of-bounds");
3391 bool isShared = typedArray->isSharedMemory();
3392 switch (typedArray->type()) {
3393 #define SORT(_, T, N) \
3394 case Scalar::N: \
3395 if (isShared) { \
3396 if (!TypedArraySort<T, SharedOps>()(cx, typedArray, *length)) { \
3397 return false; \
3399 } else { \
3400 if (!TypedArraySort<T, UnsharedOps>()(cx, typedArray, *length)) { \
3401 return false; \
3404 break;
3405 JS_FOR_EACH_TYPED_ARRAY(SORT)
3406 #undef SORT
3407 default:
3408 MOZ_CRASH("Unsupported TypedArray type");
3411 args.rval().set(args[0]);
3412 return true;
3415 /* JS Public API */
3417 #define IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(ExternalType, NativeType, Name) \
3418 JS_PUBLIC_API JSObject* JS_New##Name##Array(JSContext* cx, \
3419 size_t nelements) { \
3420 return TypedArrayObjectTemplate<NativeType>::fromLength(cx, nelements); \
3423 JS_PUBLIC_API JSObject* JS_New##Name##ArrayFromArray(JSContext* cx, \
3424 HandleObject other) { \
3425 return TypedArrayObjectTemplate<NativeType>::fromArray(cx, other); \
3428 JS_PUBLIC_API JSObject* JS_New##Name##ArrayWithBuffer( \
3429 JSContext* cx, HandleObject arrayBuffer, size_t byteOffset, \
3430 int64_t length) { \
3431 return TypedArrayObjectTemplate<NativeType>::fromBuffer( \
3432 cx, arrayBuffer, byteOffset, length); \
3435 JS_PUBLIC_API JSObject* js::Unwrap##Name##Array(JSObject* obj) { \
3436 obj = obj->maybeUnwrapIf<TypedArrayObject>(); \
3437 if (!obj) { \
3438 return nullptr; \
3440 const JSClass* clasp = obj->getClass(); \
3441 if (clasp != FixedLengthTypedArrayObjectTemplate< \
3442 NativeType>::instanceClass() && \
3443 clasp != \
3444 ResizableTypedArrayObjectTemplate<NativeType>::instanceClass()) { \
3445 return nullptr; \
3447 return obj; \
3450 JS_PUBLIC_API ExternalType* JS_Get##Name##ArrayLengthAndData( \
3451 JSObject* obj, size_t* length, bool* isSharedMemory, \
3452 const JS::AutoRequireNoGC& nogc) { \
3453 TypedArrayObject* tarr = obj->maybeUnwrapAs<TypedArrayObject>(); \
3454 if (!tarr) { \
3455 return nullptr; \
3457 mozilla::Span<ExternalType> span = \
3458 JS::TypedArray<JS::Scalar::Name>::fromObject(tarr).getData( \
3459 isSharedMemory, nogc); \
3460 *length = span.Length(); \
3461 return span.data(); \
3464 JS_PUBLIC_API ExternalType* JS_Get##Name##ArrayData( \
3465 JSObject* obj, bool* isSharedMemory, const JS::AutoRequireNoGC& nogc) { \
3466 size_t length; \
3467 return JS_Get##Name##ArrayLengthAndData(obj, &length, isSharedMemory, \
3468 nogc); \
3470 JS_PUBLIC_API JSObject* JS_GetObjectAs##Name##Array( \
3471 JSObject* obj, size_t* length, bool* isShared, ExternalType** data) { \
3472 obj = js::Unwrap##Name##Array(obj); \
3473 if (!obj) { \
3474 return nullptr; \
3476 TypedArrayObject* tarr = &obj->as<TypedArrayObject>(); \
3477 *length = tarr->length().valueOr(0); \
3478 *isShared = tarr->isSharedMemory(); \
3479 *data = static_cast<ExternalType*>(tarr->dataPointerEither().unwrap( \
3480 /*safe - caller sees isShared flag*/)); \
3481 return obj; \
3484 JS_FOR_EACH_TYPED_ARRAY(IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS)
3485 #undef IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS
3487 JS_PUBLIC_API bool JS_IsTypedArrayObject(JSObject* obj) {
3488 return obj->canUnwrapAs<TypedArrayObject>();
3491 JS_PUBLIC_API size_t JS_GetTypedArrayLength(JSObject* obj) {
3492 TypedArrayObject* tarr = obj->maybeUnwrapAs<TypedArrayObject>();
3493 if (!tarr) {
3494 return 0;
3496 return tarr->length().valueOr(0);
3499 JS_PUBLIC_API size_t JS_GetTypedArrayByteOffset(JSObject* obj) {
3500 TypedArrayObject* tarr = obj->maybeUnwrapAs<TypedArrayObject>();
3501 if (!tarr) {
3502 return 0;
3504 return tarr->byteOffset().valueOr(0);
3507 JS_PUBLIC_API size_t JS_GetTypedArrayByteLength(JSObject* obj) {
3508 TypedArrayObject* tarr = obj->maybeUnwrapAs<TypedArrayObject>();
3509 if (!tarr) {
3510 return 0;
3512 return tarr->byteLength().valueOr(0);
3515 JS_PUBLIC_API bool JS_GetTypedArraySharedness(JSObject* obj) {
3516 TypedArrayObject* tarr = obj->maybeUnwrapAs<TypedArrayObject>();
3517 if (!tarr) {
3518 return false;
3520 return tarr->isSharedMemory();
3523 JS_PUBLIC_API JS::Scalar::Type JS_GetArrayBufferViewType(JSObject* obj) {
3524 ArrayBufferViewObject* view = obj->maybeUnwrapAs<ArrayBufferViewObject>();
3525 if (!view) {
3526 return Scalar::MaxTypedArrayViewType;
3529 if (view->is<TypedArrayObject>()) {
3530 return view->as<TypedArrayObject>().type();
3532 if (view->is<DataViewObject>()) {
3533 return Scalar::MaxTypedArrayViewType;
3535 MOZ_CRASH("invalid ArrayBufferView type");
3538 JS_PUBLIC_API size_t JS_MaxMovableTypedArraySize() {
3539 return FixedLengthTypedArrayObject::INLINE_BUFFER_LIMIT;
3542 namespace JS {
3544 const JSClass* const TypedArray_base::fixedLengthClasses =
3545 TypedArrayObject::fixedLengthClasses;
3546 const JSClass* const TypedArray_base::resizableClasses =
3547 TypedArrayObject::resizableClasses;
3549 #define INSTANTIATE(ExternalType, NativeType, Name) \
3550 template class TypedArray<JS::Scalar::Name>;
3551 JS_FOR_EACH_TYPED_ARRAY(INSTANTIATE)
3552 #undef INSTANTIATE
3554 JS::ArrayBufferOrView JS::ArrayBufferOrView::unwrap(JSObject* maybeWrapped) {
3555 if (!maybeWrapped) {
3556 return JS::ArrayBufferOrView(nullptr);
3558 auto* ab = maybeWrapped->maybeUnwrapIf<ArrayBufferObjectMaybeShared>();
3559 if (ab) {
3560 return ArrayBufferOrView::fromObject(ab);
3563 return ArrayBufferView::unwrap(maybeWrapped);
3566 bool JS::ArrayBufferOrView::isDetached() const {
3567 MOZ_ASSERT(obj);
3568 if (obj->is<ArrayBufferObjectMaybeShared>()) {
3569 return obj->as<ArrayBufferObjectMaybeShared>().isDetached();
3570 } else {
3571 return obj->as<ArrayBufferViewObject>().hasDetachedBuffer();
3575 bool JS::ArrayBufferOrView::isResizable() const {
3576 MOZ_ASSERT(obj);
3577 if (obj->is<ArrayBufferObjectMaybeShared>()) {
3578 return obj->as<ArrayBufferObjectMaybeShared>().isResizable();
3579 } else {
3580 return obj->as<ArrayBufferViewObject>().hasResizableBuffer();
3584 JS::TypedArray_base JS::TypedArray_base::fromObject(JSObject* unwrapped) {
3585 if (unwrapped && unwrapped->is<TypedArrayObject>()) {
3586 return TypedArray_base(unwrapped);
3588 return TypedArray_base(nullptr);
3591 // Template getData function for TypedArrays, implemented here because
3592 // it requires internal APIs.
3593 template <JS::Scalar::Type EType>
3594 typename mozilla::Span<typename TypedArray<EType>::DataType>
3595 TypedArray<EType>::getData(bool* isSharedMemory, const AutoRequireNoGC&) {
3596 using ExternalType = TypedArray<EType>::DataType;
3597 if (!obj) {
3598 return nullptr;
3600 TypedArrayObject* tarr = &obj->as<TypedArrayObject>();
3601 MOZ_ASSERT(tarr);
3602 *isSharedMemory = tarr->isSharedMemory();
3603 return {static_cast<ExternalType*>(tarr->dataPointerEither().unwrap(
3604 /*safe - caller sees isShared*/)),
3605 tarr->length().valueOr(0)};
3608 // Force the method defined above to actually be instantianted in this
3609 // compilation unit and emitted into the object file, since otherwise a binary
3610 // could include the header file and emit an undefined symbol that would not be
3611 // satisfied by the linker. (This happens with opt gtest, at least. In a DEBUG
3612 // build, the header contains a call to this function so it will always be
3613 // emitted.)
3614 #define INSTANTIATE_GET_DATA(a, b, Name) \
3615 template mozilla::Span<typename TypedArray<JS::Scalar::Name>::DataType> \
3616 TypedArray<JS::Scalar::Name>::getData(bool* isSharedMemory, \
3617 const AutoRequireNoGC&);
3618 JS_FOR_EACH_TYPED_ARRAY(INSTANTIATE_GET_DATA)
3619 #undef INSTANTIATE_GET_DATA
3621 } /* namespace JS */