array-join.tq 24.3 KB
Newer Older
1 2 3 4
// 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.

5
namespace array_join {
6 7 8 9
  type LoadJoinElementFn = builtin(Context, JSReceiver, Number) => Object;

  // Fast C call to write a fixed array (see Buffer.fixedArray) to a single
  // string.
10 11
  extern macro
  ArrayBuiltinsAssembler::CallJSArrayArrayJoinConcatToSequentialString(
12 13
      FixedArray, intptr, String, String): String;

14
  transitioning builtin LoadJoinElement<T: type>(
15 16 17 18
      context: Context, receiver: JSReceiver, k: Number): Object {
    return GetProperty(receiver, k);
  }

19
  LoadJoinElement<array::DictionaryElements>(
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
      context: Context, receiver: JSReceiver, k: Number): Object {
    const array: JSArray = UnsafeCast<JSArray>(receiver);
    const dict: NumberDictionary = UnsafeCast<NumberDictionary>(array.elements);
    try {
      return BasicLoadNumberDictionaryElement(dict, Signed(Convert<uintptr>(k)))
          otherwise IfNoData, IfHole;
    }
    label IfNoData deferred {
      return GetProperty(receiver, k);
    }
    label IfHole {
      return kEmptyString;
    }
  }

35
  LoadJoinElement<array::FastSmiOrObjectElements>(
36 37 38
      context: Context, receiver: JSReceiver, k: Number): Object {
    const array: JSArray = UnsafeCast<JSArray>(receiver);
    const fixedArray: FixedArray = UnsafeCast<FixedArray>(array.elements);
39
    const element: Object = fixedArray.objects[UnsafeCast<Smi>(k)];
40 41 42
    return element == Hole ? kEmptyString : element;
  }

43
  LoadJoinElement<array::FastDoubleElements>(
44 45 46 47
      context: Context, receiver: JSReceiver, k: Number): Object {
    const array: JSArray = UnsafeCast<JSArray>(receiver);
    const fixedDoubleArray: FixedDoubleArray =
        UnsafeCast<FixedDoubleArray>(array.elements);
48 49 50
    const element: float64 = LoadDoubleWithHoleCheck(
        fixedDoubleArray, UnsafeCast<Smi>(k)) otherwise return kEmptyString;
    return AllocateHeapNumberWithValue(element);
51 52
  }

53 54 55
  builtin LoadJoinTypedElement<T: type>(
      context: Context, receiver: JSReceiver, k: Number): Object {
    const typedArray: JSTypedArray = UnsafeCast<JSTypedArray>(receiver);
56
    assert(!IsDetachedBuffer(typedArray.buffer));
57 58 59 60 61
    return typed_array::LoadFixedTypedArrayElementAsTagged(
        typedArray.data_ptr, UnsafeCast<Smi>(k),
        typed_array::KindForArrayType<T>(), SMI_PARAMETERS);
  }

62
  transitioning builtin ConvertToLocaleString(
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
      context: Context, element: Object, locales: Object,
      options: Object): String {
    if (IsNullOrUndefined(element)) return kEmptyString;

    const prop: Object = GetProperty(element, 'toLocaleString');
    try {
      const callable: Callable = Cast<Callable>(prop) otherwise TypeError;
      let result: Object;
      if (IsNullOrUndefined(locales)) {
        result = Call(context, callable, element);
      } else if (IsNullOrUndefined(options)) {
        result = Call(context, callable, element, locales);
      } else {
        result = Call(context, callable, element, locales, options);
      }
      return ToString_Inline(context, result);
    }
    label TypeError {
81
      ThrowTypeError(kCalledNonCallable, prop);
82 83 84 85 86
    }
  }

  // Verifies the current element JSArray accessor can still be safely used
  // (see LoadJoinElement<ElementsAccessor>).
87
  macro CannotUseSameArrayAccessor<T: type>(implicit context: Context)(
88 89
      loadFn: LoadJoinElementFn, receiver: JSReceiver, originalMap: Map,
      originalLen: Number): never
90 91 92
      labels Cannot, Can;

  CannotUseSameArrayAccessor<JSArray>(implicit context: Context)(
93 94
      loadFn: LoadJoinElementFn, receiver: JSReceiver, originalMap: Map,
      originalLen: Number): never
95
      labels Cannot, Can {
96
    if (loadFn == LoadJoinElement<array::GenericElementsAccessor>) goto Can;
97

98 99 100 101 102 103 104
    const array: JSArray = UnsafeCast<JSArray>(receiver);
    if (originalMap != array.map) goto Cannot;
    if (originalLen != array.length) goto Cannot;
    if (IsNoElementsProtectorCellInvalid()) goto Cannot;
    goto Can;
  }

105
  CannotUseSameArrayAccessor<JSTypedArray>(implicit context: Context)(
106 107
      loadFn: LoadJoinElementFn, receiver: JSReceiver, initialMap: Map,
      initialLen: Number): never
108
      labels Cannot, Can {
109
    const typedArray: JSTypedArray = UnsafeCast<JSTypedArray>(receiver);
110
    if (IsDetachedBuffer(typedArray.buffer)) goto Cannot;
111 112 113
    goto Can;
  }

114 115 116
  // Calculates the running total length of the resulting string.  If the
  // calculated length exceeds the maximum string length (see
  // String::kMaxLength), throws a range error.
117 118
  macro AddStringLength(implicit context: Context)(lenA: intptr, lenB: intptr):
      intptr {
119 120 121 122 123 124
    try {
      const length: intptr = TryIntPtrAdd(lenA, lenB) otherwise IfOverflow;
      if (length > kStringMaxLength) goto IfOverflow;
      return length;
    }
    label IfOverflow deferred {
125
      ThrowInvalidStringLength(context);
126 127 128
    }
  }

129 130 131 132 133 134 135 136
  // Stores an element to a fixed array and return the fixed array. If the fixed
  // array is not large enough, create and return a new, larger fixed array that
  // contains all previously elements and the new element.
  macro StoreAndGrowFixedArray<T: type>(
      fixedArray: FixedArray, index: intptr, element: T): FixedArray {
    const length: intptr = fixedArray.length_intptr;
    assert(index <= length);
    if (index < length) {
137
      fixedArray.objects[index] = element;
138 139 140 141 142 143 144
      return fixedArray;
    } else
      deferred {
        const newLength: intptr = CalculateNewElementsCapacity(length);
        assert(index < newLength);
        const newfixedArray: FixedArray =
            ExtractFixedArray(fixedArray, 0, length, newLength, kFixedArrays);
145
        newfixedArray.objects[index] = element;
146 147 148 149
        return newfixedArray;
      }
  }

150 151
  // Contains the information necessary to create a single, separator delimited,
  // flattened one or two byte string.
152 153
  // The buffer is maintained and updated by Buffer.constructor, Buffer.Add(),
  // Buffer.AddSeparators().
154
  struct Buffer {
155 156 157 158 159 160 161
    Add(implicit context: Context)(
        str: String, nofSeparators: intptr, separatorLength: intptr) {
      // Add separators if necessary (at the beginning or more than one)
      const writeSeparators: bool = this.index == 0 | nofSeparators > 1;
      this.AddSeparators(nofSeparators, separatorLength, writeSeparators);

      this.totalStringLength =
162
          AddStringLength(this.totalStringLength, str.length_intptr);
163 164
      this.fixedArray =
          StoreAndGrowFixedArray(this.fixedArray, this.index++, str);
165 166
      this.isOneByte =
          IsOneByteStringInstanceType(str.instanceType) & this.isOneByte;
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
    }

    AddSeparators(implicit context: Context)(
        nofSeparators: intptr, separatorLength: intptr, write: bool) {
      if (nofSeparators == 0 || separatorLength == 0) return;

      const nofSeparatorsInt: intptr = nofSeparators;
      const sepsLen: intptr = separatorLength * nofSeparatorsInt;
      // Detect integer overflow
      // TODO(tebbi): Replace with overflow-checked multiplication.
      if (sepsLen / separatorLength != nofSeparatorsInt) deferred {
          ThrowInvalidStringLength(context);
        }

      this.totalStringLength = AddStringLength(this.totalStringLength, sepsLen);
      if (write) deferred {
          this.fixedArray = StoreAndGrowFixedArray(
              this.fixedArray, this.index++, Convert<Smi>(nofSeparatorsInt));
        }
    }

188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
    // Fixed array holding elements that are either:
    //   1) String result of `ToString(next)`.
    //   2) Smi representing the number of consecutive separators.
    // `BufferJoin()` will iterate and writes these entries to a flat string.
    //
    // To save space, reduce reads and writes, only separators at the beginning,
    // end, or more than one are written.
    //
    // No hole example
    //   receiver:   ['hello', 'world']
    //   fixedArray: ['hello', 'world']
    //
    // Hole example
    //   receiver:   [<hole>, 'hello', <hole>, 'world', <hole>]
    //   fixedArray: [1, 'hello', 2, 'world', 1]
    fixedArray: FixedArray;

    // Index to insert a new entry into `fixedArray`.
    index: intptr;

    // Running total of the resulting string length.
    totalStringLength: intptr;

    // `true` if the separator and all strings in the buffer are one-byte,
    // otherwise `false`.
    isOneByte: bool;
  }

216 217 218 219 220 221
  macro NewBuffer(len: uintptr, sep: String): Buffer {
    const cappedBufferSize: intptr = len > kMaxNewSpaceFixedArrayElements ?
        kMaxNewSpaceFixedArrayElements :
        Signed(len);
    assert(cappedBufferSize > 0);
    return Buffer{
222 223 224 225
      fixedArray: AllocateZeroedFixedArray(cappedBufferSize),
      index: 0,
      totalStringLength: 0,
      isOneByte: IsOneByteStringInstanceType(sep.instanceType)
226 227 228
    };
  }

229 230
  macro BufferJoin(implicit context: Context)(buffer: Buffer, sep: String):
      String {
231 232 233
    assert(IsValidPositiveSmi(buffer.totalStringLength));
    if (buffer.totalStringLength == 0) return kEmptyString;

234 235 236
    // Fast path when there's only one buffer element.
    if (buffer.index == 1) {
      const fixedArray: FixedArray = buffer.fixedArray;
237
      typeswitch (fixedArray.objects[0]) {
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
        // When the element is a string, just return it and completely avoid
        // allocating another string.
        case (str: String): {
          return str;
        }

        // When the element is a smi, use StringRepeat to quickly build a memory
        // efficient separator repeated string.
        case (nofSeparators: Number): {
          return StringRepeat(context, sep, nofSeparators);
        }
        case (obj: Object): {
          unreachable;
        }
      }
    }

255 256 257 258 259 260 261
    const length: uint32 = Convert<uint32>(Unsigned(buffer.totalStringLength));
    const r: String = buffer.isOneByte ? AllocateSeqOneByteString(length) :
                                         AllocateSeqTwoByteString(length);
    return CallJSArrayArrayJoinConcatToSequentialString(
        buffer.fixedArray, buffer.index, sep, r);
  }

262 263
  transitioning macro ArrayJoinImpl<T: type>(implicit context: Context)(
      receiver: JSReceiver, sep: String, lengthNumber: Number,
264
      useToLocaleString: constexpr bool, locales: Object, options: Object,
265
      initialLoadFn: LoadJoinElementFn): String {
266 267
    const initialMap: Map = receiver.map;
    const len: uintptr = Convert<uintptr>(lengthNumber);
268
    const separatorLength: intptr = sep.length_intptr;
269
    let nofSeparators: intptr = 0;
270
    let loadFn: LoadJoinElementFn = initialLoadFn;
271
    let buffer: Buffer = NewBuffer(len, sep);
272 273 274 275 276 277

    // 6. Let k be 0.
    let k: uintptr = 0;

    // 7. Repeat, while k < len
    while (k < len) {
278 279 280
      if (CannotUseSameArrayAccessor<T>(
              loadFn, receiver, initialMap, lengthNumber))
        deferred {
281
          loadFn = LoadJoinElement<array::GenericElementsAccessor>;
282 283
        }

284 285 286 287
      if (k > 0) {
        // a. If k > 0, let R be the string-concatenation of R and sep.
        nofSeparators = nofSeparators + 1;
      }
288

289
      // b. Let element be ? Get(O, ! ToString(k)).
290
      const element: Object = loadFn(context, receiver, Convert<Number>(k++));
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309

      // c. If element is undefined or null, let next be the empty String;
      //    otherwise, let next be ? ToString(element).
      let next: String;
      if constexpr (useToLocaleString) {
        next = ConvertToLocaleString(context, element, locales, options);
        if (next == kEmptyString) continue;
      } else {
        typeswitch (element) {
          case (str: String): {
            if (str == kEmptyString) continue;
            next = str;
          }
          case (num: Number): {
            next = NumberToString(num);
          }
          case (obj: HeapObject): {
            if (IsNullOrUndefined(obj)) continue;
            next = ToString(context, obj);
310 311
          }
        }
312
      }
313 314

      // d. Set R to the string-concatenation of R and next.
315
      buffer.Add(next, nofSeparators, separatorLength);
316
      nofSeparators = 0;
317 318 319
    }

    // Add any separators at the end.
320
    buffer.AddSeparators(nofSeparators, separatorLength, true);
321 322 323 324 325

    // 8. Return R.
    return BufferJoin(buffer, sep);
  }

326 327 328 329 330
  transitioning macro ArrayJoin<T: type>(implicit context: Context)(
      useToLocaleString: constexpr bool, receiver: JSReceiver, sep: String,
      lenNumber: Number, locales: Object, options: Object): Object;

  ArrayJoin<JSArray>(implicit context: Context)(
331
      useToLocaleString: constexpr bool, receiver: JSReceiver, sep: String,
332
      lenNumber: Number, locales: Object, options: Object): Object {
333 334
    const map: Map = receiver.map;
    const kind: ElementsKind = map.elements_kind;
335
    let loadFn: LoadJoinElementFn;
336 337 338 339

    try {
      const array: JSArray = Cast<JSArray>(receiver) otherwise IfSlowPath;
      if (array.length != lenNumber) goto IfSlowPath;
340
      if (!IsPrototypeInitialArrayPrototype(map)) goto IfSlowPath;
341 342 343
      if (IsNoElementsProtectorCellInvalid()) goto IfSlowPath;

      if (IsElementsKindLessThanOrEqual(kind, HOLEY_ELEMENTS)) {
344
        loadFn = LoadJoinElement<array::FastSmiOrObjectElements>;
345
      } else if (IsElementsKindLessThanOrEqual(kind, HOLEY_DOUBLE_ELEMENTS)) {
346
        loadFn = LoadJoinElement<array::FastDoubleElements>;
347 348 349 350 351 352 353 354 355 356 357 358 359
      } else if (kind == DICTIONARY_ELEMENTS)
        deferred {
          const dict: NumberDictionary =
              UnsafeCast<NumberDictionary>(array.elements);
          const nofElements: Smi = GetNumberDictionaryNumberOfElements(dict);
          if (nofElements == 0) {
            if (sep == kEmptyString) return kEmptyString;
            try {
              const nofSeparators: Smi =
                  Cast<Smi>(lenNumber - 1) otherwise IfNotSmi;
              return StringRepeat(context, sep, nofSeparators);
            }
            label IfNotSmi {
360
              ThrowInvalidStringLength(context);
361 362
            }
          } else {
363
            loadFn = LoadJoinElement<array::DictionaryElements>;
364 365 366
          }
        }
      else {
367 368 369 370
        goto IfSlowPath;
      }
    }
    label IfSlowPath {
371
      loadFn = LoadJoinElement<array::GenericElementsAccessor>;
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
    }
    return ArrayJoinImpl<JSArray>(
        receiver, sep, lenNumber, useToLocaleString, locales, options, loadFn);
  }

  ArrayJoin<JSTypedArray>(implicit context: Context)(
      useToLocaleString: constexpr bool, receiver: JSReceiver, sep: String,
      lenNumber: Number, locales: Object, options: Object): Object {
    const map: Map = receiver.map;
    const kind: ElementsKind = map.elements_kind;
    let loadFn: LoadJoinElementFn;

    if (IsElementsKindGreaterThan(kind, UINT32_ELEMENTS)) {
      if (kind == INT32_ELEMENTS) {
        loadFn = LoadJoinTypedElement<FixedInt32Array>;
      } else if (kind == FLOAT32_ELEMENTS) {
        loadFn = LoadJoinTypedElement<FixedFloat32Array>;
      } else if (kind == FLOAT64_ELEMENTS) {
        loadFn = LoadJoinTypedElement<FixedFloat64Array>;
      } else if (kind == UINT8_CLAMPED_ELEMENTS) {
        loadFn = LoadJoinTypedElement<FixedUint8ClampedArray>;
      } else if (kind == BIGUINT64_ELEMENTS) {
        loadFn = LoadJoinTypedElement<FixedBigUint64Array>;
      } else if (kind == BIGINT64_ELEMENTS) {
        loadFn = LoadJoinTypedElement<FixedBigInt64Array>;
      } else {
        unreachable;
      }
    } else {
      if (kind == UINT8_ELEMENTS) {
        loadFn = LoadJoinTypedElement<FixedUint8Array>;
      } else if (kind == INT8_ELEMENTS) {
        loadFn = LoadJoinTypedElement<FixedInt8Array>;
      } else if (kind == UINT16_ELEMENTS) {
        loadFn = LoadJoinTypedElement<FixedUint16Array>;
      } else if (kind == INT16_ELEMENTS) {
        loadFn = LoadJoinTypedElement<FixedInt16Array>;
      } else if (kind == UINT32_ELEMENTS) {
        loadFn = LoadJoinTypedElement<FixedUint32Array>;
      } else {
        unreachable;
      }
414
    }
415 416
    return ArrayJoinImpl<JSTypedArray>(
        receiver, sep, lenNumber, useToLocaleString, locales, options, loadFn);
417 418
  }

419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
  // The Join Stack detects cyclical calls to Array Join builtins
  // (Array.p.join(), Array.p.toString(), Array.p.toLocaleString()). This
  // FixedArray holds a stack of receivers to the current call.
  // CycleProtectedArrayJoin() is responsible for calling JoinStackPush and
  // JoinStackPop when visiting and leaving a receiver, respectively.
  const kMinJoinStackSize:
      constexpr int31 generates 'JSArray::kMinJoinStackSize';
  macro LoadJoinStack(implicit context: Context)(): FixedArray
      labels IfUninitialized {
    const nativeContext: NativeContext = LoadNativeContext(context);
    const stack: HeapObject =
        UnsafeCast<HeapObject>(nativeContext[ARRAY_JOIN_STACK_INDEX]);
    if (stack == Undefined) goto IfUninitialized;
    assert(IsFixedArray(stack));
    return UnsafeCast<FixedArray>(stack);
  }

  macro SetJoinStack(implicit context: Context)(stack: FixedArray): void {
    const nativeContext: NativeContext = LoadNativeContext(context);
    nativeContext[ARRAY_JOIN_STACK_INDEX] = stack;
  }

  // Adds a receiver to the stack. The FixedArray will automatically grow to
  // accommodate the receiver. If the receiver already exists on the stack,
  // this indicates a cyclical call and False is returned.
  builtin JoinStackPush(implicit context: Context)(
      stack: FixedArray, receiver: JSReceiver): Boolean {
    const capacity: intptr = stack.length_intptr;
    for (let i: intptr = 0; i < capacity; i++) {
448
      const previouslyVisited: Object = stack.objects[i];
449 450 451

      // Add `receiver` to the first open slot
      if (previouslyVisited == Hole) {
452
        stack.objects[i] = receiver;
453 454 455 456 457 458 459 460 461
        return True;
      }

      // Detect cycles
      if (receiver == previouslyVisited) return False;
    }

    // If no open slots were found, grow the stack and add receiver to the end.
    const newStack: FixedArray =
462
        StoreAndGrowFixedArray(stack, capacity, receiver);
463 464 465 466
    SetJoinStack(newStack);
    return True;
  }

467 468 469 470 471 472
  // Fast path the common non-nested calls. If the receiver is not already on
  // the stack, add it to the stack and go to ReceiverAdded. Otherwise go to
  // ReceiverNotAdded.
  macro JoinStackPushInline(implicit context: Context)(receiver: JSReceiver):
      never
      labels ReceiverAdded, ReceiverNotAdded {
473 474 475
    try {
      const stack: FixedArray = LoadJoinStack()
          otherwise IfUninitialized;
476 477
      if (stack.objects[0] == Hole) {
        stack.objects[0] = receiver;
478 479
      } else if (JoinStackPush(stack, receiver) == False)
        deferred {
480
          goto ReceiverNotAdded;
481 482 483 484 485
        }
    }
    label IfUninitialized {
      const stack: FixedArray =
          AllocateFixedArrayWithHoles(kMinJoinStackSize, kNone);
486
      stack.objects[0] = receiver;
487 488
      SetJoinStack(stack);
    }
489
    goto ReceiverAdded;
490 491 492 493 494 495 496 497
  }

  // Removes a receiver from the stack. The FixedArray will automatically shrink
  // to Heap::kMinJoinStackSize once the stack becomes empty.
  builtin JoinStackPop(implicit context: Context)(
      stack: FixedArray, receiver: JSReceiver): Object {
    const len: intptr = stack.length_intptr;
    for (let i: intptr = 0; i < len; i++) {
498
      if (stack.objects[i] == receiver) {
499 500 501 502 503 504 505 506
        // Shrink the Join Stack if the stack will be empty and is larger than
        // the minimum size.
        if (i == 0 && len > kMinJoinStackSize) deferred {
            const newStack: FixedArray =
                AllocateFixedArrayWithHoles(kMinJoinStackSize, kNone);
            SetJoinStack(newStack);
          }
        else {
507
          stack.objects[i] = Hole;
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522
        }
        return Undefined;
      }
    }
    unreachable;
  }

  // Fast path the common non-nested calls.
  macro JoinStackPopInline(implicit context: Context)(receiver: JSReceiver) {
    const stack: FixedArray = LoadJoinStack()
        otherwise unreachable;
    const len: intptr = stack.length_intptr;

    // Builtin call was not nested (receiver is the first entry) and
    // did not contain other nested arrays that expanded the stack.
523
    if (stack.objects[0] == receiver && len == kMinJoinStackSize) {
524 525 526 527 528 529 530 531
      StoreFixedArrayElement(stack, 0, Hole, SKIP_WRITE_BARRIER);
    } else
      deferred {
        JoinStackPop(stack, receiver);
      }
  }

  // Main entry point for all builtins using Array Join functionality.
532 533 534 535
  transitioning macro CycleProtectedArrayJoin<T: type>(implicit context:
                                                            Context)(
      useToLocaleString: constexpr bool, o: JSReceiver, len: Number,
      sepObj: Object, locales: Object, options: Object): Object {
536 537
    // 3. If separator is undefined, let sep be the single-element String ",".
    // 4. Else, let sep be ? ToString(separator).
538 539
    let sep: String =
        sepObj == Undefined ? ',' : ToString_Inline(context, sepObj);
540

541 542 543 544 545
    // If the receiver is not empty and not already being joined, continue with
    // the normal join algorithm.
    if (len > 0 && JoinStackPushInline(o)) {
      try {
        const result: Object =
546
            ArrayJoin<T>(useToLocaleString, o, sep, len, locales, options);
547 548 549 550 551 552 553
        JoinStackPopInline(o);
        return result;
      } catch (e) deferred {
        JoinStackPopInline(o);
        ReThrow(context, e);
      }
    } else {
554 555 556 557 558
      return kEmptyString;
    }
  }

  // https://tc39.github.io/ecma262/#sec-array.prototype.join
559 560
  transitioning javascript builtin
  ArrayPrototypeJoin(context: Context, receiver: Object, ...arguments): Object {
561
    const separator: Object = arguments[0];
562 563 564 565 566 567 568 569 570

    // 1. Let O be ? ToObject(this value).
    const o: JSReceiver = ToObject_Inline(context, receiver);

    // 2. Let len be ? ToLength(? Get(O, "length")).
    const len: Number = GetLengthProperty(o);

    // Only handle valid array lengths. Although the spec allows larger values,
    // this matches historical V8 behavior.
571
    if (len > kMaxArrayIndex + 1) ThrowTypeError(kInvalidArrayLength);
572 573 574

    return CycleProtectedArrayJoin<JSArray>(
        false, o, len, separator, Undefined, Undefined);
575 576
  }

577
  // https://tc39.github.io/ecma262/#sec-array.prototype.tolocalestring
578
  transitioning javascript builtin ArrayPrototypeToLocaleString(
579 580 581
      context: Context, receiver: Object, ...arguments): Object {
    const locales: Object = arguments[0];
    const options: Object = arguments[1];
582 583 584 585 586 587 588 589 590

    // 1. Let O be ? ToObject(this value).
    const o: JSReceiver = ToObject_Inline(context, receiver);

    // 2. Let len be ? ToLength(? Get(O, "length")).
    const len: Number = GetLengthProperty(o);

    // Only handle valid array lengths. Although the spec allows larger values,
    // this matches historical V8 behavior.
591
    if (len > kMaxArrayIndex + 1) ThrowTypeError(kInvalidArrayLength);
592 593 594

    return CycleProtectedArrayJoin<JSArray>(
        true, o, len, ',', locales, options);
595 596
  }

597
  // https://tc39.github.io/ecma262/#sec-array.prototype.tostring
598
  transitioning javascript builtin ArrayPrototypeToString(
599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
      context: Context, receiver: Object, ...arguments): Object {
    // 1. Let array be ? ToObject(this value).
    const array: JSReceiver = ToObject_Inline(context, receiver);

    // 2. Let func be ? Get(array, "join").
    const prop: Object = GetProperty(array, 'join');
    try {
      // 3. If IsCallable(func) is false, let func be the intrinsic function
      //    %ObjProto_toString%.
      const func: Callable = Cast<Callable>(prop) otherwise NotCallable;

      // 4. Return ? Call(func, array).
      return Call(context, func, array);
    }
    label NotCallable {
      return ObjectToString(context, array);
    }
  }
617 618 619 620 621 622 623 624 625 626

  // https://tc39.github.io/ecma262/#sec-%typedarray%.prototype.join
  transitioning javascript builtin TypedArrayPrototypeJoin(
      context: Context, receiver: Object, ...arguments): Object {
    const separator: Object = arguments[0];

    // Spec: ValidateTypedArray is applied to the this value prior to evaluating
    // the algorithm.
    const typedArray: JSTypedArray = typed_array::ValidateTypedArray(
        context, receiver, '%TypedArray%.prototype.join');
627
    const length = Convert<Number>(typedArray.length);
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642

    return CycleProtectedArrayJoin<JSTypedArray>(
        false, typedArray, length, separator, Undefined, Undefined);
  }

  // https://tc39.github.io/ecma262/#sec-%typedarray%.prototype.tolocalestring
  transitioning javascript builtin TypedArrayPrototypeToLocaleString(
      context: Context, receiver: Object, ...arguments): Object {
    const locales: Object = arguments[0];
    const options: Object = arguments[1];

    // Spec: ValidateTypedArray is applied to the this value prior to evaluating
    // the algorithm.
    const typedArray: JSTypedArray = typed_array::ValidateTypedArray(
        context, receiver, '%TypedArray%.prototype.toLocaleString');
643
    const length = Convert<Number>(typedArray.length);
644 645 646 647

    return CycleProtectedArrayJoin<JSTypedArray>(
        true, typedArray, length, ',', locales, options);
  }
648
}