// 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); } } }