| // Copyright 2019 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include 'src/builtins/builtins-constructor-gen.h' |
| |
| namespace typed_array_createtypedarray { |
| extern builtin IterableToListMayPreserveHoles(Context, Object, Callable): |
| JSArray; |
| |
| extern macro TypedArrayBuiltinsAssembler::AllocateEmptyOnHeapBuffer( |
| implicit context: Context)(uintptr): JSArrayBuffer; |
| extern macro CodeStubAssembler::AllocateByteArray(uintptr): ByteArray; |
| extern macro TypedArrayBuiltinsAssembler::GetDefaultConstructor( |
| implicit context: Context)(JSTypedArray): JSFunction; |
| extern macro TypedArrayBuiltinsAssembler::IsSharedArrayBuffer(JSArrayBuffer): |
| bool; |
| extern macro TypedArrayBuiltinsAssembler::SetupTypedArrayEmbedderFields( |
| JSTypedArray): void; |
| |
| extern runtime ThrowInvalidTypedArrayAlignment(implicit context: Context)( |
| Map, String): never; |
| extern runtime TypedArrayCopyElements(Context, JSTypedArray, Object, Number): |
| void; |
| |
| transitioning macro AllocateTypedArray(implicit context: Context)( |
| isOnHeap: constexpr bool, map: Map, buffer: JSArrayBuffer, |
| byteOffset: uintptr, byteLength: uintptr, length: uintptr): JSTypedArray { |
| let elements: ByteArray; |
| let externalPointer: RawPtr; |
| let basePointer: ByteArray | Smi; |
| if constexpr (isOnHeap) { |
| elements = AllocateByteArray(byteLength); |
| basePointer = elements; |
| externalPointer = PointerConstant(kExternalPointerForOnHeapArray); |
| } else { |
| basePointer = Convert<Smi>(0); |
| |
| // The max byteOffset is 8 * MaxSmi on the particular platform. 32 bit |
| // platforms are self-limiting, because we can't allocate an array bigger |
| // than our 32-bit arithmetic range anyway. 64 bit platforms could |
| // theoretically have an offset up to 2^35 - 1. |
| const backingStore: RawPtr = buffer.backing_store; |
| externalPointer = backingStore + Convert<intptr>(byteOffset); |
| |
| // Assert no overflow has occurred. Only assert if the mock array buffer |
| // allocator is NOT used. When the mock array buffer is used, impossibly |
| // large allocations are allowed that would erroneously cause an overflow |
| // and this assertion to fail. |
| assert( |
| IsMockArrayBufferAllocatorFlag() || |
| Convert<uintptr>(externalPointer) >= Convert<uintptr>(backingStore)); |
| |
| elements = kEmptyByteArray; |
| } |
| |
| // We can't just build the new object with "new JSTypedArray" here because |
| // Torque doesn't know its full size including embedder fields, so use CSA |
| // for the allocation step. |
| const typedArray = |
| UnsafeCast<JSTypedArray>(AllocateFastOrSlowJSObjectFromMap(map)); |
| typedArray.elements = elements; |
| typedArray.buffer = buffer; |
| typedArray.byte_offset = byteOffset; |
| typedArray.byte_length = byteLength; |
| typedArray.length = length; |
| typedArray.external_pointer = externalPointer; |
| typedArray.base_pointer = basePointer; |
| SetupTypedArrayEmbedderFields(typedArray); |
| return typedArray; |
| } |
| |
| transitioning macro TypedArrayInitialize(implicit context: Context)( |
| initialize: constexpr bool, map: Map, length: PositiveSmi, |
| elementsInfo: typed_array::TypedArrayElementsInfo, |
| bufferConstructor: JSReceiver): JSTypedArray { |
| const byteLength = elementsInfo.CalculateByteLength(length) |
| otherwise ThrowRangeError(kInvalidArrayBufferLength); |
| const byteLengthNum = Convert<Number>(byteLength); |
| const defaultConstructor = GetArrayBufferFunction(); |
| const byteOffset: uintptr = 0; |
| |
| try { |
| if (bufferConstructor != defaultConstructor) { |
| goto AttachOffHeapBuffer(ConstructWithTarget( |
| defaultConstructor, bufferConstructor, byteLengthNum)); |
| } |
| |
| if (byteLength > kMaxTypedArrayInHeap) goto AllocateOffHeap; |
| |
| const buffer = AllocateEmptyOnHeapBuffer(byteLength); |
| |
| const isOnHeap: constexpr bool = true; |
| const typedArray = AllocateTypedArray( |
| isOnHeap, map, buffer, byteOffset, byteLength, |
| Convert<uintptr>(length)); |
| |
| if constexpr (initialize) { |
| const backingStore = typedArray.data_ptr; |
| typed_array::CallCMemset(backingStore, 0, byteLength); |
| } |
| |
| return typedArray; |
| } |
| label AllocateOffHeap { |
| if constexpr (initialize) { |
| goto AttachOffHeapBuffer(Construct(defaultConstructor, byteLengthNum)); |
| } else { |
| goto AttachOffHeapBuffer(Call( |
| context, GetArrayBufferNoInitFunction(), Undefined, byteLengthNum)); |
| } |
| } |
| label AttachOffHeapBuffer(bufferObj: Object) { |
| const buffer = Cast<JSArrayBuffer>(bufferObj) otherwise unreachable; |
| const isOnHeap: constexpr bool = false; |
| return AllocateTypedArray( |
| isOnHeap, map, buffer, byteOffset, byteLength, |
| Convert<uintptr>(length)); |
| } |
| } |
| |
| // 22.2.4.2 TypedArray ( length ) |
| // ES #sec-typedarray-length |
| transitioning macro ConstructByLength(implicit context: Context)( |
| map: Map, length: Object, |
| elementsInfo: typed_array::TypedArrayElementsInfo): JSTypedArray { |
| const convertedLength: Number = |
| ToInteger_Inline(context, length, kTruncateMinusZero); |
| // The maximum length of a TypedArray is MaxSmi(). |
| // Note: this is not per spec, but rather a constraint of our current |
| // representation (which uses Smis). |
| // TODO(7881): support larger-than-smi typed array lengths |
| const positiveLength: PositiveSmi = Cast<PositiveSmi>(convertedLength) |
| otherwise ThrowRangeError(kInvalidTypedArrayLength, length); |
| const defaultConstructor: Constructor = GetArrayBufferFunction(); |
| const initialize: constexpr bool = true; |
| return TypedArrayInitialize( |
| initialize, map, positiveLength, elementsInfo, defaultConstructor); |
| } |
| |
| // 22.2.4.4 TypedArray ( object ) |
| // ES #sec-typedarray-object |
| transitioning macro ConstructByArrayLike(implicit context: Context)( |
| map: Map, arrayLike: HeapObject, initialLength: Object, |
| elementsInfo: typed_array::TypedArrayElementsInfo, |
| bufferConstructor: JSReceiver): JSTypedArray { |
| // The caller has looked up length on arrayLike, which is observable. |
| const length: PositiveSmi = ToSmiLength(initialLength) |
| otherwise ThrowRangeError(kInvalidTypedArrayLength, initialLength); |
| const initialize: constexpr bool = false; |
| const typedArray = TypedArrayInitialize( |
| initialize, map, length, elementsInfo, bufferConstructor); |
| |
| try { |
| const src: JSTypedArray = Cast<JSTypedArray>(arrayLike) otherwise IfSlow; |
| |
| if (IsDetachedBuffer(src.buffer)) { |
| ThrowTypeError(kDetachedOperation, 'Construct'); |
| |
| } else if (src.elements_kind != elementsInfo.kind) { |
| goto IfSlow; |
| |
| } else if (length > 0) { |
| const byteLength = typedArray.byte_length; |
| assert(byteLength <= kArrayBufferMaxByteLength); |
| typed_array::CallCMemcpy(typedArray.data_ptr, src.data_ptr, byteLength); |
| } |
| } |
| label IfSlow deferred { |
| if (length > 0) { |
| TypedArrayCopyElements(context, typedArray, arrayLike, length); |
| } |
| } |
| return typedArray; |
| } |
| |
| // 22.2.4.4 TypedArray ( object ) |
| // ES #sec-typedarray-object |
| transitioning macro ConstructByIterable(implicit context: Context)( |
| iterable: JSReceiver, iteratorFn: Callable): never |
| labels IfConstructByArrayLike(HeapObject, Object, JSReceiver) { |
| const array: JSArray = |
| IterableToListMayPreserveHoles(context, iterable, iteratorFn); |
| goto IfConstructByArrayLike(array, array.length, GetArrayBufferFunction()); |
| } |
| |
| // 22.2.4.3 TypedArray ( typedArray ) |
| // ES #sec-typedarray-typedarray |
| transitioning macro ConstructByTypedArray(implicit context: Context)( |
| srcTypedArray: JSTypedArray): never |
| labels IfConstructByArrayLike(HeapObject, Object, JSReceiver) { |
| let bufferConstructor: JSReceiver = GetArrayBufferFunction(); |
| const srcBuffer: JSArrayBuffer = srcTypedArray.buffer; |
| // TODO(petermarshall): Throw on detached typedArray. |
| // TODO(v8:4156): Update this to support huge TypedArrays. |
| let length = |
| IsDetachedBuffer(srcBuffer) ? 0 : Convert<Number>(srcTypedArray.length); |
| |
| // The spec requires that constructing a typed array using a SAB-backed |
| // typed array use the ArrayBuffer constructor, not the species constructor. |
| // See https://tc39.github.io/ecma262/#sec-typedarray-typedarray. |
| if (!IsSharedArrayBuffer(srcBuffer)) { |
| bufferConstructor = SpeciesConstructor(srcBuffer, bufferConstructor); |
| // TODO(petermarshall): Throw on detached typedArray. |
| if (IsDetachedBuffer(srcBuffer)) length = 0; |
| } |
| goto IfConstructByArrayLike(srcTypedArray, length, bufferConstructor); |
| } |
| |
| // 22.2.4.5 TypedArray ( buffer, byteOffset, length ) |
| // ES #sec-typedarray-buffer-byteoffset-length |
| transitioning macro ConstructByArrayBuffer(implicit context: Context)( |
| map: Map, buffer: JSArrayBuffer, byteOffset: Object, length: Object, |
| elementsInfo: typed_array::TypedArrayElementsInfo): JSTypedArray { |
| try { |
| let offset: uintptr = 0; |
| if (byteOffset != Undefined) { |
| // 6. Let offset be ? ToIndex(byteOffset). |
| offset = TryNumberToUintPtr( |
| ToInteger_Inline(context, byteOffset, kTruncateMinusZero)) |
| otherwise goto IfInvalidOffset; |
| |
| // 7. If offset modulo elementSize ≠ 0, throw a RangeError exception. |
| if (elementsInfo.IsUnaligned(offset)) { |
| goto IfInvalidAlignment('start offset'); |
| } |
| } |
| |
| let newLength: PositiveSmi = 0; |
| let newByteLength: uintptr; |
| // 8. If length is present and length is not undefined, then |
| if (length != Undefined) { |
| // a. Let newLength be ? ToIndex(length). |
| newLength = ToSmiIndex(length) otherwise IfInvalidLength; |
| } |
| |
| // 9. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. |
| if (IsDetachedBuffer(buffer)) { |
| ThrowTypeError(kDetachedOperation, 'Construct'); |
| } |
| |
| // 10. Let bufferByteLength be buffer.[[ArrayBufferByteLength]]. |
| const bufferByteLength: uintptr = buffer.byte_length; |
| |
| // 11. If length is either not present or undefined, then |
| if (length == Undefined) { |
| // a. If bufferByteLength modulo elementSize ≠ 0, throw a RangeError |
| // exception. |
| if (elementsInfo.IsUnaligned(bufferByteLength)) { |
| goto IfInvalidAlignment('byte length'); |
| } |
| |
| // b. Let newByteLength be bufferByteLength - offset. |
| // c. If newByteLength < 0, throw a RangeError exception. |
| if (bufferByteLength < offset) goto IfInvalidOffset; |
| |
| // Spec step 16 length calculated here to avoid recalculating the length |
| // in the step 12 branch. |
| newByteLength = bufferByteLength - offset; |
| newLength = elementsInfo.CalculateLength(newByteLength) |
| otherwise IfInvalidOffset; |
| |
| // 12. Else, |
| } else { |
| // a. Let newByteLength be newLength × elementSize. |
| newByteLength = elementsInfo.CalculateByteLength(newLength) |
| otherwise IfInvalidByteLength; |
| |
| // b. If offset + newByteLength > bufferByteLength, throw a RangeError |
| // exception. |
| if ((bufferByteLength < newByteLength) || |
| (offset > bufferByteLength - newByteLength)) |
| goto IfInvalidLength; |
| } |
| |
| const isOnHeap: constexpr bool = false; |
| return AllocateTypedArray( |
| isOnHeap, map, buffer, offset, newByteLength, |
| Convert<uintptr>(newLength)); |
| } |
| label IfInvalidAlignment(problemString: String) deferred { |
| ThrowInvalidTypedArrayAlignment(map, problemString); |
| } |
| label IfInvalidByteLength deferred { |
| ThrowRangeError(kInvalidArrayBufferLength); |
| } |
| label IfInvalidLength deferred { |
| ThrowRangeError(kInvalidTypedArrayLength, length); |
| } |
| label IfInvalidOffset deferred { |
| ThrowRangeError(kInvalidOffset, byteOffset); |
| } |
| } |
| |
| transitioning macro ConstructByJSReceiver(implicit context: |
| Context)(obj: JSReceiver): never |
| labels IfConstructByArrayLike(HeapObject, Object, JSReceiver) { |
| try { |
| const iteratorMethod: Object = |
| GetIteratorMethod(obj) otherwise IfIteratorUndefined; |
| const iteratorFn: Callable = Cast<Callable>(iteratorMethod) |
| otherwise ThrowTypeError(kIteratorSymbolNonCallable); |
| ConstructByIterable(obj, iteratorFn) |
| otherwise IfConstructByArrayLike; |
| } |
| label IfIteratorUndefined { |
| const lengthObj: Object = GetProperty(obj, kLengthString); |
| const length: Smi = ToSmiLength(lengthObj) |
| otherwise goto IfInvalidLength(lengthObj); |
| goto IfConstructByArrayLike(obj, length, GetArrayBufferFunction()); |
| } |
| label IfInvalidLength(length: Object) { |
| ThrowRangeError(kInvalidTypedArrayLength, length); |
| } |
| } |
| |
| // 22.2.4 The TypedArray Constructors |
| // ES #sec-typedarray-constructors |
| transitioning builtin CreateTypedArray( |
| context: Context, target: JSFunction, newTarget: JSReceiver, arg1: Object, |
| arg2: Object, arg3: Object): JSTypedArray { |
| assert(IsConstructor(target)); |
| // 4. Let O be ? AllocateTypedArray(constructorName, NewTarget, |
| // "%TypedArrayPrototype%"). |
| const map = GetDerivedMap(target, newTarget); |
| |
| // 5. Let elementSize be the Number value of the Element Size value in Table |
| // 56 for constructorName. |
| const elementsInfo: typed_array::TypedArrayElementsInfo = |
| typed_array::GetTypedArrayElementsInfo(map); |
| |
| try { |
| typeswitch (arg1) { |
| case (length: Smi): { |
| goto IfConstructByLength(length); |
| } |
| case (buffer: JSArrayBuffer): { |
| return ConstructByArrayBuffer(map, buffer, arg2, arg3, elementsInfo); |
| } |
| case (typedArray: JSTypedArray): { |
| ConstructByTypedArray(typedArray) otherwise IfConstructByArrayLike; |
| } |
| case (obj: JSReceiver): { |
| ConstructByJSReceiver(obj) otherwise IfConstructByArrayLike; |
| } |
| // The first argument was a number or fell through and is treated as |
| // a number. https://tc39.github.io/ecma262/#sec-typedarray-length |
| case (lengthObj: HeapObject): { |
| goto IfConstructByLength(lengthObj); |
| } |
| } |
| } |
| label IfConstructByLength(length: Object) { |
| return ConstructByLength(map, length, elementsInfo); |
| } |
| label IfConstructByArrayLike( |
| arrayLike: HeapObject, length: Object, bufferConstructor: JSReceiver) { |
| return ConstructByArrayLike( |
| map, arrayLike, length, elementsInfo, bufferConstructor); |
| } |
| } |
| |
| transitioning macro TypedArraySpeciesCreate(implicit context: Context)( |
| methodName: constexpr string, numArgs: constexpr int31, |
| exemplar: JSTypedArray, arg0: Object, arg1: Object, |
| arg2: Object): JSTypedArray { |
| const defaultConstructor = GetDefaultConstructor(exemplar); |
| |
| try { |
| if (!IsPrototypeTypedArrayPrototype(exemplar.map)) goto IfSlow; |
| if (IsTypedArraySpeciesProtectorCellInvalid()) goto IfSlow; |
| |
| const typedArray = CreateTypedArray( |
| context, defaultConstructor, defaultConstructor, arg0, arg1, arg2); |
| |
| // It is assumed that the CreateTypedArray builtin does not produce a |
| // typed array that fails ValidateTypedArray |
| assert(!IsDetachedBuffer(typedArray.buffer)); |
| |
| return typedArray; |
| } |
| label IfSlow deferred { |
| const constructor = |
| Cast<Constructor>(SpeciesConstructor(exemplar, defaultConstructor)) |
| otherwise unreachable; |
| |
| // TODO(pwong): Simplify and remove numArgs when varargs are supported in |
| // macros. |
| let newObj: Object = Undefined; |
| if constexpr (numArgs == 1) { |
| newObj = Construct(constructor, arg0); |
| } else { |
| assert(numArgs == 3); |
| newObj = Construct(constructor, arg0, arg1, arg2); |
| } |
| |
| return typed_array::ValidateTypedArray(context, newObj, methodName); |
| } |
| } |
| |
| @export |
| transitioning macro TypedArraySpeciesCreateByLength(implicit context: |
| Context)( |
| methodName: constexpr string, exemplar: JSTypedArray, |
| length: PositiveSmi): JSTypedArray { |
| const numArgs: constexpr int31 = 1; |
| const typedArray: JSTypedArray = TypedArraySpeciesCreate( |
| methodName, numArgs, exemplar, length, Undefined, Undefined); |
| if (typedArray.length < Convert<uintptr>(length)) deferred { |
| ThrowTypeError(kTypedArrayTooShort); |
| } |
| |
| return typedArray; |
| } |
| } |