blob: d03c1a0be977e3f97ba48c6b370df3d20b60fa27 [file] [log] [blame]
// Copyright 2018 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-typed-array-gen.h'
namespace typed_array {
// Naming convention from elements.cc. We have a similar intent but implement
// fastpaths using generics instead of using a class hierarchy for elements
// kinds specific implementations.
type Uint8Elements;
type Int8Elements;
type Uint16Elements;
type Int16Elements;
type Uint32Elements;
type Int32Elements;
type Float32Elements;
type Float64Elements;
type Uint8ClampedElements;
type BigUint64Elements;
type BigInt64Elements;
struct TypedArrayElementsInfo {
// Calculates the number of bytes required for specified number of elements.
CalculateByteLength(lengthSmi: PositiveSmi): uintptr labels IfInvalid {
const length = Convert<uintptr>(lengthSmi);
const byteLength = length << this.sizeLog2;
// If an overflow ocurred, the byte length exceeds
// JSArrayBuffer::kMaxByteLength and is invalid.
if (byteLength >>> this.sizeLog2 != length) goto IfInvalid;
return byteLength;
}
// Calculates the maximum number of elements supported by a specified number
// of bytes.
CalculateLength(byteLength: uintptr): PositiveSmi labels IfInvalid {
return Convert<PositiveSmi>(byteLength >>> this.sizeLog2)
otherwise IfInvalid;
}
// Determines if `bytes` (byte offset or length) cannot be evenly divided by
// element size.
IsUnaligned(bytes: uintptr): bool {
// Exploits the fact the element size is a power of 2. Determining whether
// there is remainder (not aligned) can be achieved efficiently with bit
// masking. Shift is safe as sizeLog2 can be 3 at most (see
// ElementsKindToShiftSize).
return (bytes & ((1 << this.sizeLog2) - 1)) != 0;
}
sizeLog2: uintptr;
kind: ElementsKind;
}
extern runtime TypedArraySortFast(Context, Object): JSTypedArray;
extern macro TypedArrayBuiltinsAssembler::ValidateTypedArray(
Context, Object, constexpr string): JSTypedArray;
extern macro TypedArrayBuiltinsAssembler::CallCMemcpy(
RawPtr, RawPtr, uintptr): void;
extern macro TypedArrayBuiltinsAssembler::CallCMemmove(
RawPtr, RawPtr, uintptr): void;
extern macro TypedArrayBuiltinsAssembler::CallCMemset(
RawPtr, intptr, uintptr): void;
extern macro TypedArrayBuiltinsAssembler::GetBuffer(
implicit context: Context)(JSTypedArray): JSArrayBuffer;
extern macro TypedArrayBuiltinsAssembler::GetTypedArrayElementsInfo(
JSTypedArray): TypedArrayElementsInfo;
extern macro TypedArrayBuiltinsAssembler::GetTypedArrayElementsInfo(Map):
TypedArrayElementsInfo;
extern macro TypedArrayBuiltinsAssembler::IsBigInt64ElementsKind(
ElementsKind): bool;
extern macro LoadFixedTypedArrayElementAsTagged(
RawPtr, Smi, constexpr ElementsKind): Numeric;
extern macro StoreJSTypedArrayElementFromTagged(
Context, JSTypedArray, Smi, Object, constexpr ElementsKind);
type LoadFn = builtin(Context, JSTypedArray, Smi) => Object;
type StoreFn = builtin(Context, JSTypedArray, Smi, Object) => Object;
// AttachedJSTypedArray guards that the array's buffer is not detached.
transient type AttachedJSTypedArray extends JSTypedArray;
macro EnsureAttached(array: JSTypedArray): AttachedJSTypedArray
labels Detached {
if (IsDetachedBuffer(array.buffer)) goto Detached;
return %RawDownCast<AttachedJSTypedArray>(array);
}
struct AttachedJSTypedArrayWitness {
Get(): AttachedJSTypedArray {
return this.unstable;
}
GetStable(): JSTypedArray {
return this.stable;
}
Recheck() labels Detached {
if (IsDetachedBuffer(this.stable.buffer)) goto Detached;
this.unstable = %RawDownCast<AttachedJSTypedArray>(this.stable);
}
Load(implicit context: Context)(k: Smi): Object {
const lf: LoadFn = this.loadfn;
return lf(context, this.unstable, k);
}
stable: JSTypedArray;
unstable: AttachedJSTypedArray;
loadfn: LoadFn;
}
macro NewAttachedJSTypedArrayWitness(array: AttachedJSTypedArray):
AttachedJSTypedArrayWitness {
const kind = array.elements_kind;
return AttachedJSTypedArrayWitness{
stable: array,
unstable: array,
loadfn: GetLoadFnForElementsKind(kind)
};
}
macro GetLoadFnForElementsKind(elementsKind: ElementsKind): LoadFn {
if (IsElementsKindGreaterThan(elementsKind, UINT32_ELEMENTS)) {
if (elementsKind == INT32_ELEMENTS) {
return LoadFixedElement<Int32Elements>;
} else if (elementsKind == FLOAT32_ELEMENTS) {
return LoadFixedElement<Float32Elements>;
} else if (elementsKind == FLOAT64_ELEMENTS) {
return LoadFixedElement<Float64Elements>;
} else if (elementsKind == UINT8_CLAMPED_ELEMENTS) {
return LoadFixedElement<Uint8ClampedElements>;
} else if (elementsKind == BIGUINT64_ELEMENTS) {
return LoadFixedElement<BigUint64Elements>;
} else if (elementsKind == BIGINT64_ELEMENTS) {
return LoadFixedElement<BigInt64Elements>;
} else {
unreachable;
}
} else {
if (elementsKind == UINT8_ELEMENTS) {
return LoadFixedElement<Uint8Elements>;
} else if (elementsKind == INT8_ELEMENTS) {
return LoadFixedElement<Int8Elements>;
} else if (elementsKind == UINT16_ELEMENTS) {
return LoadFixedElement<Uint16Elements>;
} else if (elementsKind == INT16_ELEMENTS) {
return LoadFixedElement<Int16Elements>;
} else if (elementsKind == UINT32_ELEMENTS) {
return LoadFixedElement<Uint32Elements>;
} else {
unreachable;
}
}
}
macro KindForArrayType<T: type>(): constexpr ElementsKind;
KindForArrayType<Uint8Elements>(): constexpr ElementsKind {
return UINT8_ELEMENTS;
}
KindForArrayType<Int8Elements>(): constexpr ElementsKind {
return INT8_ELEMENTS;
}
KindForArrayType<Uint16Elements>(): constexpr ElementsKind {
return UINT16_ELEMENTS;
}
KindForArrayType<Int16Elements>(): constexpr ElementsKind {
return INT16_ELEMENTS;
}
KindForArrayType<Uint32Elements>(): constexpr ElementsKind {
return UINT32_ELEMENTS;
}
KindForArrayType<Int32Elements>(): constexpr ElementsKind {
return INT32_ELEMENTS;
}
KindForArrayType<Float32Elements>(): constexpr ElementsKind {
return FLOAT32_ELEMENTS;
}
KindForArrayType<Float64Elements>(): constexpr ElementsKind {
return FLOAT64_ELEMENTS;
}
KindForArrayType<Uint8ClampedElements>(): constexpr ElementsKind {
return UINT8_CLAMPED_ELEMENTS;
}
KindForArrayType<BigUint64Elements>(): constexpr ElementsKind {
return BIGUINT64_ELEMENTS;
}
KindForArrayType<BigInt64Elements>(): constexpr ElementsKind {
return BIGINT64_ELEMENTS;
}
builtin LoadFixedElement<T: type>(
_context: Context, array: JSTypedArray, index: Smi): Object {
return LoadFixedTypedArrayElementAsTagged(
array.data_ptr, index, KindForArrayType<T>());
}
builtin StoreFixedElement<T: type>(
context: Context, typedArray: JSTypedArray, index: Smi,
value: Object): Object {
StoreJSTypedArrayElementFromTagged(
context, typedArray, index, value, KindForArrayType<T>());
return Undefined;
}
transitioning macro CallCompare(
implicit context: Context, array: JSTypedArray,
comparefn: Callable)(a: Object, b: Object): Number {
// a. Let v be ? ToNumber(? Call(comparefn, undefined, x, y)).
const v: Number =
ToNumber_Inline(context, Call(context, comparefn, Undefined, a, b));
// b. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
if (IsDetachedBuffer(array.buffer)) {
ThrowTypeError(kDetachedOperation, '%TypedArray%.prototype.sort');
}
// c. If v is NaN, return +0.
if (NumberIsNaN(v)) return 0;
// d. return v.
return v;
}
// Merges two sorted runs [from, middle) and [middle, to)
// from "source" into "target".
transitioning macro
TypedArrayMerge(
implicit context: Context, array: JSTypedArray, comparefn: Callable)(
source: FixedArray, from: Smi, middle: Smi, to: Smi, target: FixedArray) {
let left: Smi = from;
let right: Smi = middle;
for (let targetIndex: Smi = from; targetIndex < to; ++targetIndex) {
if (left < middle && right >= to) {
// If the left run has elements, but the right does not, we take
// from the left.
target.objects[targetIndex] = source.objects[left++];
} else if (left < middle) {
// If both have elements, we need to compare.
const leftElement: Object = source.objects[left];
const rightElement: Object = source.objects[right];
if (CallCompare(leftElement, rightElement) <= 0) {
target.objects[targetIndex] = leftElement;
left++;
} else {
target.objects[targetIndex] = rightElement;
right++;
}
} else {
// No elements on the left, but the right does, so we take
// from the right.
assert(left == middle);
target.objects[targetIndex] = source.objects[right++];
}
}
}
transitioning builtin
TypedArrayMergeSort(
implicit context: Context, array: JSTypedArray, comparefn: Callable)(
source: FixedArray, from: Smi, to: Smi, target: FixedArray): Object {
assert(to - from > 1);
const middle: Smi = from + ((to - from) >> 1);
// On the next recursion step source becomes target and vice versa.
// This saves the copy of the relevant range from the original
// array into a work array on each recursion step.
if (middle - from > 1) TypedArrayMergeSort(target, from, middle, source);
if (to - middle > 1) TypedArrayMergeSort(target, middle, to, source);
TypedArrayMerge(source, from, middle, to, target);
return Undefined;
}
// https://tc39.github.io/ecma262/#sec-%typedarray%.prototype.sort
transitioning javascript builtin TypedArrayPrototypeSort(
js-implicit context: Context,
receiver: Object)(...arguments): JSTypedArray {
// 1. If comparefn is not undefined and IsCallable(comparefn) is false,
// throw a TypeError exception.
const comparefnObj: Object =
arguments.length > 0 ? arguments[0] : Undefined;
if (comparefnObj != Undefined && !TaggedIsCallable(comparefnObj)) {
ThrowTypeError(kBadSortComparisonFunction, comparefnObj);
}
// 2. Let obj be the this value.
const obj: Object = receiver;
// 3. Let buffer be ? ValidateTypedArray(obj).
// ValidateTypedArray currently returns the array, not the ViewBuffer.
const array: JSTypedArray =
ValidateTypedArray(context, obj, '%TypedArray%.prototype.sort');
// Default sorting is done in C++ using std::sort
if (comparefnObj == Undefined) {
return TypedArraySortFast(context, obj);
}
// 4. Let len be obj.[[ArrayLength]].
// TODO(v8:4153): Support huge TypedArrays here.
const len = Cast<Smi>(Convert<Number>(array.length)) otherwise unreachable;
// Arrays of length 1 or less are considered sorted.
if (len < 2) return array;
const comparefn: Callable =
Cast<Callable>(comparefnObj) otherwise unreachable;
let loadfn: LoadFn;
let storefn: StoreFn;
const elementsKind: ElementsKind = array.elements_kind;
if (IsElementsKindGreaterThan(elementsKind, UINT32_ELEMENTS)) {
if (elementsKind == INT32_ELEMENTS) {
loadfn = LoadFixedElement<Int32Elements>;
storefn = StoreFixedElement<Int32Elements>;
} else if (elementsKind == FLOAT32_ELEMENTS) {
loadfn = LoadFixedElement<Float32Elements>;
storefn = StoreFixedElement<Float32Elements>;
} else if (elementsKind == FLOAT64_ELEMENTS) {
loadfn = LoadFixedElement<Float64Elements>;
storefn = StoreFixedElement<Float64Elements>;
} else if (elementsKind == UINT8_CLAMPED_ELEMENTS) {
loadfn = LoadFixedElement<Uint8ClampedElements>;
storefn = StoreFixedElement<Uint8ClampedElements>;
} else if (elementsKind == BIGUINT64_ELEMENTS) {
loadfn = LoadFixedElement<BigUint64Elements>;
storefn = StoreFixedElement<BigUint64Elements>;
} else if (elementsKind == BIGINT64_ELEMENTS) {
loadfn = LoadFixedElement<BigInt64Elements>;
storefn = StoreFixedElement<BigInt64Elements>;
} else {
unreachable;
}
} else {
if (elementsKind == UINT8_ELEMENTS) {
loadfn = LoadFixedElement<Uint8Elements>;
storefn = StoreFixedElement<Uint8Elements>;
} else if (elementsKind == INT8_ELEMENTS) {
loadfn = LoadFixedElement<Int8Elements>;
storefn = StoreFixedElement<Int8Elements>;
} else if (elementsKind == UINT16_ELEMENTS) {
loadfn = LoadFixedElement<Uint16Elements>;
storefn = StoreFixedElement<Uint16Elements>;
} else if (elementsKind == INT16_ELEMENTS) {
loadfn = LoadFixedElement<Int16Elements>;
storefn = StoreFixedElement<Int16Elements>;
} else if (elementsKind == UINT32_ELEMENTS) {
loadfn = LoadFixedElement<Uint32Elements>;
storefn = StoreFixedElement<Uint32Elements>;
} else {
unreachable;
}
}
// Prepare the two work arrays. All numbers are converted to tagged
// objects first, and merge sorted between the two FixedArrays.
// The result is then written back into the JSTypedArray.
const work1: FixedArray = AllocateZeroedFixedArray(Convert<intptr>(len));
const work2: FixedArray = AllocateZeroedFixedArray(Convert<intptr>(len));
for (let i: Smi = 0; i < len; ++i) {
const element: Object = loadfn(context, array, i);
work1.objects[i] = element;
work2.objects[i] = element;
}
TypedArrayMergeSort(work2, 0, len, work1);
// work1 contains the sorted numbers. Write them back.
for (let i: Smi = 0; i < len; ++i)
storefn(context, array, i, work1.objects[i]);
return array;
}
}