| // 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 { |
| 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::SetupTypedArrayEmbedderFields( |
| JSTypedArray): void; |
| |
| extern runtime ThrowInvalidTypedArrayAlignment(implicit context: Context)( |
| Map, String): never; |
| |
| transitioning macro AllocateTypedArray(implicit context: Context)( |
| isOnHeap: constexpr bool, map: Map, buffer: JSArrayBuffer, |
| byteOffset: uintptr, byteLength: uintptr, length: uintptr): JSTypedArray { |
| let elements: ByteArray; |
| if constexpr (isOnHeap) { |
| elements = AllocateByteArray(byteLength); |
| } else { |
| elements = kEmptyByteArray; |
| |
| // 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: uintptr = Convert<uintptr>(buffer.backing_store_ptr); |
| |
| // 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() || |
| (backingStore + byteOffset) >= backingStore); |
| } |
| |
| // 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; |
| typed_array::AllocateJSTypedArrayExternalPointerEntry(typedArray); |
| if constexpr (isOnHeap) { |
| typed_array::SetJSTypedArrayOnHeapDataPtr(typedArray, elements, byteOffset); |
| } else { |
| typed_array::SetJSTypedArrayOffHeapDataPtr( |
| typedArray, buffer.backing_store_ptr, byteOffset); |
| assert( |
| typedArray.data_ptr == |
| (buffer.backing_store_ptr + Convert<intptr>(byteOffset))); |
| } |
| SetupTypedArrayEmbedderFields(typedArray); |
| return typedArray; |
| } |
| |
| transitioning macro TypedArrayInitialize(implicit context: Context)( |
| initialize: constexpr bool, map: Map, length: uintptr, |
| elementsInfo: typed_array::TypedArrayElementsInfo, |
| bufferConstructor: JSReceiver): JSTypedArray labels IfRangeError { |
| const byteLength = elementsInfo.CalculateByteLength(length) |
| otherwise IfRangeError; |
| 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, 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, length); |
| } |
| } |
| |
| // 22.2.4.2 TypedArray ( length ) |
| // ES #sec-typedarray-length |
| transitioning macro ConstructByLength(implicit context: Context)( |
| map: Map, lengthObj: JSAny, |
| elementsInfo: typed_array::TypedArrayElementsInfo): JSTypedArray { |
| try { |
| const length: uintptr = ToIndex(lengthObj) otherwise RangeError; |
| const defaultConstructor: Constructor = GetArrayBufferFunction(); |
| const initialize: constexpr bool = true; |
| return TypedArrayInitialize( |
| initialize, map, length, elementsInfo, defaultConstructor) |
| otherwise RangeError; |
| } label RangeError deferred { |
| ThrowRangeError(MessageTemplate::kInvalidTypedArrayLength, lengthObj); |
| } |
| } |
| |
| // 22.2.4.4 TypedArray ( object ) |
| // ES #sec-typedarray-object |
| transitioning macro ConstructByArrayLike(implicit context: Context)( |
| map: Map, arrayLike: HeapObject, length: uintptr, |
| elementsInfo: typed_array::TypedArrayElementsInfo, |
| bufferConstructor: JSReceiver): JSTypedArray { |
| try { |
| const initialize: constexpr bool = false; |
| const typedArray = TypedArrayInitialize( |
| initialize, map, length, elementsInfo, bufferConstructor) |
| otherwise RangeError; |
| |
| try { |
| const src: JSTypedArray = Cast<JSTypedArray>(arrayLike) otherwise IfSlow; |
| |
| if (IsDetachedBuffer(src.buffer)) { |
| ThrowTypeError(MessageTemplate::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, Convert<Number>(length)); |
| } |
| } |
| return typedArray; |
| } label RangeError deferred { |
| ThrowRangeError( |
| MessageTemplate::kInvalidTypedArrayLength, Convert<Number>(length)); |
| } |
| } |
| |
| // 22.2.4.4 TypedArray ( object ) |
| // ES #sec-typedarray-object |
| transitioning macro ConstructByIterable(implicit context: Context)( |
| iterable: JSReceiver, iteratorFn: Callable): never |
| labels IfConstructByArrayLike(JSArray, uintptr, JSReceiver) { |
| const array: JSArray = |
| IterableToListMayPreserveHoles(context, iterable, iteratorFn); |
| // Max JSArray length is a valid JSTypedArray length so we just use it. |
| goto IfConstructByArrayLike( |
| array, array.length_uintptr, GetArrayBufferFunction()); |
| } |
| |
| // 22.2.4.3 TypedArray ( typedArray ) |
| // ES #sec-typedarray-typedarray |
| transitioning macro ConstructByTypedArray(implicit context: Context)( |
| srcTypedArray: JSTypedArray): never |
| labels IfConstructByArrayLike(JSTypedArray, uintptr, JSReceiver) { |
| let bufferConstructor: JSReceiver = GetArrayBufferFunction(); |
| const srcBuffer: JSArrayBuffer = srcTypedArray.buffer; |
| // TODO(petermarshall): Throw on detached typedArray. |
| let length: uintptr = IsDetachedBuffer(srcBuffer) ? 0 : 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: JSAny, length: JSAny, |
| elementsInfo: typed_array::TypedArrayElementsInfo): JSTypedArray { |
| try { |
| // 6. Let offset be ? ToIndex(byteOffset). |
| const offset: uintptr = ToIndex(byteOffset) otherwise IfInvalidOffset; |
| |
| // 7. If offset modulo elementSize ≠0, throw a RangeError exception. |
| if (elementsInfo.IsUnaligned(offset)) { |
| goto IfInvalidAlignment('start offset'); |
| } |
| |
| // 8. If length is present and length is not undefined, then |
| // a. Let newLength be ? ToIndex(length). |
| let newLength: uintptr = ToIndex(length) otherwise IfInvalidLength; |
| let newByteLength: uintptr; |
| |
| // 9. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. |
| if (IsDetachedBuffer(buffer)) { |
| ThrowTypeError(MessageTemplate::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 IfInvalidLength; |
| |
| // 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, newLength); |
| } label IfInvalidAlignment(problemString: String) deferred { |
| ThrowInvalidTypedArrayAlignment(map, problemString); |
| } label IfInvalidLength deferred { |
| ThrowRangeError(MessageTemplate::kInvalidTypedArrayLength, length); |
| } label IfInvalidOffset deferred { |
| ThrowRangeError(MessageTemplate::kInvalidOffset, byteOffset); |
| } |
| } |
| |
| // 22.2.4.6 TypedArrayCreate ( constructor, argumentList ) |
| // ES #typedarray-create |
| @export |
| transitioning macro TypedArrayCreateByLength(implicit context: Context)( |
| constructor: Constructor, length: Number, methodName: constexpr string): |
| JSTypedArray { |
| assert(IsSafeInteger(length)); |
| |
| // 1. Let newTypedArray be ? Construct(constructor, argumentList). |
| const newTypedArrayObj = Construct(constructor, length); |
| |
| // 2. Perform ? ValidateTypedArray(newTypedArray). |
| // ValidateTypedArray currently returns the array, not the ViewBuffer. |
| const newTypedArray: JSTypedArray = |
| ValidateTypedArray(context, newTypedArrayObj, methodName); |
| |
| if (IsDetachedBuffer(newTypedArray.buffer)) deferred { |
| ThrowTypeError(MessageTemplate::kDetachedOperation, methodName); |
| } |
| |
| // 3. If argumentList is a List of a single Number, then |
| // a. If newTypedArray.[[ArrayLength]] < argumentList[0], throw a |
| // TypeError exception. |
| if (newTypedArray.length < Convert<uintptr>(length)) deferred { |
| ThrowTypeError(MessageTemplate::kTypedArrayTooShort); |
| } |
| |
| // 4. Return newTypedArray. |
| return newTypedArray; |
| } |
| |
| transitioning macro ConstructByJSReceiver(implicit context: Context)( |
| obj: JSReceiver): never |
| labels IfConstructByArrayLike(JSReceiver, uintptr, JSReceiver) { |
| try { |
| // TODO(v8:8906): Use iterator::GetIteratorMethod() once it supports |
| // labels. |
| const iteratorMethod = GetMethod(obj, IteratorSymbolConstant()) |
| otherwise IfIteratorUndefined, IfIteratorNotCallable; |
| ConstructByIterable(obj, iteratorMethod) |
| otherwise IfConstructByArrayLike; |
| } label IfIteratorUndefined { |
| const lengthObj: JSAny = GetProperty(obj, kLengthString); |
| const lengthNumber: Number = ToLength_Inline(lengthObj); |
| // Throw RangeError here if the length does not fit in uintptr because |
| // such a length will not pass bounds checks in ConstructByArrayLike() |
| // anyway. |
| const length: uintptr = ChangeSafeIntegerNumberToUintPtr(lengthNumber) |
| otherwise goto IfInvalidLength(lengthNumber); |
| goto IfConstructByArrayLike(obj, length, GetArrayBufferFunction()); |
| } label IfInvalidLength(length: Number) { |
| ThrowRangeError(MessageTemplate::kInvalidTypedArrayLength, length); |
| } label IfIteratorNotCallable(_value: JSAny) deferred { |
| ThrowTypeError(MessageTemplate::kIteratorSymbolNonCallable); |
| } |
| } |
| |
| // 22.2.4 The TypedArray Constructors |
| // ES #sec-typedarray-constructors |
| transitioning builtin CreateTypedArray( |
| context: Context, target: JSFunction, newTarget: JSReceiver, arg1: JSAny, |
| arg2: JSAny, arg3: JSAny): 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 = 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: JSAny): { |
| goto IfConstructByLength(lengthObj); |
| } |
| } |
| } label IfConstructByLength(length: JSAny) { |
| return ConstructByLength(map, length, elementsInfo); |
| } label IfConstructByArrayLike( |
| arrayLike: JSReceiver, length: uintptr, bufferConstructor: JSReceiver) { |
| return ConstructByArrayLike( |
| map, arrayLike, length, elementsInfo, bufferConstructor); |
| } |
| } |
| |
| transitioning macro TypedArraySpeciesCreate(implicit context: Context)( |
| methodName: constexpr string, numArgs: constexpr int31, |
| exemplar: JSTypedArray, arg0: JSAny, arg1: JSAny, |
| arg2: JSAny): 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: JSAny = Undefined; |
| if constexpr (numArgs == 1) { |
| newObj = Construct(constructor, arg0); |
| } else { |
| assert(numArgs == 3); |
| newObj = Construct(constructor, arg0, arg1, arg2); |
| } |
| |
| return ValidateTypedArray(context, newObj, methodName); |
| } |
| } |
| |
| @export |
| transitioning macro TypedArraySpeciesCreateByLength(implicit context: Context)( |
| methodName: constexpr string, exemplar: JSTypedArray, length: uintptr): |
| JSTypedArray { |
| const numArgs: constexpr int31 = 1; |
| // TODO(v8:4153): pass length further as uintptr. |
| const typedArray: JSTypedArray = TypedArraySpeciesCreate( |
| methodName, numArgs, exemplar, Convert<Number>(length), Undefined, |
| Undefined); |
| if (typedArray.length < length) deferred { |
| ThrowTypeError(MessageTemplate::kTypedArrayTooShort); |
| } |
| return typedArray; |
| } |
| |
| transitioning macro TypedArraySpeciesCreateByBuffer(implicit context: Context)( |
| methodName: constexpr string, exemplar: JSTypedArray, buffer: JSArrayBuffer, |
| beginByteOffset: uintptr, newLength: uintptr): JSTypedArray { |
| const numArgs: constexpr int31 = 3; |
| // TODO(v8:4153): pass length further as uintptr. |
| const typedArray: JSTypedArray = TypedArraySpeciesCreate( |
| methodName, numArgs, exemplar, buffer, Convert<Number>(beginByteOffset), |
| Convert<Number>(newLength)); |
| return typedArray; |
| } |
| } |