// 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(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(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(externalPointer) >= Convert(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(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(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(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(bufferObj) otherwise unreachable; const isOnHeap: constexpr bool = false; return AllocateTypedArray( isOnHeap, map, buffer, byteOffset, byteLength, Convert(length)); } } // 22.2.4.2 TypedArray ( length ) // ES #sec-typedarray-length transitioning macro ConstructByLength(implicit context: Context)( map: Map, length: JSAny, 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(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: JSAny, 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(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(JSArray, Number, 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(JSTypedArray, Number, 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(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 { 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(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(JSReceiver, Number, JSReceiver) { try { const iteratorMethod: Object = GetIteratorMethod(obj) otherwise IfIteratorUndefined; const iteratorFn: Callable = Cast(iteratorMethod) otherwise ThrowTypeError(kIteratorSymbolNonCallable); ConstructByIterable(obj, iteratorFn) otherwise IfConstructByArrayLike; } label IfIteratorUndefined { const lengthObj: JSAny = 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: 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: 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: JSAny): { goto IfConstructByLength(lengthObj); } } } label IfConstructByLength(length: JSAny) { return ConstructByLength(map, length, elementsInfo); } label IfConstructByArrayLike( arrayLike: JSReceiver, length: Number, 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(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 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(length)) deferred { ThrowTypeError(kTypedArrayTooShort); } return typedArray; } }