typed-array-from.tq 8.29 KB
Newer Older
1 2 3 4 5 6 7
// 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 {
8 9
const kBuiltinNameFrom: constexpr string = '%TypedArray%.from';

10
type BuiltinsName extends int31 constexpr 'Builtin';
11
const kTypedArrayPrototypeValues: constexpr BuiltinsName
12
    generates 'Builtin::kTypedArrayPrototypeValues';
13
const kArrayPrototypeValues: constexpr BuiltinsName
14
    generates 'Builtin::kArrayPrototypeValues';
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39

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

41 42 43 44 45 46 47 48
    // 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.
49

50 51
    let finalLength: uintptr;
    let finalSource: JSAny;
52 53

    try {
54 55 56 57 58
      // 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;
59 60

      try {
61 62 63
        // 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).
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
        //
        // 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.

79 80 81 82
        // If there is a mapping, we need to gather the values from the
        // iterables before applying the mapping.
        if (mapping) goto UseUserProvidedIterator;

83 84 85 86 87 88 89
        const iteratorFn =
            Cast<JSFunction>(usingIterator) otherwise UseUserProvidedIterator;

        // Check that the ArrayIterator prototype's "next" method hasn't been
        // overridden.
        if (IsArrayIteratorProtectorCellInvalid()) goto UseUserProvidedIterator;

90 91 92
        typeswitch (source) {
          case (sourceArray: JSArray): {
            // Check that the iterator function is exactly
93
            // Builtin::kArrayPrototypeValues.
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
            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
111
            // Builtin::kTypedArrayPrototypeValues.
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
            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;
          }
        }
127 128 129 130 131 132 133 134
      } 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;
135
      }
136 137 138
    } label IteratorIsUndefined {
      // 7. NOTE: source is not an Iterable so assume it is already an
      // array-like object.
139

140 141
      // 8. Let arrayLike be ! ToObject(source).
      const arrayLike: JSReceiver = ToObject_Inline(context, source);
142

143 144
      // 9. Let len be ? LengthOfArrayLike(arrayLike).
      const length = GetLengthProperty(arrayLike);
145

146 147 148 149 150 151
      try {
        finalLength = ChangeSafeIntegerNumberToUintPtr(length)
            otherwise IfInvalidLength;
        finalSource = arrayLike;
      } label IfInvalidLength deferred {
        ThrowRangeError(MessageTemplate::kInvalidTypedArrayLength, length);
152
      }
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
    } 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);
168 169 170
      }
      return targetObj;
    }
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
    // 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);
209 210
  }
}
211
}