blob: df9ebbf3b4709c8d921573c883e75895bb482842 [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-typed-array-gen.h'
namespace typed_array {
const kBuiltinNameFrom: constexpr string = '%TypedArray%.from';
type BuiltinsName extends int31 constexpr 'Builtins::Name';
const kTypedArrayPrototypeValues: constexpr BuiltinsName
generates 'Builtins::kTypedArrayPrototypeValues';
const kArrayPrototypeValues: constexpr BuiltinsName
generates 'Builtins::kArrayPrototypeValues';
extern builtin IterableToList(implicit context: Context)(JSAny, JSAny): JSArray;
// %TypedArray%.from ( source [ , mapfn [ , thisArg ] ] )
// https://tc39.github.io/ecma262/#sec-%typedarray%.from
transitioning javascript builtin
TypedArrayFrom(js-implicit context: NativeContext, receiver: JSAny)(
...arguments): JSTypedArray {
try {
const source: JSAny = arguments[0];
const mapfnObj: JSAny = arguments[1];
const thisArg = arguments[2];
// 1. Let C be the this value.
// 2. If IsConstructor(C) is false, throw a TypeError exception.
const constructor = Cast<Constructor>(receiver) otherwise NotConstructor;
// 3. If mapfn is undefined, then let mapping be false.
// 4. Else,
// a. If IsCallable(mapfn) is false, throw a TypeError exception.
// b. Let mapping be true.
const mapping: bool = mapfnObj != Undefined;
if (mapping && !Is<Callable>(mapfnObj)) deferred {
ThrowTypeError(MessageTemplate::kCalledNonCallable, mapfnObj);
}
// We split up this builtin differently to the way it is written in the
// spec. We already have great code in the elements accessor for copying
// from a JSArray into a TypedArray, so we use that when possible. We only
// avoid calling into the elements accessor when we have a mapping
// function, because we can't handle that. Here, presence of a mapping
// function is the slow path. We also combine the two different loops in
// the specification (starting at 7.e and 13) because they are essentially
// identical. We also save on code-size this way.
let finalLength: uintptr;
let finalSource: JSAny;
try {
// 5. Let usingIterator be ? GetMethod(source, @@iterator).
// TODO(v8:8906): Use iterator::GetIteratorMethod() once it supports
// labels.
const usingIterator = GetMethod(source, IteratorSymbolConstant())
otherwise IteratorIsUndefined, IteratorNotCallable;
try {
// TypedArrays and JSArrays have iterators, so normally we would go
// through the IterableToList case below, which would convert the
// source to a JSArray (boxing the values if they won't fit in a Smi).
//
// However, if we can guarantee that the source object has the
// built-in iterator and that the %ArrayIteratorPrototype%.next method
// has not been overridden, then we know the behavior of the iterator:
// returning the values in the TypedArray sequentially from index 0 to
// length-1.
//
// In this case, we can avoid creating the intermediate array and the
// associated HeapNumbers, and use the fast path in
// TypedArrayCopyElements which uses the same ordering as the default
// iterator.
//
// Drop through to the default check_iterator behavior if any of these
// checks fail.
// If there is a mapping, we need to gather the values from the
// iterables before applying the mapping.
if (mapping) goto UseUserProvidedIterator;
const iteratorFn =
Cast<JSFunction>(usingIterator) otherwise UseUserProvidedIterator;
// Check that the ArrayIterator prototype's "next" method hasn't been
// overridden.
if (IsArrayIteratorProtectorCellInvalid()) goto UseUserProvidedIterator;
typeswitch (source) {
case (sourceArray: JSArray): {
// Check that the iterator function is exactly
// Builtins::kArrayPrototypeValues.
if (!TaggedEqual(
iteratorFn.shared_function_info.function_data,
SmiConstant(kArrayPrototypeValues))) {
goto UseUserProvidedIterator;
}
// Source is a JSArray with unmodified iterator behavior. Use the
// source object directly, taking advantage of the special-case code
// in TypedArrayCopyElements
finalLength = Convert<uintptr>(sourceArray.length);
finalSource = source;
}
case (sourceTypedArray: JSTypedArray): {
const sourceBuffer = sourceTypedArray.buffer;
if (IsDetachedBuffer(sourceBuffer)) goto UseUserProvidedIterator;
// Check that the iterator function is exactly
// Builtins::kTypedArrayPrototypeValues.
if (!TaggedEqual(
iteratorFn.shared_function_info.function_data,
SmiConstant(kTypedArrayPrototypeValues)))
goto UseUserProvidedIterator;
// Source is a TypedArray with unmodified iterator behavior. Use the
// source object directly, taking advantage of the special-case code
// in TypedArrayCopyElements
finalLength = sourceTypedArray.length;
finalSource = source;
}
case (Object): {
goto UseUserProvidedIterator;
}
}
} label UseUserProvidedIterator {
// 6. If usingIterator is not undefined, then
// a. Let values be ? IterableToList(source, usingIterator).
// b. Let len be the number of elements in values.
const values: JSArray = IterableToList(source, usingIterator);
finalLength = Convert<uintptr>(values.length);
finalSource = values;
}
} label IteratorIsUndefined {
// 7. NOTE: source is not an Iterable so assume it is already an
// array-like object.
// 8. Let arrayLike be ! ToObject(source).
const arrayLike: JSReceiver = ToObject_Inline(context, source);
// 9. Let len be ? LengthOfArrayLike(arrayLike).
const length = GetLengthProperty(arrayLike);
try {
finalLength = ChangeSafeIntegerNumberToUintPtr(length)
otherwise IfInvalidLength;
finalSource = arrayLike;
} label IfInvalidLength deferred {
ThrowRangeError(MessageTemplate::kInvalidTypedArrayLength, length);
}
} label IteratorNotCallable(_value: JSAny) deferred {
ThrowTypeError(MessageTemplate::kIteratorSymbolNonCallable);
}
const finalLengthNum = Convert<Number>(finalLength);
// 6c/10. Let targetObj be ? TypedArrayCreate(C, «len»).
const targetObj =
TypedArrayCreateByLength(constructor, finalLengthNum, kBuiltinNameFrom);
if (!mapping) {
// Fast path.
if (finalLength != 0) {
// Call runtime.
TypedArrayCopyElements(context, targetObj, finalSource, finalLengthNum);
}
return targetObj;
}
// Slow path.
const mapfn: Callable = Cast<Callable>(mapfnObj) otherwise unreachable;
const accessor: TypedArrayAccessor =
GetTypedArrayAccessor(targetObj.elements_kind);
// 6d-6e and 11-12.
// 11. Let k be 0.
// 12. Repeat, while k < len
for (let k: uintptr = 0; k < finalLength; k++) {
// 12a. Let Pk be ! ToString(k).
const kNum = Convert<Number>(k);
// 12b. Let kValue be ? Get(arrayLike, Pk).
const kValue: JSAny = GetProperty(finalSource, kNum);
let mappedValue: JSAny;
// 12c. If mapping is true, then
if (mapping) {
// i. Let mappedValue be ? Call(mapfn, T, « kValue, k »).
mappedValue = Call(context, mapfn, thisArg, kValue, kNum);
} else {
// 12d. Else, let mappedValue be kValue.
mappedValue = kValue;
}
// 12e. Perform ? Set(targetObj, Pk, mappedValue, true).
// Buffer may be detached during executing ToNumber/ToBigInt.
accessor.StoreJSAny(context, targetObj, k, mappedValue)
otherwise IfDetached;
// 12f. Set k to k + 1. (done by the loop).
}
return targetObj;
} label NotConstructor deferred {
ThrowTypeError(MessageTemplate::kNotConstructor, receiver);
} label IfDetached deferred {
ThrowTypeError(MessageTemplate::kDetachedOperation, kBuiltinNameFrom);
}
}
}