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
  type LoadJoinElementFn = builtin(Context, JSReceiver, Number) => JSAny;
7 8 9

  // 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
      context: Context, receiver: JSReceiver, k: Number): JSAny {
16 17 18
    return GetProperty(receiver, k);
  }

19
  transitioning LoadJoinElement<array::DictionaryElements>(
20
      context: Context, receiver: JSReceiver, k: Number): JSAny {
21 22 23 24 25 26 27 28 29 30 31 32 33 34
    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
      context: Context, receiver: JSReceiver, k: Number): JSAny {
37 38
    const array: JSArray = UnsafeCast<JSArray>(receiver);
    const fixedArray: FixedArray = UnsafeCast<FixedArray>(array.elements);
39
    const element: Object = fixedArray.objects[UnsafeCast<Smi>(k)];
40
    return element == TheHole ? kEmptyString : UnsafeCast<JSAny>(element);
41 42
  }

43
  LoadJoinElement<array::FastDoubleElements>(
44
      context: Context, receiver: JSReceiver, k: Number): JSAny {
45 46 47
    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
  builtin LoadJoinTypedElement<T: type>(
54
      context: Context, receiver: JSReceiver, k: Number): JSAny {
55
    const typedArray: JSTypedArray = UnsafeCast<JSTypedArray>(receiver);
56
    assert(!IsDetachedBuffer(typedArray.buffer));
57 58
    return typed_array::LoadFixedTypedArrayElementAsTagged(
        typedArray.data_ptr, UnsafeCast<Smi>(k),
59
        typed_array::KindForArrayType<T>());
60 61
  }

62
  transitioning builtin ConvertToLocaleString(
63 64
      context: Context, element: JSAny, locales: JSAny,
      options: JSAny): String {
65 66
    if (IsNullOrUndefined(element)) return kEmptyString;

67
    const prop: JSAny = GetProperty(element, 'toLocaleString');
68 69
    try {
      const callable: Callable = Cast<Callable>(prop) otherwise TypeError;
70
      let result: JSAny;
71 72 73 74 75 76 77 78 79 80
      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
      loadFn: LoadJoinElementFn, receiver: JSReceiver, originalMap: Map,
89
      originalLen: Number): bool;
90 91

  CannotUseSameArrayAccessor<JSArray>(implicit context: Context)(
92
      loadFn: LoadJoinElementFn, receiver: JSReceiver, originalMap: Map,
93 94
      originalLen: Number): bool {
    if (loadFn == LoadJoinElement<array::GenericElementsAccessor>) return false;
95

96
    const array: JSArray = UnsafeCast<JSArray>(receiver);
97 98 99 100
    if (originalMap != array.map) return true;
    if (originalLen != array.length) return true;
    if (IsNoElementsProtectorCellInvalid()) return true;
    return false;
101 102
  }

103
  CannotUseSameArrayAccessor<JSTypedArray>(implicit context: Context)(
104
      _loadFn: LoadJoinElementFn, receiver: JSReceiver, _initialMap: Map,
105
      _initialLen: Number): bool {
106
    const typedArray: JSTypedArray = UnsafeCast<JSTypedArray>(receiver);
107
    return IsDetachedBuffer(typedArray.buffer);
108 109
  }

110 111 112
  // 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.
113 114
  macro AddStringLength(implicit context: Context)(lenA: intptr, lenB: intptr):
      intptr {
115 116 117 118 119 120
    try {
      const length: intptr = TryIntPtrAdd(lenA, lenB) otherwise IfOverflow;
      if (length > kStringMaxLength) goto IfOverflow;
      return length;
    }
    label IfOverflow deferred {
121
      ThrowInvalidStringLength(context);
122 123 124
    }
  }

125 126 127 128 129 130 131 132
  // 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) {
133
      fixedArray.objects[index] = element;
134 135 136 137 138 139 140
      return fixedArray;
    } else
      deferred {
        const newLength: intptr = CalculateNewElementsCapacity(length);
        assert(index < newLength);
        const newfixedArray: FixedArray =
            ExtractFixedArray(fixedArray, 0, length, newLength, kFixedArrays);
141
        newfixedArray.objects[index] = element;
142 143 144 145
        return newfixedArray;
      }
  }

146 147
  // Contains the information necessary to create a single, separator delimited,
  // flattened one or two byte string.
148 149
  // The buffer is maintained and updated by Buffer.constructor, Buffer.Add(),
  // Buffer.AddSeparators().
150
  struct Buffer {
151 152 153 154 155 156 157
    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 =
158
          AddStringLength(this.totalStringLength, str.length_intptr);
159 160
      this.fixedArray =
          StoreAndGrowFixedArray(this.fixedArray, this.index++, str);
161 162
      this.isOneByte =
          IsOneByteStringInstanceType(str.instanceType) & this.isOneByte;
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
    }

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

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 209 210 211
    // 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;
  }

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

225 226
  macro BufferJoin(implicit context: Context)(buffer: Buffer, sep: String):
      String {
227 228 229
    assert(IsValidPositiveSmi(buffer.totalStringLength));
    if (buffer.totalStringLength == 0) return kEmptyString;

230 231 232
    // Fast path when there's only one buffer element.
    if (buffer.index == 1) {
      const fixedArray: FixedArray = buffer.fixedArray;
233
      typeswitch (fixedArray.objects[0]) {
234 235 236 237 238 239 240 241 242 243 244
        // 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);
        }
245
        case (Object): {
246 247 248 249 250
          unreachable;
        }
      }
    }

251 252 253 254 255 256 257
    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);
  }

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

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

    // 7. Repeat, while k < len
    while (k < len) {
274 275 276
      if (CannotUseSameArrayAccessor<T>(
              loadFn, receiver, initialMap, lengthNumber))
        deferred {
277
          loadFn = LoadJoinElement<array::GenericElementsAccessor>;
278 279
        }

280 281 282 283
      if (k > 0) {
        // a. If k > 0, let R be the string-concatenation of R and sep.
        nofSeparators = nofSeparators + 1;
      }
284

285
      // b. Let element be ? Get(O, ! ToString(k)).
286
      const element: JSAny = loadFn(context, receiver, Convert<Number>(k++));
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302

      // 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);
          }
303
          case (obj: JSAny): {
304 305
            if (IsNullOrUndefined(obj)) continue;
            next = ToString(context, obj);
306 307
          }
        }
308
      }
309 310

      // d. Set R to the string-concatenation of R and next.
311
      buffer.Add(next, nofSeparators, separatorLength);
312
      nofSeparators = 0;
313 314 315
    }

    // Add any separators at the end.
316
    buffer.AddSeparators(nofSeparators, separatorLength, true);
317 318 319 320 321

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

322 323
  transitioning macro ArrayJoin<T: type>(implicit context: Context)(
      useToLocaleString: constexpr bool, receiver: JSReceiver, sep: String,
324
      lenNumber: Number, locales: JSAny, options: JSAny): JSAny;
325

326
  transitioning ArrayJoin<JSArray>(implicit context: Context)(
327
      useToLocaleString: constexpr bool, receiver: JSReceiver, sep: String,
328
      lenNumber: Number, locales: JSAny, options: JSAny): JSAny {
329 330
    const map: Map = receiver.map;
    const kind: ElementsKind = map.elements_kind;
331
    let loadFn: LoadJoinElementFn;
332 333 334 335

    try {
      const array: JSArray = Cast<JSArray>(receiver) otherwise IfSlowPath;
      if (array.length != lenNumber) goto IfSlowPath;
336
      if (!IsPrototypeInitialArrayPrototype(map)) goto IfSlowPath;
337 338 339
      if (IsNoElementsProtectorCellInvalid()) goto IfSlowPath;

      if (IsElementsKindLessThanOrEqual(kind, HOLEY_ELEMENTS)) {
340
        loadFn = LoadJoinElement<array::FastSmiOrObjectElements>;
341
      } else if (IsElementsKindLessThanOrEqual(kind, HOLEY_DOUBLE_ELEMENTS)) {
342
        loadFn = LoadJoinElement<array::FastDoubleElements>;
343 344 345 346 347 348 349 350 351 352 353 354 355
      } 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 {
356
              ThrowInvalidStringLength(context);
357 358
            }
          } else {
359
            loadFn = LoadJoinElement<array::DictionaryElements>;
360 361 362
          }
        }
      else {
363 364 365 366
        goto IfSlowPath;
      }
    }
    label IfSlowPath {
367
      loadFn = LoadJoinElement<array::GenericElementsAccessor>;
368 369 370 371 372
    }
    return ArrayJoinImpl<JSArray>(
        receiver, sep, lenNumber, useToLocaleString, locales, options, loadFn);
  }

373
  transitioning ArrayJoin<JSTypedArray>(implicit context: Context)(
374
      useToLocaleString: constexpr bool, receiver: JSReceiver, sep: String,
375
      lenNumber: Number, locales: JSAny, options: JSAny): JSAny {
376 377 378 379 380 381
    const map: Map = receiver.map;
    const kind: ElementsKind = map.elements_kind;
    let loadFn: LoadJoinElementFn;

    if (IsElementsKindGreaterThan(kind, UINT32_ELEMENTS)) {
      if (kind == INT32_ELEMENTS) {
382
        loadFn = LoadJoinTypedElement<typed_array::Int32Elements>;
383
      } else if (kind == FLOAT32_ELEMENTS) {
384
        loadFn = LoadJoinTypedElement<typed_array::Float32Elements>;
385
      } else if (kind == FLOAT64_ELEMENTS) {
386
        loadFn = LoadJoinTypedElement<typed_array::Float64Elements>;
387
      } else if (kind == UINT8_CLAMPED_ELEMENTS) {
388
        loadFn = LoadJoinTypedElement<typed_array::Uint8ClampedElements>;
389
      } else if (kind == BIGUINT64_ELEMENTS) {
390
        loadFn = LoadJoinTypedElement<typed_array::BigUint64Elements>;
391
      } else if (kind == BIGINT64_ELEMENTS) {
392
        loadFn = LoadJoinTypedElement<typed_array::BigInt64Elements>;
393 394 395 396 397
      } else {
        unreachable;
      }
    } else {
      if (kind == UINT8_ELEMENTS) {
398
        loadFn = LoadJoinTypedElement<typed_array::Uint8Elements>;
399
      } else if (kind == INT8_ELEMENTS) {
400
        loadFn = LoadJoinTypedElement<typed_array::Int8Elements>;
401
      } else if (kind == UINT16_ELEMENTS) {
402
        loadFn = LoadJoinTypedElement<typed_array::Uint16Elements>;
403
      } else if (kind == INT16_ELEMENTS) {
404
        loadFn = LoadJoinTypedElement<typed_array::Int16Elements>;
405
      } else if (kind == UINT32_ELEMENTS) {
406
        loadFn = LoadJoinTypedElement<typed_array::Uint32Elements>;
407 408 409
      } else {
        unreachable;
      }
410
    }
411 412
    return ArrayJoinImpl<JSTypedArray>(
        receiver, sep, lenNumber, useToLocaleString, locales, options, loadFn);
413 414
  }

415 416 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
  // 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++) {
444
      const previouslyVisited: Object = stack.objects[i];
445 446

      // Add `receiver` to the first open slot
447
      if (previouslyVisited == TheHole) {
448
        stack.objects[i] = receiver;
449 450 451 452 453 454 455 456 457
        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 =
458
        StoreAndGrowFixedArray(stack, capacity, receiver);
459 460 461 462
    SetJoinStack(newStack);
    return True;
  }

463
  // Fast path the common non-nested calls. If the receiver is not already on
464
  // the stack, add it to the stack and return true. Otherwise return false.
465
  macro JoinStackPushInline(implicit context: Context)(receiver: JSReceiver):
466
      bool {
467 468 469
    try {
      const stack: FixedArray = LoadJoinStack()
          otherwise IfUninitialized;
470
      if (stack.objects[0] == TheHole) {
471
        stack.objects[0] = receiver;
472 473
      } else if (JoinStackPush(stack, receiver) == False)
        deferred {
474
          return false;
475 476 477 478 479
        }
    }
    label IfUninitialized {
      const stack: FixedArray =
          AllocateFixedArrayWithHoles(kMinJoinStackSize, kNone);
480
      stack.objects[0] = receiver;
481 482
      SetJoinStack(stack);
    }
483
    return true;
484 485 486 487 488
  }

  // Removes a receiver from the stack. The FixedArray will automatically shrink
  // to Heap::kMinJoinStackSize once the stack becomes empty.
  builtin JoinStackPop(implicit context: Context)(
489
      stack: FixedArray, receiver: JSReceiver): JSAny {
490 491
    const len: intptr = stack.length_intptr;
    for (let i: intptr = 0; i < len; i++) {
492
      if (stack.objects[i] == receiver) {
493 494 495 496 497 498 499 500
        // 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 {
501
          stack.objects[i] = TheHole;
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
        }
        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.
517
    if (stack.objects[0] == receiver && len == kMinJoinStackSize) {
518
      StoreFixedArrayElement(stack, 0, TheHole, SKIP_WRITE_BARRIER);
519 520 521 522 523 524 525
    } else
      deferred {
        JoinStackPop(stack, receiver);
      }
  }

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

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

  // https://tc39.github.io/ecma262/#sec-array.prototype.join
553
  transitioning javascript builtin
554 555 556
  ArrayPrototypeJoin(js-implicit context: Context, receiver: JSAny)(
      ...arguments): JSAny {
    const separator: JSAny = arguments[0];
557 558 559 560 561 562 563

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

564 565
    // Only handle valid array lengths. Although the spec allows larger
    // values, this matches historical V8 behavior.
566
    if (len > kMaxArrayIndex + 1) ThrowTypeError(kInvalidArrayLength);
567 568 569

    return CycleProtectedArrayJoin<JSArray>(
        false, o, len, separator, Undefined, Undefined);
570 571
  }

572
  // https://tc39.github.io/ecma262/#sec-array.prototype.tolocalestring
573
  transitioning javascript builtin ArrayPrototypeToLocaleString(
574 575 576
      js-implicit context: Context, receiver: JSAny)(...arguments): JSAny {
    const locales: JSAny = arguments[0];
    const options: JSAny = arguments[1];
577 578 579 580 581 582 583

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

584 585
    // Only handle valid array lengths. Although the spec allows larger
    // values, this matches historical V8 behavior.
586
    if (len > kMaxArrayIndex + 1) ThrowTypeError(kInvalidArrayLength);
587 588 589

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

592
  // https://tc39.github.io/ecma262/#sec-array.prototype.tostring
593
  transitioning javascript builtin ArrayPrototypeToString(
594
      js-implicit context: Context, receiver: JSAny)(...arguments): JSAny {
595 596 597 598
    // 1. Let array be ? ToObject(this value).
    const array: JSReceiver = ToObject_Inline(context, receiver);

    // 2. Let func be ? Get(array, "join").
599
    const prop: JSAny = GetProperty(array, 'join');
600 601 602 603 604 605 606 607 608 609 610 611
    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);
    }
  }
612 613 614

  // https://tc39.github.io/ecma262/#sec-%typedarray%.prototype.join
  transitioning javascript builtin TypedArrayPrototypeJoin(
615 616
      js-implicit context: Context, receiver: JSAny)(...arguments): JSAny {
    const separator: JSAny = arguments[0];
617 618 619 620 621

    // Spec: ValidateTypedArray is applied to the this value prior to evaluating
    // the algorithm.
    const typedArray: JSTypedArray = typed_array::ValidateTypedArray(
        context, receiver, '%TypedArray%.prototype.join');
622
    const length = Convert<Number>(typedArray.length);
623 624 625 626 627 628 629

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

  // https://tc39.github.io/ecma262/#sec-%typedarray%.prototype.tolocalestring
  transitioning javascript builtin TypedArrayPrototypeToLocaleString(
630 631 632
      js-implicit context: Context, receiver: JSAny)(...arguments): JSAny {
    const locales: JSAny = arguments[0];
    const options: JSAny = arguments[1];
633 634 635 636 637

    // Spec: ValidateTypedArray is applied to the this value prior to evaluating
    // the algorithm.
    const typedArray: JSTypedArray = typed_array::ValidateTypedArray(
        context, receiver, '%TypedArray%.prototype.toLocaleString');
638
    const length = Convert<Number>(typedArray.length);
639 640 641 642

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