blob: b4ad99dec1d64bbd06e3b37bceb93149ea6f95a6 [file] [log] [blame]
// 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;
}
}