typed-array-createtypedarray.tq 15.4 KB
Newer Older
1 2 3 4
// 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.

5 6
#include 'src/builtins/builtins-constructor-gen.h'

7
namespace typed_array_createtypedarray {
8 9
  extern builtin IterableToListMayPreserveHoles(Context, Object, Callable):
      JSArray;
10

11 12
  extern macro ConstructorBuiltinsAssembler::EmitFastNewObject(
      implicit context: Context)(JSFunction, JSReceiver): JSTypedArray;
13 14 15 16
  extern macro TypedArrayBuiltinsAssembler::AllocateEmptyOnHeapBuffer(
      implicit context: Context)(JSTypedArray, uintptr): JSArrayBuffer;
  extern macro TypedArrayBuiltinsAssembler::AllocateOnHeapElements(
      Map, intptr, Number): FixedTypedArrayBase;
17 18
  extern macro TypedArrayBuiltinsAssembler::GetDefaultConstructor(
      implicit context: Context)(JSTypedArray): JSFunction;
19 20
  extern macro TypedArrayBuiltinsAssembler::IsSharedArrayBuffer(JSArrayBuffer):
      bool;
21
  extern macro TypedArrayBuiltinsAssembler::SetupTypedArray(
22
      JSTypedArray, Smi, uintptr, uintptr): void;
23

24 25
  extern runtime ThrowInvalidTypedArrayAlignment(implicit context: Context)(
      Map, String): never;
26 27 28
  extern runtime TypedArrayCopyElements(Context, JSTypedArray, Object, Number):
      void;

29 30 31 32 33 34
  macro CalculateTotalElementsByteSize(byteLength: intptr): intptr {
    return (kFixedTypedArrayBaseHeaderSize + kObjectAlignmentMask +
            byteLength) &
        ~kObjectAlignmentMask;
  }

35 36 37
  transitioning macro TypedArrayInitialize(implicit context: Context)(
      initialize: constexpr bool, typedArray: JSTypedArray, length: PositiveSmi,
      elementsInfo: typed_array::TypedArrayElementsInfo,
38
      bufferConstructor: JSReceiver): uintptr {
39 40
    const byteLength = elementsInfo.CalculateByteLength(length)
        otherwise ThrowRangeError(kInvalidArrayBufferLength);
41
    const byteLengthNum = Convert<Number>(byteLength);
42 43 44 45
    const defaultConstructor = GetArrayBufferFunction();

    try {
      if (bufferConstructor != defaultConstructor) {
46
        goto AttachOffHeapBuffer(ConstructWithTarget(
47 48 49 50 51 52 53 54 55
            defaultConstructor, bufferConstructor, byteLengthNum));
      }

      if (byteLength > V8_TYPED_ARRAY_MAX_SIZE_IN_HEAP) goto AllocateOffHeap;

      AllocateEmptyOnHeapBuffer(typedArray, byteLength);

      const totalSize =
          CalculateTotalElementsByteSize(Convert<intptr>(byteLength));
56 57
      const elements =
          AllocateOnHeapElements(elementsInfo.map, totalSize, length);
58 59
      typedArray.elements = elements;

60
      if constexpr (initialize) {
61 62 63 64 65
        const backingStore = LoadFixedTypedArrayOnHeapBackingStore(elements);
        typed_array::CallCMemset(backingStore, 0, byteLength);
      }
    }
    label AllocateOffHeap {
66
      if constexpr (initialize) {
67
        goto AttachOffHeapBuffer(Construct(defaultConstructor, byteLengthNum));
68
      } else {
69
        goto AttachOffHeapBuffer(Call(
70 71 72
            context, GetArrayBufferNoInitFunction(), Undefined, byteLengthNum));
      }
    }
73
    label AttachOffHeapBuffer(bufferObj: Object) {
74
      const buffer = Cast<JSArrayBuffer>(bufferObj) otherwise unreachable;
75
      const byteOffset: uintptr = 0;
76 77
      typedArray.AttachOffHeapBuffer(
          buffer, elementsInfo.map, length, byteOffset);
78 79 80 81 82
    }

    const byteOffset: uintptr = 0;
    SetupTypedArray(typedArray, length, byteOffset, byteLength);

83
    return byteLength;
84 85
  }

86 87
  // 22.2.4.2 TypedArray ( length )
  // ES #sec-typedarray-length
88
  transitioning macro ConstructByLength(implicit context: Context)(
89
      typedArray: JSTypedArray, length: Object,
90
      elementsInfo: typed_array::TypedArrayElementsInfo): void {
91 92 93 94 95 96 97
    const convertedLength: Number =
        ToInteger_Inline(context, length, kTruncateMinusZero);
    // The maximum length of a TypedArray is MaxSmi().
    // Note: this is not per spec, but rather a constraint of our current
    // representation (which uses Smis).
    // TODO(7881): support larger-than-smi typed array lengths
    const positiveLength: PositiveSmi = Cast<PositiveSmi>(convertedLength)
98
        otherwise ThrowRangeError(kInvalidTypedArrayLength, length);
99
    const defaultConstructor: Constructor = GetArrayBufferFunction();
100
    const initialize: constexpr bool = true;
101
    TypedArrayInitialize(
102 103
        initialize, typedArray, positiveLength, elementsInfo,
        defaultConstructor);
104
  }
105 106 107

  // 22.2.4.4 TypedArray ( object )
  // ES #sec-typedarray-object
108
  transitioning macro ConstructByArrayLike(implicit context: Context)(
109
      typedArray: JSTypedArray, arrayLike: HeapObject, initialLength: Object,
110
      elementsInfo: typed_array::TypedArrayElementsInfo,
111
      bufferConstructor: JSReceiver): void {
112 113
    // The caller has looked up length on arrayLike, which is observable.
    const length: PositiveSmi = ToSmiLength(initialLength)
114
        otherwise ThrowRangeError(kInvalidTypedArrayLength, initialLength);
115
    const initialize: constexpr bool = false;
116
    const byteLength = TypedArrayInitialize(
117
        initialize, typedArray, length, elementsInfo, bufferConstructor);
118 119 120 121 122

    try {
      const src: JSTypedArray = Cast<JSTypedArray>(arrayLike) otherwise IfSlow;

      if (IsDetachedBuffer(src.buffer)) {
123
        ThrowTypeError(kDetachedOperation, 'Construct');
124

125
      } else if (src.elements_kind != elementsInfo.kind) {
126 127 128
        goto IfSlow;

      } else if (length > 0) {
129 130
        assert(byteLength <= kTypedArrayMaxByteLength);
        typed_array::CallCMemcpy(typedArray.data_ptr, src.data_ptr, byteLength);
131 132 133 134 135 136 137 138
      }
    }
    label IfSlow deferred {
      if (length > 0) {
        TypedArrayCopyElements(context, typedArray, arrayLike, length);
      }
    }
  }
139 140 141

  // 22.2.4.4 TypedArray ( object )
  // ES #sec-typedarray-object
142
  transitioning macro ConstructByIterable(implicit context: Context)(
143
      typedArray: JSTypedArray, iterable: JSReceiver, iteratorFn: Callable,
144 145
      elementsInfo: typed_array::TypedArrayElementsInfo): never
      labels IfConstructByArrayLike(HeapObject, Object, JSReceiver) {
146 147
    const array: JSArray =
        IterableToListMayPreserveHoles(context, iterable, iteratorFn);
148
    goto IfConstructByArrayLike(array, array.length, GetArrayBufferFunction());
149 150 151 152
  }

  // 22.2.4.3 TypedArray ( typedArray )
  // ES #sec-typedarray-typedarray
153
  transitioning macro ConstructByTypedArray(implicit context: Context)(
154
      typedArray: JSTypedArray, srcTypedArray: JSTypedArray,
155 156
      elementsInfo: typed_array::TypedArrayElementsInfo): never
      labels IfConstructByArrayLike(HeapObject, Object, JSReceiver) {
157 158 159 160 161 162 163 164 165 166 167 168 169
    let bufferConstructor: JSReceiver = GetArrayBufferFunction();
    const srcBuffer: JSArrayBuffer = srcTypedArray.buffer;
    // TODO(petermarshall): Throw on detached typedArray.
    let length: Smi = IsDetachedBuffer(srcBuffer) ? 0 : srcTypedArray.length;

    // The spec requires that constructing a typed array using a SAB-backed
    // typed array use the ArrayBuffer constructor, not the species constructor.
    // See https://tc39.github.io/ecma262/#sec-typedarray-typedarray.
    if (!IsSharedArrayBuffer(srcBuffer)) {
      bufferConstructor = SpeciesConstructor(srcBuffer, bufferConstructor);
      // TODO(petermarshall): Throw on detached typedArray.
      if (IsDetachedBuffer(srcBuffer)) length = 0;
    }
170
    goto IfConstructByArrayLike(srcTypedArray, length, bufferConstructor);
171
  }
172 173 174 175 176

  // 22.2.4.5 TypedArray ( buffer, byteOffset, length )
  // ES #sec-typedarray-buffer-byteoffset-length
  macro ConstructByArrayBuffer(implicit context: Context)(
      typedArray: JSTypedArray, buffer: JSArrayBuffer, byteOffset: Object,
177
      length: Object, elementsInfo: typed_array::TypedArrayElementsInfo): void {
178
    try {
179
      let offset: uintptr = 0;
180 181
      if (byteOffset != Undefined) {
        // 6. Let offset be ? ToIndex(byteOffset).
182 183 184
        offset = TryNumberToUintPtr(
            ToInteger_Inline(context, byteOffset, kTruncateMinusZero))
            otherwise goto IfInvalidOffset;
185 186

        // 7. If offset modulo elementSize ≠ 0, throw a RangeError exception.
187
        if (elementsInfo.IsUnaligned(offset)) {
188 189 190 191 192
          goto IfInvalidAlignment('start offset');
        }
      }

      let newLength: PositiveSmi = 0;
193
      let newByteLength: uintptr;
194 195 196 197 198 199 200 201
      // 8. If length is present and length is not undefined, then
      if (length != Undefined) {
        // a. Let newLength be ? ToIndex(length).
        newLength = ToSmiIndex(length) otherwise IfInvalidLength;
      }

      // 9. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
      if (IsDetachedBuffer(buffer)) {
202
        ThrowTypeError(kDetachedOperation, 'Construct');
203 204 205
      }

      // 10. Let bufferByteLength be buffer.[[ArrayBufferByteLength]].
206
      const bufferByteLength: uintptr = buffer.byte_length;
207 208 209 210 211

      // 11. If length is either not present or undefined, then
      if (length == Undefined) {
        // a. If bufferByteLength modulo elementSize ≠ 0, throw a RangeError
        // exception.
212
        if (elementsInfo.IsUnaligned(bufferByteLength)) {
213 214 215 216 217 218 219 220 221
          goto IfInvalidAlignment('byte length');
        }

        // b. Let newByteLength be bufferByteLength - offset.
        // c. If newByteLength < 0, throw a RangeError exception.
        if (bufferByteLength < offset) goto IfInvalidOffset;

        // Spec step 16 length calculated here to avoid recalculating the length
        // in the step 12 branch.
222 223 224
        newByteLength = bufferByteLength - offset;
        newLength = elementsInfo.CalculateLength(newByteLength)
            otherwise IfInvalidOffset;
225 226 227 228

        // 12. Else,
      } else {
        // a. Let newByteLength be newLength × elementSize.
229 230
        newByteLength = elementsInfo.CalculateByteLength(newLength)
            otherwise IfInvalidByteLength;
231 232 233

        // b. If offset + newByteLength > bufferByteLength, throw a RangeError
        // exception.
234 235 236
        if ((bufferByteLength < newByteLength) ||
            (offset > bufferByteLength - newByteLength))
          goto IfInvalidLength;
237 238
      }

239
      SetupTypedArray(typedArray, newLength, offset, newByteLength);
240 241
      typedArray.AttachOffHeapBuffer(
          buffer, elementsInfo.map, newLength, offset);
242 243 244 245
    }
    label IfInvalidAlignment(problemString: String) deferred {
      ThrowInvalidTypedArrayAlignment(typedArray.map, problemString);
    }
246
    label IfInvalidByteLength deferred {
247
      ThrowRangeError(kInvalidArrayBufferLength);
248
    }
249
    label IfInvalidLength deferred {
250
      ThrowRangeError(kInvalidTypedArrayLength, length);
251 252
    }
    label IfInvalidOffset deferred {
253
      ThrowRangeError(kInvalidOffset, byteOffset);
254 255
    }
  }
256 257

  transitioning macro ConstructByJSReceiver(implicit context: Context)(
258
      array: JSTypedArray, obj: JSReceiver,
259 260
      elementsInfo: typed_array::TypedArrayElementsInfo): never
      labels IfConstructByArrayLike(HeapObject, Object, JSReceiver) {
261 262 263 264
    try {
      const iteratorMethod: Object =
          GetIteratorMethod(obj) otherwise IfIteratorUndefined;
      const iteratorFn: Callable = Cast<Callable>(iteratorMethod)
265
          otherwise ThrowTypeError(kIteratorSymbolNonCallable);
266 267
      ConstructByIterable(array, obj, iteratorFn, elementsInfo)
          otherwise IfConstructByArrayLike;
268 269 270 271 272
    }
    label IfIteratorUndefined {
      const lengthObj: Object = GetProperty(obj, kLengthString);
      const length: Smi = ToSmiLength(lengthObj)
          otherwise goto IfInvalidLength(lengthObj);
273
      goto IfConstructByArrayLike(obj, length, GetArrayBufferFunction());
274 275
    }
    label IfInvalidLength(length: Object) {
276
      ThrowRangeError(kInvalidTypedArrayLength, length);
277 278 279 280 281 282 283
    }
  }

  // 22.2.4 The TypedArray Constructors
  // ES #sec-typedarray-constructors
  transitioning builtin CreateTypedArray(
      context: Context, target: JSFunction, newTarget: JSReceiver, arg1: Object,
284
      arg2: Object, arg3: Object): JSTypedArray {
285 286 287 288 289 290 291 292 293 294 295 296 297 298
    assert(IsConstructor(target));
    // 4. Let O be ? AllocateTypedArray(constructorName, NewTarget,
    // "%TypedArrayPrototype%").
    const array: JSTypedArray = EmitFastNewObject(target, newTarget);
    // We need to set the byte_offset / byte_length to some sane values
    // to keep the heap verifier happy.
    // TODO(bmeurer): Fix this initialization to not use EmitFastNewObject,
    // which causes the problem, since it puts Undefined into all slots of
    // the object even though that doesn't make any sense for these fields.
    array.byte_offset = 0;
    array.byte_length = 0;

    // 5. Let elementSize be the Number value of the Element Size value in Table
    // 56 for constructorName.
299 300
    const elementsInfo: typed_array::TypedArrayElementsInfo =
        typed_array::GetTypedArrayElementsInfo(array);
301 302 303 304 305 306 307

    try {
      typeswitch (arg1) {
        case (length: Smi): {
          goto IfConstructByLength(length);
        }
        case (buffer: JSArrayBuffer): {
308
          ConstructByArrayBuffer(array, buffer, arg2, arg3, elementsInfo);
309 310
        }
        case (typedArray: JSTypedArray): {
311 312
          ConstructByTypedArray(array, typedArray, elementsInfo)
              otherwise IfConstructByArrayLike;
313 314
        }
        case (obj: JSReceiver): {
315 316
          ConstructByJSReceiver(array, obj, elementsInfo)
              otherwise IfConstructByArrayLike;
317 318 319 320 321 322 323 324 325
        }
        // 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: HeapObject): {
          goto IfConstructByLength(lengthObj);
        }
      }
    }
    label IfConstructByLength(length: Object) {
326
      ConstructByLength(array, length, elementsInfo);
327
    }
328 329 330 331 332
    label IfConstructByArrayLike(
        arrayLike: HeapObject, length: Object, bufferConstructor: JSReceiver) {
      ConstructByArrayLike(
          array, arrayLike, length, elementsInfo, bufferConstructor);
    }
333 334
    return array;
  }
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372

  transitioning macro TypedArraySpeciesCreate(implicit context: Context)(
      methodName: constexpr string, numArgs: constexpr int31,
      exemplar: JSTypedArray, arg0: Object, arg1: Object,
      arg2: Object): 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
      assert(!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: Object = Undefined;
      if constexpr (numArgs == 1) {
        newObj = Construct(constructor, arg0);
      } else {
        assert(numArgs == 3);
        newObj = Construct(constructor, arg0, arg1, arg2);
      }

      return typed_array::ValidateTypedArray(context, newObj, methodName);
    }
  }
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387

  transitioning macro TypedArraySpeciesCreateByLength(implicit context:
                                                          Context)(
      methodName: constexpr string, exemplar: JSTypedArray,
      length: Smi): JSTypedArray {
    assert(Is<PositiveSmi>(length));
    const numArgs: constexpr int31 = 1;
    const typedArray: JSTypedArray = TypedArraySpeciesCreate(
        methodName, numArgs, exemplar, length, Undefined, Undefined);
    if (typedArray.length < length) deferred {
        ThrowTypeError(kTypedArrayTooShort);
      }

    return typedArray;
  }
388
}