// 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)(): 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; 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(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(AllocateFastOrSlowJSObjectFromMap(map)); typedArray.elements = elements; typedArray.buffer = buffer; typedArray.byte_offset = byteOffset; 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(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(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(); 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(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 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(arrayLike) otherwise IfSlow; if (IsDetachedBuffer(src.buffer)) { ThrowTypeError(MessageTemplate::kDetachedOperation, 'Construct'); } else if (src.elements_kind != elementsInfo.kind) { goto IfElementsKindMismatch(src.elements_kind); } else if (length > 0) { const byteLength = typedArray.byte_length; 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(length)); } } return typedArray; } label RangeError deferred { ThrowRangeError( MessageTemplate::kInvalidTypedArrayLength, Convert(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)( 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 = GetDerivedRabGsabMap(target, newTarget); } else { 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 { // 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]]. let bufferByteLength: uintptr; if (IsResizableArrayBuffer(buffer) && IsSharedArrayBuffer(buffer)) { bufferByteLength = ToIndex(GrowableSharedArrayBufferByteLength(buffer)) otherwise unreachable; } else { bufferByteLength = 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, 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 newTypedArray: JSTypedArray = ValidateTypedArray(context, newTypedArrayObj, methodName); // TODO(v8:11111): bit_field should be initialized to 0. newTypedArray.bit_field.is_length_tracking = false; newTypedArray.bit_field.is_backed_by_rab = false; 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(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 { 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): { // TODO(v8:11111): Support RAB / GSAB. 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) { 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, bufferConstructor: JSReceiver) { 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, 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 dcheck(!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 { 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(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(beginByteOffset), Convert(newLength)); return typedArray; } }