| // 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 IterableToListConvertHoles(Context, Object, Callable): JSArray; |
| |
| extern macro TypedArrayBuiltinsAssembler::AllocateEmptyOnHeapBuffer( |
| implicit context: Context)(): JSArrayBuffer; |
| extern macro TypedArrayBuiltinsAssembler::GetDefaultConstructor( |
| implicit context: Context)(JSTypedArray): JSFunction; |
| extern macro TypedArrayBuiltinsAssembler::SetupTypedArrayEmbedderFields( |
| JSTypedArray): void; |
| |
| extern runtime ThrowInvalidTypedArrayAlignment( |
| implicit context: Context)(Map, String): never; |
| |
| extern runtime GrowableSharedArrayBufferByteLength( |
| implicit context: Context)(Object): JSAny; |
| |
| transitioning macro AllocateTypedArray( |
| implicit context: Context)(isOnHeap: constexpr bool, map: Map, |
| buffer: JSArrayBuffer, byteOffset: uintptr, byteLength: uintptr, |
| length: uintptr, isLengthTracking: bool): JSTypedArray { |
| let elements: ByteArray; |
| if constexpr (isOnHeap) { |
| dcheck(!IsResizableArrayBuffer(buffer)); |
| dcheck(!isLengthTracking); |
| 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. |
| dcheck( |
| 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; |
| if (isLengthTracking) { |
| dcheck(IsResizableArrayBuffer(buffer)); |
| // Set the byte_length and length fields of length-tracking TAs to zero, so |
| // that we won't accidentally use them and access invalid data. |
| typedArray.byte_length = 0; |
| typedArray.length = 0; |
| } else { |
| typedArray.byte_length = byteLength; |
| typedArray.length = length; |
| } |
| typedArray.bit_field.is_length_tracking = isLengthTracking; |
| typedArray.bit_field.is_backed_by_rab = |
| IsResizableArrayBuffer(buffer) && !IsSharedArrayBuffer(buffer); |
| if constexpr (isOnHeap) { |
| typed_array::SetJSTypedArrayOnHeapDataPtr(typedArray, elements, byteOffset); |
| } else { |
| typed_array::SetJSTypedArrayOffHeapDataPtr( |
| typedArray, buffer.backing_store_ptr, byteOffset); |
| dcheck( |
| 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): |
| JSTypedArray labels IfRangeError { |
| const byteLength = elementsInfo.CalculateByteLength(length) |
| otherwise IfRangeError; |
| const byteLengthNum = Convert<Number>(byteLength); |
| const defaultConstructor = GetArrayBufferFunction(); |
| const byteOffset: uintptr = 0; |
| |
| try { |
| if (byteLength > kMaxTypedArrayInHeap) goto AllocateOffHeap; |
| |
| const buffer = AllocateEmptyOnHeapBuffer(); |
| |
| const isOnHeap: constexpr bool = true; |
| const isLengthTracking: constexpr bool = false; |
| const typedArray = AllocateTypedArray( |
| isOnHeap, map, buffer, byteOffset, byteLength, length, |
| isLengthTracking); |
| |
| 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; |
| const isLengthTracking: constexpr bool = false; |
| return AllocateTypedArray( |
| isOnHeap, map, buffer, byteOffset, byteLength, length, |
| isLengthTracking); |
| } |
| } |
| |
| // 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 initialize: constexpr bool = true; |
| return TypedArrayInitialize(initialize, map, length, elementsInfo) |
| 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): JSTypedArray { |
| try { |
| const initialize: constexpr bool = false; |
| const typedArray = |
| TypedArrayInitialize(initialize, map, length, elementsInfo) |
| otherwise RangeError; |
| |
| try { |
| const src: JSTypedArray = Cast<JSTypedArray>(arrayLike) otherwise IfSlow; |
| let byteLength: uintptr; |
| try { |
| byteLength = LoadJSArrayBufferViewByteLength(src, src.buffer) |
| otherwise DetachedOrOutOfBounds; |
| } label DetachedOrOutOfBounds deferred { |
| ThrowTypeError(MessageTemplate::kDetachedOperation, 'Construct'); |
| } |
| if (src.elements_kind != elementsInfo.kind) { |
| goto IfElementsKindMismatch(src.elements_kind); |
| |
| } else if (length > 0) { |
| dcheck(byteLength <= kArrayBufferMaxByteLength); |
| if (IsSharedArrayBuffer(src.buffer)) { |
| typed_array::CallCRelaxedMemcpy( |
| typedArray.data_ptr, src.data_ptr, byteLength); |
| } else { |
| typed_array::CallCMemcpy( |
| typedArray.data_ptr, src.data_ptr, byteLength); |
| } |
| } |
| } label IfElementsKindMismatch(srcKind: ElementsKind) deferred { |
| if (IsBigInt64ElementsKind(srcKind) != |
| IsBigInt64ElementsKind(elementsInfo.kind)) { |
| ThrowTypeError(MessageTemplate::kBigIntMixedTypes); |
| } |
| goto IfSlow; |
| } 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) { |
| const array: JSArray = |
| IterableToListConvertHoles(context, iterable, iteratorFn); |
| // Max JSArray length is a valid JSTypedArray length so we just use it. |
| goto IfConstructByArrayLike(array, array.length_uintptr); |
| } |
| |
| // 22.2.4.3 TypedArray ( typedArray ) |
| // ES #sec-typedarray-typedarray |
| transitioning macro ConstructByTypedArray( |
| implicit context: Context)(srcTypedArray: JSTypedArray): never |
| labels IfConstructByArrayLike(JSTypedArray, uintptr) { |
| let length: uintptr; |
| try { |
| // TODO(petermarshall): Throw on detached typedArray. |
| length = LoadJSTypedArrayLengthAndCheckDetached(srcTypedArray) |
| otherwise DetachedOrOutOfBounds; |
| } label DetachedOrOutOfBounds { |
| length = 0; |
| } |
| |
| goto IfConstructByArrayLike(srcTypedArray, length); |
| } |
| |
| // 22.2.4.5 TypedArray ( buffer, byteOffset, length ) |
| // ES #sec-initializetypedarrayfromarraybuffer |
| transitioning macro ConstructByArrayBuffer( |
| implicit context: Context)(target: JSFunction, newTarget: JSReceiver, |
| buffer: JSArrayBuffer, byteOffset: JSAny, length: JSAny): JSTypedArray { |
| let map: Map; |
| const isLengthTracking: bool = |
| IsResizableArrayBuffer(buffer) && (length == Undefined); |
| // Pick the RAB / GSAB map (containing the corresponding RAB / GSAB |
| // ElementsKind). GSAB-backed non-length-tracking TypedArrays behave just like |
| // normal TypedArrays, so exclude them. |
| const rabGsab: bool = IsResizableArrayBuffer(buffer) && |
| (!IsSharedArrayBuffer(buffer) || isLengthTracking); |
| if (rabGsab) { |
| map = GetDerivedRabGsabTypedArrayMap(target, newTarget); |
| } else { |
| map = GetDerivedMap(target, newTarget); |
| } |
| |
| // 1. Let elementSize be TypedArrayElementSize(O). |
| const elementsInfo = GetTypedArrayElementsInfo(map); |
| |
| try { |
| // 2. Let offset be ? ToIndex(byteOffset). |
| const offset: uintptr = ToIndex(byteOffset) otherwise IfInvalidOffset; |
| |
| // 3. If offset modulo elementSize ≠0, throw a RangeError exception. |
| if (elementsInfo.IsUnaligned(offset)) { |
| goto IfInvalidAlignment('start offset'); |
| } |
| |
| // 4. Let bufferIsResizable be IsResizableArrayBuffer(buffer). |
| |
| // 5. If length is not undefined, then |
| // a. Let newLength be ? ToIndex(length). |
| let newLength: uintptr = ToIndex(length) otherwise IfInvalidLength; |
| let newByteLength: uintptr; |
| |
| // 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. |
| if (IsDetachedBuffer(buffer)) { |
| ThrowTypeError(MessageTemplate::kDetachedOperation, 'Construct'); |
| } |
| |
| // 7. Let bufferByteLength be ArrayBufferByteLength(buffer, SeqCst). |
| let bufferByteLength: uintptr; |
| if (IsResizableArrayBuffer(buffer) && IsSharedArrayBuffer(buffer)) { |
| bufferByteLength = ToIndex(GrowableSharedArrayBufferByteLength(buffer)) |
| otherwise unreachable; |
| } else { |
| bufferByteLength = buffer.byte_length; |
| } |
| |
| // 8. If length is undefined and bufferIsResizable is true, then |
| // a. If offset > bufferByteLength, throw a RangeError exception. |
| // b. Set O.[[ByteLength]] to auto. |
| // c. Set O.[[ArrayLength]] to auto. |
| if (isLengthTracking) { |
| if (bufferByteLength < offset) goto IfInvalidOffset; |
| newLength = 0; |
| newByteLength = 0; |
| } else { |
| // 9. Else |
| // a. If length is undefined, then |
| if (length == Undefined) { |
| // i. If bufferByteLength modulo elementSize ≠0, throw a RangeError |
| // exception. |
| if (elementsInfo.IsUnaligned(bufferByteLength)) { |
| goto IfInvalidAlignment('byte length'); |
| } |
| |
| // ii. Let newByteLength be bufferByteLength - offset. |
| // iii. If newByteLength < 0, throw a RangeError exception. |
| if (bufferByteLength < offset) goto IfInvalidOffset; |
| |
| newByteLength = bufferByteLength - offset; |
| newLength = elementsInfo.CalculateLength(newByteLength); |
| } else { |
| // b. Else, |
| // i. Let newByteLength be newLength × elementSize. |
| // Since the ArrayBuffer already exists, the typed array view on top |
| // of it can't be too big to allocate, but it could still exceed the |
| // limit. |
| newByteLength = elementsInfo.CalculateByteLength(newLength) |
| otherwise IfInvalidLength; |
| |
| // ii. 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, |
| isLengthTracking); |
| } 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 { |
| dcheck(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 newTypedArrayLength = |
| ValidateTypedArrayAndGetLength(context, newTypedArrayObj, methodName); |
| const newTypedArray: JSTypedArray = |
| UnsafeCast<JSTypedArray>(newTypedArrayObj); |
| |
| dcheck( |
| newTypedArray.bit_field.is_backed_by_rab == |
| (IsResizableArrayBuffer(newTypedArray.buffer) && |
| !IsSharedArrayBuffer(newTypedArray.buffer))); |
| dcheck( |
| !newTypedArray.bit_field.is_length_tracking || |
| IsResizableArrayBuffer(newTypedArray.buffer)); |
| |
| 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 (newTypedArrayLength < 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), |
| IfIteratorNotCallable(JSAny) { |
| 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); |
| } label IfInvalidLength(length: Number) { |
| ThrowRangeError(MessageTemplate::kInvalidTypedArrayLength, length); |
| } |
| } |
| |
| // 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 { |
| dcheck(IsConstructor(target)); |
| // 4. Let O be ? AllocateTypedArray(constructorName, NewTarget, |
| // "%TypedArrayPrototype%"). |
| try { |
| typeswitch (arg1) { |
| case (length: Smi): { |
| goto IfConstructByLength(length); |
| } |
| case (buffer: JSArrayBuffer): { |
| return ConstructByArrayBuffer(target, newTarget, buffer, arg2, arg3); |
| } |
| case (typedArray: JSTypedArray): { |
| ConstructByTypedArray(typedArray) otherwise IfConstructByArrayLike; |
| } |
| case (obj: JSReceiver): { |
| ConstructByJSReceiver(obj) otherwise IfConstructByArrayLike, |
| IfIteratorNotCallable; |
| } |
| // 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) { |
| 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); |
| |
| return ConstructByLength(map, length, elementsInfo); |
| } label IfConstructByArrayLike(arrayLike: JSReceiver, length: uintptr) { |
| 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); |
| return ConstructByArrayLike(map, arrayLike, length, elementsInfo); |
| } label IfIteratorNotCallable(_value: JSAny) deferred { |
| ThrowTypeError( |
| MessageTemplate::kFirstArgumentIteratorSymbolNonCallable, |
| 'TypedArray\'s constructor'); |
| } |
| } |
| |
| 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 |
| dcheck(!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 { |
| dcheck(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); |
| try { |
| const createdArrayLength = |
| LoadJSTypedArrayLengthAndCheckDetached(typedArray) |
| otherwise DetachedOrOutOfBounds; |
| if (createdArrayLength < length) deferred { |
| ThrowTypeError(MessageTemplate::kTypedArrayTooShort); |
| } |
| } label DetachedOrOutOfBounds { |
| ThrowTypeError(MessageTemplate::kTypedArrayTooShort); |
| } |
| return typedArray; |
| } |
| |
| transitioning macro TypedArraySpeciesCreateByBuffer( |
| implicit context: Context)(methodName: constexpr string, |
| exemplar: JSTypedArray, buffer: JSArrayBuffer, beginByteOffset: uintptr, |
| newLength: NumberOrUndefined): 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), |
| newLength); |
| return typedArray; |
| } |
| |
| transitioning macro TypedArrayCreateSameType( |
| implicit context: Context)(exemplar: JSTypedArray, |
| newLength: uintptr): JSTypedArray { |
| const constructor = GetDefaultConstructor(exemplar); |
| const typedArray = CreateTypedArray( |
| context, constructor, constructor, Convert<Number>(newLength), Undefined, |
| Undefined); |
| dcheck(!IsDetachedBuffer(typedArray.buffer)); |
| // The elements kind are equal up to resizability / growability. The newly |
| // created TypedArray is never backed by a resizable / growable buffer. |
| dcheck( |
| GetNonRabGsabElementsKind(exemplar.elements_kind) == |
| typedArray.elements_kind); |
| return typedArray; |
| } |
| } |