1// Copyright 2019 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include 'src/builtins/builtins-constructor-gen.h'
6
7namespace typed_array {
8  extern builtin IterableToListMayPreserveHoles(Context, Object, Callable):
9      JSArray;
10
11  extern macro TypedArrayBuiltinsAssembler::AllocateEmptyOnHeapBuffer(
12      implicit context: Context)(uintptr): JSArrayBuffer;
13  extern macro CodeStubAssembler::AllocateByteArray(uintptr): ByteArray;
14  extern macro TypedArrayBuiltinsAssembler::GetDefaultConstructor(
15      implicit context: Context)(JSTypedArray): JSFunction;
16  extern macro TypedArrayBuiltinsAssembler::SetupTypedArrayEmbedderFields(
17      JSTypedArray): void;
18
19  extern runtime ThrowInvalidTypedArrayAlignment(implicit context: Context)(
20      Map, String): never;
21
22  transitioning macro AllocateTypedArray(implicit context: Context)(
23      isOnHeap: constexpr bool, map: Map, buffer: JSArrayBuffer,
24      byteOffset: uintptr, byteLength: uintptr, length: uintptr): JSTypedArray {
25    let elements: ByteArray;
26    if constexpr (isOnHeap) {
27      elements = AllocateByteArray(byteLength);
28    } else {
29      elements = kEmptyByteArray;
30
31      // The max byteOffset is 8 * MaxSmi on the particular platform. 32 bit
32      // platforms are self-limiting, because we can't allocate an array bigger
33      // than our 32-bit arithmetic range anyway. 64 bit platforms could
34      // theoretically have an offset up to 2^35 - 1.
35      const backingStore: uintptr = Convert<uintptr>(buffer.backing_store);
36
37      // Assert no overflow has occurred. Only assert if the mock array buffer
38      // allocator is NOT used. When the mock array buffer is used, impossibly
39      // large allocations are allowed that would erroneously cause an overflow
40      // and this assertion to fail.
41      assert(
42          IsMockArrayBufferAllocatorFlag() ||
43          (backingStore + byteOffset) >= backingStore);
44    }
45
46    // We can't just build the new object with "new JSTypedArray" here because
47    // Torque doesn't know its full size including embedder fields, so use CSA
48    // for the allocation step.
49    const typedArray =
50        UnsafeCast<JSTypedArray>(AllocateFastOrSlowJSObjectFromMap(map));
51    typedArray.elements = elements;
52    typedArray.buffer = buffer;
53    typedArray.byte_offset = byteOffset;
54    typedArray.byte_length = byteLength;
55    typedArray.length = length;
56    if constexpr (isOnHeap) {
57      typed_array::SetJSTypedArrayOnHeapDataPtr(
58          typedArray, elements, byteOffset);
59    } else {
60      typed_array::SetJSTypedArrayOffHeapDataPtr(
61          typedArray, buffer.backing_store, byteOffset);
62      assert(
63          typedArray.data_ptr ==
64          (buffer.backing_store + Convert<intptr>(byteOffset)));
65    }
66    SetupTypedArrayEmbedderFields(typedArray);
67    return typedArray;
68  }
69
70  transitioning macro TypedArrayInitialize(implicit context: Context)(
71      initialize: constexpr bool, map: Map, length: uintptr,
72      elementsInfo: typed_array::TypedArrayElementsInfo,
73      bufferConstructor: JSReceiver): JSTypedArray labels IfRangeError {
74    const byteLength = elementsInfo.CalculateByteLength(length)
75        otherwise IfRangeError;
76    const byteLengthNum = Convert<Number>(byteLength);
77    const defaultConstructor = GetArrayBufferFunction();
78    const byteOffset: uintptr = 0;
79
80    try {
81      if (bufferConstructor != defaultConstructor) {
82        goto AttachOffHeapBuffer(ConstructWithTarget(
83            defaultConstructor, bufferConstructor, byteLengthNum));
84      }
85
86      if (byteLength > kMaxTypedArrayInHeap) goto AllocateOffHeap;
87
88      const buffer = AllocateEmptyOnHeapBuffer(byteLength);
89
90      const isOnHeap: constexpr bool = true;
91      const typedArray = AllocateTypedArray(
92          isOnHeap, map, buffer, byteOffset, byteLength, length);
93
94      if constexpr (initialize) {
95        const backingStore = typedArray.data_ptr;
96        typed_array::CallCMemset(backingStore, 0, byteLength);
97      }
98
99      return typedArray;
100    }
101    label AllocateOffHeap {
102      if constexpr (initialize) {
103        goto AttachOffHeapBuffer(Construct(defaultConstructor, byteLengthNum));
104      } else {
105        goto AttachOffHeapBuffer(Call(
106            context, GetArrayBufferNoInitFunction(), Undefined, byteLengthNum));
107      }
108    }
109    label AttachOffHeapBuffer(bufferObj: Object) {
110      const buffer = Cast<JSArrayBuffer>(bufferObj) otherwise unreachable;
111      const isOnHeap: constexpr bool = false;
112      return AllocateTypedArray(
113          isOnHeap, map, buffer, byteOffset, byteLength, length);
114    }
115  }
116
117  // 22.2.4.2 TypedArray ( length )
118  // ES #sec-typedarray-length
119  transitioning macro ConstructByLength(implicit context: Context)(
120      map: Map, lengthObj: JSAny,
121      elementsInfo: typed_array::TypedArrayElementsInfo): JSTypedArray {
122    try {
123      const length: uintptr = ToIndex(lengthObj) otherwise RangeError;
124      const defaultConstructor: Constructor = GetArrayBufferFunction();
125      const initialize: constexpr bool = true;
126      return TypedArrayInitialize(
127          initialize, map, length, elementsInfo, defaultConstructor)
128          otherwise RangeError;
129    }
130    label RangeError deferred {
131      ThrowRangeError(MessageTemplate::kInvalidTypedArrayLength, lengthObj);
132    }
133  }
134
135  // 22.2.4.4 TypedArray ( object )
136  // ES #sec-typedarray-object
137  transitioning macro ConstructByArrayLike(implicit context: Context)(
138      map: Map, arrayLike: HeapObject, length: uintptr,
139      elementsInfo: typed_array::TypedArrayElementsInfo,
140      bufferConstructor: JSReceiver): JSTypedArray {
141    try {
142      const initialize: constexpr bool = false;
143      const typedArray = TypedArrayInitialize(
144          initialize, map, length, elementsInfo, bufferConstructor)
145          otherwise RangeError;
146
147      try {
148        const src: JSTypedArray =
149            Cast<JSTypedArray>(arrayLike) otherwise IfSlow;
150
151        if (IsDetachedBuffer(src.buffer)) {
152          ThrowTypeError(MessageTemplate::kDetachedOperation, 'Construct');
153
154        } else if (src.elements_kind != elementsInfo.kind) {
155          goto IfSlow;
156
157        } else if (length > 0) {
158          const byteLength = typedArray.byte_length;
159          assert(byteLength <= kArrayBufferMaxByteLength);
160          typed_array::CallCMemcpy(
161              typedArray.data_ptr, src.data_ptr, byteLength);
162        }
163      }
164      label IfSlow deferred {
165        if (length > 0) {
166          TypedArrayCopyElements(
167              context, typedArray, arrayLike, Convert<Number>(length));
168        }
169      }
170      return typedArray;
171    }
172    label RangeError deferred {
173      ThrowRangeError(
174          MessageTemplate::kInvalidTypedArrayLength, Convert<Number>(length));
175    }
176  }
177
178  // 22.2.4.4 TypedArray ( object )
179  // ES #sec-typedarray-object
180  transitioning macro ConstructByIterable(implicit context: Context)(
181      iterable: JSReceiver, iteratorFn: Callable): never
182      labels IfConstructByArrayLike(JSArray, uintptr, JSReceiver) {
183    const array: JSArray =
184        IterableToListMayPreserveHoles(context, iterable, iteratorFn);
185    // Max JSArray length is a valid JSTypedArray length so we just use it.
186    goto IfConstructByArrayLike(
187        array, array.length_uintptr, GetArrayBufferFunction());
188  }
189
190  // 22.2.4.3 TypedArray ( typedArray )
191  // ES #sec-typedarray-typedarray
192  transitioning macro ConstructByTypedArray(implicit context: Context)(
193      srcTypedArray: JSTypedArray): never
194      labels IfConstructByArrayLike(JSTypedArray, uintptr, JSReceiver) {
195    let bufferConstructor: JSReceiver = GetArrayBufferFunction();
196    const srcBuffer: JSArrayBuffer = srcTypedArray.buffer;
197    // TODO(petermarshall): Throw on detached typedArray.
198    let length: uintptr =
199        IsDetachedBuffer(srcBuffer) ? 0 : srcTypedArray.length;
200
201    // The spec requires that constructing a typed array using a SAB-backed
202    // typed array use the ArrayBuffer constructor, not the species constructor.
203    // See https://tc39.github.io/ecma262/#sec-typedarray-typedarray.
204    if (!IsSharedArrayBuffer(srcBuffer)) {
205      bufferConstructor = SpeciesConstructor(srcBuffer, bufferConstructor);
206      // TODO(petermarshall): Throw on detached typedArray.
207      if (IsDetachedBuffer(srcBuffer)) length = 0;
208    }
209    goto IfConstructByArrayLike(srcTypedArray, length, bufferConstructor);
210  }
211
212  // 22.2.4.5 TypedArray ( buffer, byteOffset, length )
213  // ES #sec-typedarray-buffer-byteoffset-length
214  transitioning macro ConstructByArrayBuffer(implicit context: Context)(
215      map: Map, buffer: JSArrayBuffer, byteOffset: JSAny, length: JSAny,
216      elementsInfo: typed_array::TypedArrayElementsInfo): JSTypedArray {
217    try {
218      // 6. Let offset be ? ToIndex(byteOffset).
219      const offset: uintptr = ToIndex(byteOffset) otherwise IfInvalidOffset;
220
221      // 7. If offset modulo elementSize ≠ 0, throw a RangeError exception.
222      if (elementsInfo.IsUnaligned(offset)) {
223        goto IfInvalidAlignment('start offset');
224      }
225
226      // 8. If length is present and length is not undefined, then
227      // a. Let newLength be ? ToIndex(length).
228      let newLength: uintptr = ToIndex(length) otherwise IfInvalidLength;
229      let newByteLength: uintptr;
230
231      // 9. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
232      if (IsDetachedBuffer(buffer)) {
233        ThrowTypeError(MessageTemplate::kDetachedOperation, 'Construct');
234      }
235
236      // 10. Let bufferByteLength be buffer.[[ArrayBufferByteLength]].
237      const bufferByteLength: uintptr = buffer.byte_length;
238
239      // 11. If length is either not present or undefined, then
240      if (length == Undefined) {
241        // a. If bufferByteLength modulo elementSize ≠ 0, throw a RangeError
242        // exception.
243        if (elementsInfo.IsUnaligned(bufferByteLength)) {
244          goto IfInvalidAlignment('byte length');
245        }
246
247        // b. Let newByteLength be bufferByteLength - offset.
248        // c. If newByteLength < 0, throw a RangeError exception.
249        if (bufferByteLength < offset) goto IfInvalidOffset;
250
251        // Spec step 16 length calculated here to avoid recalculating the length
252        // in the step 12 branch.
253        newByteLength = bufferByteLength - offset;
254        newLength = elementsInfo.CalculateLength(newByteLength)
255            otherwise IfInvalidOffset;
256
257        // 12. Else,
258      } else {
259        // a. Let newByteLength be newLength × elementSize.
260        newByteLength = elementsInfo.CalculateByteLength(newLength)
261            otherwise IfInvalidLength;
262
263        // b. If offset + newByteLength > bufferByteLength, throw a RangeError
264        // exception.
265        if ((bufferByteLength < newByteLength) ||
266            (offset > bufferByteLength - newByteLength))
267          goto IfInvalidLength;
268      }
269
270      const isOnHeap: constexpr bool = false;
271      return AllocateTypedArray(
272          isOnHeap, map, buffer, offset, newByteLength, newLength);
273    }
274    label IfInvalidAlignment(problemString: String) deferred {
275      ThrowInvalidTypedArrayAlignment(map, problemString);
276    }
277    label IfInvalidLength deferred {
278      ThrowRangeError(MessageTemplate::kInvalidTypedArrayLength, length);
279    }
280    label IfInvalidOffset deferred {
281      ThrowRangeError(MessageTemplate::kInvalidOffset, byteOffset);
282    }
283  }
284
285  // 22.2.4.6 TypedArrayCreate ( constructor, argumentList )
286  // ES #typedarray-create
287  @export
288  transitioning macro TypedArrayCreateByLength(implicit context: Context)(
289      constructor: Constructor, length: Number, methodName: constexpr string):
290      JSTypedArray {
291    assert(IsSafeInteger(length));
292
293    // 1. Let newTypedArray be ? Construct(constructor, argumentList).
294    const newTypedArrayObj = Construct(constructor, length);
295
296    // 2. Perform ? ValidateTypedArray(newTypedArray).
297    //    ValidateTypedArray currently returns the array, not the ViewBuffer.
298    const newTypedArray: JSTypedArray =
299        ValidateTypedArray(context, newTypedArrayObj, methodName);
300
301    if (IsDetachedBuffer(newTypedArray.buffer)) deferred {
302        ThrowTypeError(MessageTemplate::kDetachedOperation, methodName);
303      }
304
305    // 3. If argumentList is a List of a single Number, then
306    //   a. If newTypedArray.[[ArrayLength]] < argumentList[0], throw a
307    //      TypeError exception.
308    if (newTypedArray.length < Convert<uintptr>(length)) deferred {
309        ThrowTypeError(MessageTemplate::kTypedArrayTooShort);
310      }
311
312    // 4. Return newTypedArray.
313    return newTypedArray;
314  }
315
316  transitioning macro ConstructByJSReceiver(implicit context:
317                                                Context)(obj: JSReceiver): never
318      labels IfConstructByArrayLike(JSReceiver, uintptr, JSReceiver) {
319    try {
320      // TODO(v8:8906): Use iterator::GetIteratorMethod() once it supports
321      // labels.
322      const iteratorMethod = GetMethod(obj, IteratorSymbolConstant())
323          otherwise IfIteratorUndefined, IfIteratorNotCallable;
324      ConstructByIterable(obj, iteratorMethod)
325          otherwise IfConstructByArrayLike;
326    }
327    label IfIteratorUndefined {
328      const lengthObj: JSAny = GetProperty(obj, kLengthString);
329      const lengthNumber: Number = ToLength_Inline(lengthObj);
330      // Throw RangeError here if the length does not fit in uintptr because
331      // such a length will not pass bounds checks in ConstructByArrayLike()
332      // anyway.
333      const length: uintptr = ChangeSafeIntegerNumberToUintPtr(lengthNumber)
334          otherwise goto IfInvalidLength(lengthNumber);
335      goto IfConstructByArrayLike(obj, length, GetArrayBufferFunction());
336    }
337    label IfInvalidLength(length: Number) {
338      ThrowRangeError(MessageTemplate::kInvalidTypedArrayLength, length);
339    }
340    label IfIteratorNotCallable(_value: JSAny) deferred {
341      ThrowTypeError(MessageTemplate::kIteratorSymbolNonCallable);
342    }
343  }
344
345  // 22.2.4 The TypedArray Constructors
346  // ES #sec-typedarray-constructors
347  transitioning builtin CreateTypedArray(
348      context: Context, target: JSFunction, newTarget: JSReceiver, arg1: JSAny,
349      arg2: JSAny, arg3: JSAny): JSTypedArray {
350    assert(IsConstructor(target));
351    // 4. Let O be ? AllocateTypedArray(constructorName, NewTarget,
352    // "%TypedArrayPrototype%").
353    const map = GetDerivedMap(target, newTarget);
354
355    // 5. Let elementSize be the Number value of the Element Size value in Table
356    // 56 for constructorName.
357    const elementsInfo = GetTypedArrayElementsInfo(map);
358
359    try {
360      typeswitch (arg1) {
361        case (length: Smi): {
362          goto IfConstructByLength(length);
363        }
364        case (buffer: JSArrayBuffer): {
365          return ConstructByArrayBuffer(map, buffer, arg2, arg3, elementsInfo);
366        }
367        case (typedArray: JSTypedArray): {
368          ConstructByTypedArray(typedArray) otherwise IfConstructByArrayLike;
369        }
370        case (obj: JSReceiver): {
371          ConstructByJSReceiver(obj) otherwise IfConstructByArrayLike;
372        }
373        // The first argument was a number or fell through and is treated as
374        // a number. https://tc39.github.io/ecma262/#sec-typedarray-length
375        case (lengthObj: JSAny): {
376          goto IfConstructByLength(lengthObj);
377        }
378      }
379    }
380    label IfConstructByLength(length: JSAny) {
381      return ConstructByLength(map, length, elementsInfo);
382    }
383    label IfConstructByArrayLike(
384        arrayLike: JSReceiver, length: uintptr, bufferConstructor: JSReceiver) {
385      return ConstructByArrayLike(
386          map, arrayLike, length, elementsInfo, bufferConstructor);
387    }
388  }
389
390  transitioning macro TypedArraySpeciesCreate(implicit context: Context)(
391      methodName: constexpr string, numArgs: constexpr int31,
392      exemplar: JSTypedArray, arg0: JSAny, arg1: JSAny,
393      arg2: JSAny): JSTypedArray {
394    const defaultConstructor = GetDefaultConstructor(exemplar);
395
396    try {
397      if (!IsPrototypeTypedArrayPrototype(exemplar.map)) goto IfSlow;
398      if (IsTypedArraySpeciesProtectorCellInvalid()) goto IfSlow;
399
400      const typedArray = CreateTypedArray(
401          context, defaultConstructor, defaultConstructor, arg0, arg1, arg2);
402
403      // It is assumed that the CreateTypedArray builtin does not produce a
404      // typed array that fails ValidateTypedArray
405      assert(!IsDetachedBuffer(typedArray.buffer));
406
407      return typedArray;
408    }
409    label IfSlow deferred {
410      const constructor =
411          Cast<Constructor>(SpeciesConstructor(exemplar, defaultConstructor))
412          otherwise unreachable;
413
414      // TODO(pwong): Simplify and remove numArgs when varargs are supported in
415      // macros.
416      let newObj: JSAny = Undefined;
417      if constexpr (numArgs == 1) {
418        newObj = Construct(constructor, arg0);
419      } else {
420        assert(numArgs == 3);
421        newObj = Construct(constructor, arg0, arg1, arg2);
422      }
423
424      return ValidateTypedArray(context, newObj, methodName);
425    }
426  }
427
428  @export
429  transitioning macro TypedArraySpeciesCreateByLength(implicit context:
430                                                          Context)(
431      methodName: constexpr string, exemplar: JSTypedArray, length: uintptr):
432      JSTypedArray {
433    const numArgs: constexpr int31 = 1;
434    // TODO(v8:4153): pass length further as uintptr.
435    const typedArray: JSTypedArray = TypedArraySpeciesCreate(
436        methodName, numArgs, exemplar, Convert<Number>(length), Undefined,
437        Undefined);
438    if (typedArray.length < length) deferred {
439        ThrowTypeError(MessageTemplate::kTypedArrayTooShort);
440      }
441    return typedArray;
442  }
443
444  transitioning macro TypedArraySpeciesCreateByBuffer(implicit context:
445                                                          Context)(
446      methodName: constexpr string, exemplar: JSTypedArray,
447      buffer: JSArrayBuffer, beginByteOffset: uintptr,
448      newLength: uintptr): JSTypedArray {
449    const numArgs: constexpr int31 = 3;
450    // TODO(v8:4153): pass length further as uintptr.
451    const typedArray: JSTypedArray = TypedArraySpeciesCreate(
452        methodName, numArgs, exemplar, buffer, Convert<Number>(beginByteOffset),
453        Convert<Number>(newLength));
454    return typedArray;
455  }
456}
457