Bug 1893651: Avoid repeated typed array length reads. r=jandem
[gecko.git] / js / src / vm / TypedArrayObject.h
blobe78ba4e05a23dd3ab4e3253b12d24b055f1c26d0
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 #ifndef vm_TypedArrayObject_h
8 #define vm_TypedArrayObject_h
10 #include "mozilla/Maybe.h"
11 #include "mozilla/TextUtils.h"
13 #include "gc/AllocKind.h"
14 #include "gc/MaybeRooted.h"
15 #include "js/Class.h"
16 #include "js/experimental/TypedData.h" // js::detail::TypedArrayLengthSlot
17 #include "js/ScalarType.h" // js::Scalar::Type
18 #include "vm/ArrayBufferObject.h"
19 #include "vm/ArrayBufferViewObject.h"
20 #include "vm/JSObject.h"
21 #include "vm/SharedArrayObject.h"
23 namespace js {
26 * TypedArrayObject
28 * The non-templated base class for the specific typed implementations.
29 * This class holds all the member variables that are used by
30 * the subclasses.
33 class TypedArrayObject : public ArrayBufferViewObject {
34 public:
35 static_assert(js::detail::TypedArrayLengthSlot == LENGTH_SLOT,
36 "bad inlined constant in TypedData.h");
37 static_assert(js::detail::TypedArrayDataSlot == DATA_SLOT,
38 "bad inlined constant in TypedData.h");
40 static bool sameBuffer(Handle<TypedArrayObject*> a,
41 Handle<TypedArrayObject*> b) {
42 // Inline buffers.
43 if (!a->hasBuffer() || !b->hasBuffer()) {
44 return a.get() == b.get();
47 // Shared buffers.
48 if (a->isSharedMemory() && b->isSharedMemory()) {
49 return a->bufferShared()->globalID() == b->bufferShared()->globalID();
52 return a->bufferEither() == b->bufferEither();
55 static const JSClass anyClasses[2][Scalar::MaxTypedArrayViewType];
56 static const JSClass (&fixedLengthClasses)[Scalar::MaxTypedArrayViewType];
57 static const JSClass (&resizableClasses)[Scalar::MaxTypedArrayViewType];
58 static const JSClass protoClasses[Scalar::MaxTypedArrayViewType];
59 static const JSClass sharedTypedArrayPrototypeClass;
61 static const JSClass* protoClassForType(Scalar::Type type) {
62 MOZ_ASSERT(type < Scalar::MaxTypedArrayViewType);
63 return &protoClasses[type];
66 inline Scalar::Type type() const;
67 inline size_t bytesPerElement() const;
69 static bool ensureHasBuffer(JSContext* cx,
70 Handle<TypedArrayObject*> typedArray);
72 public:
73 /**
74 * Return the current length, or |Nothing| if the TypedArray is detached or
75 * out-of-bounds.
77 mozilla::Maybe<size_t> length() const {
78 return ArrayBufferViewObject::length();
81 /**
82 * Return the current byteLength, or |Nothing| if the TypedArray is detached
83 * or out-of-bounds.
85 mozilla::Maybe<size_t> byteLength() const {
86 return length().map(
87 [this](size_t value) { return value * bytesPerElement(); });
90 // Self-hosted TypedArraySubarray function needs to read [[ByteOffset]], even
91 // when it's currently out-of-bounds.
92 size_t byteOffsetMaybeOutOfBounds() const {
93 // dataPointerOffset() returns the [[ByteOffset]] spec value, except when
94 // the buffer is detached. (bug 1840991)
95 return ArrayBufferViewObject::dataPointerOffset();
98 template <AllowGC allowGC>
99 bool getElement(JSContext* cx, size_t index,
100 typename MaybeRooted<Value, allowGC>::MutableHandleType val);
101 bool getElementPure(size_t index, Value* vp);
104 * Copy |length| elements from this typed array to vp. vp must point to rooted
105 * memory. |length| must not exceed the typed array's current length.
107 static bool getElements(JSContext* cx, Handle<TypedArrayObject*> tarray,
108 size_t length, Value* vp);
110 static bool GetTemplateObjectForNative(JSContext* cx, Native native,
111 const JS::HandleValueArray args,
112 MutableHandleObject res);
114 // Maximum allowed byte length for any typed array.
115 static constexpr size_t ByteLengthLimit = ArrayBufferObject::ByteLengthLimit;
117 static bool isOriginalLengthGetter(Native native);
119 static bool isOriginalByteOffsetGetter(Native native);
121 static bool isOriginalByteLengthGetter(Native native);
123 /* Initialization bits */
125 static const JSFunctionSpec protoFunctions[];
126 static const JSPropertySpec protoAccessors[];
127 static const JSFunctionSpec staticFunctions[];
128 static const JSPropertySpec staticProperties[];
130 /* Accessors and functions */
132 static bool set(JSContext* cx, unsigned argc, Value* vp);
133 static bool copyWithin(JSContext* cx, unsigned argc, Value* vp);
135 bool convertValue(JSContext* cx, HandleValue v,
136 MutableHandleValue result) const;
138 private:
139 static bool set_impl(JSContext* cx, const CallArgs& args);
140 static bool copyWithin_impl(JSContext* cx, const CallArgs& args);
143 class FixedLengthTypedArrayObject : public TypedArrayObject {
144 public:
145 static constexpr size_t FIXED_DATA_START = RESERVED_SLOTS;
147 // For typed arrays which can store their data inline, the array buffer
148 // object is created lazily.
149 static constexpr uint32_t INLINE_BUFFER_LIMIT =
150 (NativeObject::MAX_FIXED_SLOTS - FIXED_DATA_START) * sizeof(Value);
152 inline gc::AllocKind allocKindForTenure() const;
153 static inline gc::AllocKind AllocKindForLazyBuffer(size_t nbytes);
155 size_t byteOffset() const {
156 return ArrayBufferViewObject::byteOffsetSlotValue();
159 size_t byteLength() const { return length() * bytesPerElement(); }
161 size_t length() const { return ArrayBufferViewObject::lengthSlotValue(); }
163 bool hasInlineElements() const;
164 void setInlineElements();
165 uint8_t* elementsRaw() const {
166 return maybePtrFromReservedSlot<uint8_t>(DATA_SLOT);
168 uint8_t* elements() const {
169 assertZeroLengthArrayData();
170 return elementsRaw();
173 #ifdef DEBUG
174 void assertZeroLengthArrayData() const;
175 #else
176 void assertZeroLengthArrayData() const {};
177 #endif
179 static void finalize(JS::GCContext* gcx, JSObject* obj);
180 static size_t objectMoved(JSObject* obj, JSObject* old);
183 class ResizableTypedArrayObject : public TypedArrayObject {
184 public:
185 static const uint8_t RESERVED_SLOTS = RESIZABLE_RESERVED_SLOTS;
188 extern TypedArrayObject* NewTypedArrayWithTemplateAndLength(
189 JSContext* cx, HandleObject templateObj, int32_t len);
191 extern TypedArrayObject* NewTypedArrayWithTemplateAndArray(
192 JSContext* cx, HandleObject templateObj, HandleObject array);
194 extern TypedArrayObject* NewTypedArrayWithTemplateAndBuffer(
195 JSContext* cx, HandleObject templateObj, HandleObject arrayBuffer,
196 HandleValue byteOffset, HandleValue length);
198 extern TypedArrayObject* NewUint8ArrayWithLength(
199 JSContext* cx, int32_t len, gc::Heap heap = gc::Heap::Default);
201 inline bool IsFixedLengthTypedArrayClass(const JSClass* clasp) {
202 return std::begin(TypedArrayObject::fixedLengthClasses) <= clasp &&
203 clasp < std::end(TypedArrayObject::fixedLengthClasses);
206 inline bool IsResizableTypedArrayClass(const JSClass* clasp) {
207 return std::begin(TypedArrayObject::resizableClasses) <= clasp &&
208 clasp < std::end(TypedArrayObject::resizableClasses);
211 inline bool IsTypedArrayClass(const JSClass* clasp) {
212 MOZ_ASSERT(std::end(TypedArrayObject::fixedLengthClasses) ==
213 std::begin(TypedArrayObject::resizableClasses),
214 "TypedArray classes are in contiguous memory");
215 return std::begin(TypedArrayObject::fixedLengthClasses) <= clasp &&
216 clasp < std::end(TypedArrayObject::resizableClasses);
219 inline Scalar::Type GetTypedArrayClassType(const JSClass* clasp) {
220 MOZ_ASSERT(IsTypedArrayClass(clasp));
221 if (clasp < std::end(TypedArrayObject::fixedLengthClasses)) {
222 return static_cast<Scalar::Type>(clasp -
223 &TypedArrayObject::fixedLengthClasses[0]);
225 return static_cast<Scalar::Type>(clasp -
226 &TypedArrayObject::resizableClasses[0]);
229 bool IsTypedArrayConstructor(const JSObject* obj);
231 bool IsTypedArrayConstructor(HandleValue v, Scalar::Type type);
233 JSNative TypedArrayConstructorNative(Scalar::Type type);
235 // In WebIDL terminology, a BufferSource is either an ArrayBuffer or a typed
236 // array view. In either case, extract the dataPointer/byteLength.
237 bool IsBufferSource(JSObject* object, SharedMem<uint8_t*>* dataPointer,
238 size_t* byteLength);
240 inline Scalar::Type TypedArrayObject::type() const {
241 return GetTypedArrayClassType(getClass());
244 inline size_t TypedArrayObject::bytesPerElement() const {
245 return Scalar::byteSize(type());
248 // ES2020 draft rev a5375bdad264c8aa264d9c44f57408087761069e
249 // 7.1.16 CanonicalNumericIndexString
251 // Checks whether or not the string is a canonical numeric index string. If the
252 // string is a canonical numeric index which is not representable as a uint64_t,
253 // the returned index is UINT64_MAX.
254 template <typename CharT>
255 mozilla::Maybe<uint64_t> StringToTypedArrayIndex(mozilla::Range<const CharT> s);
257 // A string |s| is a TypedArray index (or: canonical numeric index string) iff
258 // |s| is "-0" or |SameValue(ToString(ToNumber(s)), s)| is true. So check for
259 // any characters which can start the string representation of a number,
260 // including "NaN" and "Infinity".
261 template <typename CharT>
262 inline bool CanStartTypedArrayIndex(CharT ch) {
263 return mozilla::IsAsciiDigit(ch) || ch == '-' || ch == 'N' || ch == 'I';
266 [[nodiscard]] inline mozilla::Maybe<uint64_t> ToTypedArrayIndex(jsid id) {
267 if (id.isInt()) {
268 int32_t i = id.toInt();
269 MOZ_ASSERT(i >= 0);
270 return mozilla::Some(i);
273 if (MOZ_UNLIKELY(!id.isString())) {
274 return mozilla::Nothing();
277 JS::AutoCheckCannotGC nogc;
278 JSAtom* atom = id.toAtom();
280 if (atom->empty() || !CanStartTypedArrayIndex(atom->latin1OrTwoByteChar(0))) {
281 return mozilla::Nothing();
284 if (atom->hasLatin1Chars()) {
285 mozilla::Range<const Latin1Char> chars = atom->latin1Range(nogc);
286 return StringToTypedArrayIndex(chars);
289 mozilla::Range<const char16_t> chars = atom->twoByteRange(nogc);
290 return StringToTypedArrayIndex(chars);
293 bool SetTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj,
294 uint64_t index, HandleValue v,
295 ObjectOpResult& result);
297 bool SetTypedArrayElementOutOfBounds(JSContext* cx,
298 Handle<TypedArrayObject*> obj,
299 uint64_t index, HandleValue v,
300 ObjectOpResult& result);
303 * Implements [[DefineOwnProperty]] for TypedArrays when the property
304 * key is a TypedArray index.
306 bool DefineTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj,
307 uint64_t index, Handle<PropertyDescriptor> desc,
308 ObjectOpResult& result);
310 // Sort a typed array in ascending order. The typed array may be wrapped, but
311 // must not be detached.
312 bool intrinsic_TypedArrayNativeSort(JSContext* cx, unsigned argc, Value* vp);
314 static inline constexpr unsigned TypedArrayShift(Scalar::Type viewType) {
315 switch (viewType) {
316 case Scalar::Int8:
317 case Scalar::Uint8:
318 case Scalar::Uint8Clamped:
319 return 0;
320 case Scalar::Int16:
321 case Scalar::Uint16:
322 case Scalar::Float16:
323 return 1;
324 case Scalar::Int32:
325 case Scalar::Uint32:
326 case Scalar::Float32:
327 return 2;
328 case Scalar::BigInt64:
329 case Scalar::BigUint64:
330 case Scalar::Int64:
331 case Scalar::Float64:
332 return 3;
333 default:
334 MOZ_CRASH("Unexpected array type");
338 static inline constexpr unsigned TypedArrayElemSize(Scalar::Type viewType) {
339 return 1u << TypedArrayShift(viewType);
342 } // namespace js
344 template <>
345 inline bool JSObject::is<js::TypedArrayObject>() const {
346 return js::IsTypedArrayClass(getClass());
349 template <>
350 inline bool JSObject::is<js::FixedLengthTypedArrayObject>() const {
351 return js::IsFixedLengthTypedArrayClass(getClass());
354 template <>
355 inline bool JSObject::is<js::ResizableTypedArrayObject>() const {
356 return js::IsResizableTypedArrayClass(getClass());
359 #endif /* vm_TypedArrayObject_h */