array-join.tq 23.4 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 {
6

7
type LoadJoinElementFn = builtin(Context, JSReceiver, uintptr) => JSAny;
8

9 10 11
const kMaxArrayLength:
    constexpr uint32 generates 'JSArray::kMaxArrayLength';

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

18 19 20 21 22 23 24 25 26 27 28 29 30
transitioning builtin LoadJoinElement<T : type extends ElementsKind>(
    context: Context, receiver: JSReceiver, k: uintptr): JSAny {
  return GetProperty(receiver, Convert<Number>(k));
}

transitioning LoadJoinElement<array::DictionaryElements>(
    context: Context, receiver: JSReceiver, k: uintptr): JSAny {
  const array: JSArray = UnsafeCast<JSArray>(receiver);
  const dict: NumberDictionary = UnsafeCast<NumberDictionary>(array.elements);
  try {
    return BasicLoadNumberDictionaryElement(dict, Signed(k))
        otherwise IfNoData, IfHole;
  } label IfNoData deferred {
31
    return GetProperty(receiver, Convert<Number>(k));
32 33
  } label IfHole {
    return kEmptyString;
34
  }
35
}
36

37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
LoadJoinElement<array::FastSmiOrObjectElements>(
    context: Context, receiver: JSReceiver, k: uintptr): JSAny {
  const array: JSArray = UnsafeCast<JSArray>(receiver);
  const fixedArray: FixedArray = UnsafeCast<FixedArray>(array.elements);
  const element: Object = fixedArray.objects[k];
  return element == TheHole ? kEmptyString : UnsafeCast<JSAny>(element);
}

LoadJoinElement<array::FastDoubleElements>(
    context: Context, receiver: JSReceiver, k: uintptr): JSAny {
  const array: JSArray = UnsafeCast<JSArray>(receiver);
  const fixedDoubleArray: FixedDoubleArray =
      UnsafeCast<FixedDoubleArray>(array.elements);
  const element: float64 =
      fixedDoubleArray.floats[k].Value() otherwise return kEmptyString;
  return AllocateHeapNumberWithValue(element);
}

builtin LoadJoinTypedElement<T : type extends ElementsKind>(
    context: Context, receiver: JSReceiver, k: uintptr): JSAny {
  const typedArray: JSTypedArray = UnsafeCast<JSTypedArray>(receiver);
  assert(!IsDetachedBuffer(typedArray.buffer));
  return typed_array::LoadFixedTypedArrayElementAsTagged(
      typedArray.data_ptr, k, typed_array::KindForArrayType<T>());
}

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

  const prop: JSAny = GetProperty(element, 'toLocaleString');
  try {
    const callable: Callable = Cast<Callable>(prop) otherwise TypeError;
    let result: JSAny;
    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);
77
    }
78 79 80
    return ToString_Inline(result);
  } label TypeError {
    ThrowTypeError(MessageTemplate::kCalledNonCallable, prop);
81
  }
82
}
83

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
// Verifies the current element JSArray accessor can still be safely used
// (see LoadJoinElement<ElementsAccessor>).
macro CannotUseSameArrayAccessor<T: type>(implicit context: Context)(
    loadFn: LoadJoinElementFn, receiver: JSReceiver, originalMap: Map,
    originalLen: Number): bool;

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

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

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

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

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

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
// Contains the information necessary to create a single, separator delimited,
// flattened one or two byte string.
// The buffer is maintained and updated by Buffer.constructor, Buffer.Add(),
// Buffer.AddSeparators().
struct Buffer {
  macro 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 =
        AddStringLength(this.totalStringLength, str.length_intptr);
    this.fixedArray =
        StoreAndGrowFixedArray(this.fixedArray, this.index++, str);
    this.isOneByte =
        IsOneByteStringInstanceType(str.instanceType) & this.isOneByte;
161 162
  }

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

167 168 169
    const nofSeparatorsInt: intptr = nofSeparators;
    const sepsLen: intptr = separatorLength * nofSeparatorsInt;
    // Detect integer overflow
170
    // TODO(turbofan): Replace with overflow-checked multiplication.
171 172 173
    if (sepsLen / separatorLength != nofSeparatorsInt) deferred {
        ThrowInvalidStringLength(context);
      }
174

175 176 177 178
    this.totalStringLength = AddStringLength(this.totalStringLength, sepsLen);
    if (write) deferred {
        this.fixedArray = StoreAndGrowFixedArray(
            this.fixedArray, this.index++, Convert<Smi>(nofSeparatorsInt));
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
  // 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;
}
209

210 211 212 213 214 215 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{
    fixedArray: AllocateZeroedFixedArray(cappedBufferSize),
    index: 0,
    totalStringLength: 0,
    isOneByte: IsOneByteStringInstanceType(sep.instanceType)
  };
}
222

223 224 225 226 227 228 229 230 231 232 233 234 235 236
macro BufferJoin(implicit context: Context)(
    buffer: Buffer, sep: String): String {
  assert(IsValidPositiveSmi(buffer.totalStringLength));
  if (buffer.totalStringLength == 0) return kEmptyString;

  // Fast path when there's only one buffer element.
  if (buffer.index == 1) {
    const fixedArray: FixedArray = buffer.fixedArray;
    typeswitch (fixedArray.objects[0]) {
      // When the element is a string, just return it and completely avoid
      // allocating another string.
      case (str: String): {
        return str;
      }
237

238 239 240 241 242 243 244 245
      // 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 (Object): {
        unreachable;
      }
246
    }
247 248
  }

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

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

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

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

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

283 284
    // b. Let element be ? Get(O, ! ToString(k)).
    const element: JSAny = loadFn(context, receiver, k++);
285

286 287 288 289 290 291 292 293 294 295 296
    // 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;
297
        }
298
        case (num: Number): {
299
          next = NumberToString(num);
300 301 302
        }
        case (obj: JSAny): {
          if (IsNullOrUndefined(obj)) continue;
303
          next = string::ToString(context, obj);
304
        }
305
      }
306 307
    }

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

313 314
  // Add any separators at the end.
  buffer.AddSeparators(nofSeparators, separatorLength, true);
315

316 317 318
  // 8. Return R.
  return BufferJoin(buffer, sep);
}
319

320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
transitioning macro ArrayJoin<T: type>(implicit context: Context)(
    useToLocaleString: constexpr bool, receiver: JSReceiver, sep: String,
    lenNumber: Number, locales: JSAny, options: JSAny): JSAny;

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

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

    if (IsElementsKindLessThanOrEqual(kind, ElementsKind::HOLEY_ELEMENTS)) {
      loadFn = LoadJoinElement<array::FastSmiOrObjectElements>;
    } else if (IsElementsKindLessThanOrEqual(
                   kind, ElementsKind::HOLEY_DOUBLE_ELEMENTS)) {
      loadFn = LoadJoinElement<array::FastDoubleElements>;
    } else if (kind == ElementsKind::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 {
            ThrowInvalidStringLength(context);
355
          }
356 357
        } else {
          loadFn = LoadJoinElement<array::DictionaryElements>;
358
        }
359
      }
360 361
    else {
      goto IfSlowPath;
362
    }
363 364
  } label IfSlowPath {
    loadFn = LoadJoinElement<array::GenericElementsAccessor>;
365
  }
366 367 368
  return ArrayJoinImpl<JSArray>(
      receiver, sep, lenNumber, useToLocaleString, locales, options, loadFn);
}
369

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

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

412 413 414 415 416 417 418 419 420
// 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 {
421 422 423 424 425 426 427 428
  typeswitch (*NativeContextSlot(ContextSlot::ARRAY_JOIN_STACK_INDEX)) {
    case (Undefined): {
      goto IfUninitialized;
    }
    case (stack: FixedArray): {
      return stack;
    }
  }
429
}
430

431
macro SetJoinStack(implicit context: Context)(stack: FixedArray): void {
432
  *NativeContextSlot(ContextSlot::ARRAY_JOIN_STACK_INDEX) = stack;
433
}
434

435 436 437 438 439 440 441 442 443 444 445 446 447
// 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++) {
    const previouslyVisited: Object = stack.objects[i];

    // Add `receiver` to the first open slot
    if (previouslyVisited == TheHole) {
      stack.objects[i] = receiver;
      return True;
448 449
    }

450 451
    // Detect cycles
    if (receiver == previouslyVisited) return False;
452 453
  }

454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
  // If no open slots were found, grow the stack and add receiver to the end.
  const newStack: FixedArray =
      StoreAndGrowFixedArray(stack, capacity, receiver);
  SetJoinStack(newStack);
  return True;
}

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

483 484 485 486 487 488 489 490 491 492 493 494 495
// 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): JSAny {
  const len: intptr = stack.length_intptr;
  for (let i: intptr = 0; i < len; i++) {
    if (stack.objects[i] == receiver) {
      // 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, AllocationFlag::kNone);
          SetJoinStack(newStack);
496
        }
497 498
      else {
        stack.objects[i] = TheHole;
499
      }
500
      return Undefined;
501 502
    }
  }
503 504
  unreachable;
}
505

506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
// 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.
  if (stack.objects[0] == receiver && len == kMinJoinStackSize) {
    StoreFixedArrayElement(stack, 0, TheHole, SKIP_WRITE_BARRIER);
  } else
    deferred {
      JoinStackPop(stack, receiver);
    }
}
521

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

  // 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: JSAny =
          ArrayJoin<T>(useToLocaleString, o, sep, len, locales, options);
      JoinStackPopInline(o);
      return result;
    } catch (e) deferred {
      JoinStackPopInline(o);
      ReThrow(context, e);
542
    }
543 544
  } else {
    return kEmptyString;
545
  }
546
}
547

548 549 550 551 552
// https://tc39.github.io/ecma262/#sec-array.prototype.join
transitioning javascript builtin
ArrayPrototypeJoin(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const separator: JSAny = arguments[0];
553

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

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

560 561
  // Only handle valid array lengths. Although the spec allows larger
  // values, this matches historical V8 behavior.
562
  if (len > kMaxArrayLength) {
563
    ThrowTypeError(MessageTemplate::kInvalidArrayLength);
564
  }
565

566 567 568
  return CycleProtectedArrayJoin<JSArray>(
      false, o, len, separator, Undefined, Undefined);
}
569

570 571 572 573 574
// https://tc39.github.io/ecma262/#sec-array.prototype.tolocalestring
transitioning javascript builtin ArrayPrototypeToLocaleString(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const locales: JSAny = arguments[0];
  const options: JSAny = arguments[1];
575

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

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

582 583
  // Only handle valid array lengths. Although the spec allows larger
  // values, this matches historical V8 behavior.
584
  if (len > kMaxArrayLength) {
585
    ThrowTypeError(MessageTemplate::kInvalidArrayLength);
586
  }
587

588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
  return CycleProtectedArrayJoin<JSArray>(true, o, len, ',', locales, options);
}

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

  // 2. Let func be ? Get(array, "join").
  const prop: JSAny = 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);
608
  }
609
}
610

611 612 613 614
// https://tc39.github.io/ecma262/#sec-%typedarray%.prototype.join
transitioning javascript builtin TypedArrayPrototypeJoin(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const separator: JSAny = arguments[0];
615

616 617 618 619 620
  // Spec: ValidateTypedArray is applied to the this value prior to evaluating
  // the algorithm.
  const typedArray: JSTypedArray = typed_array::ValidateTypedArray(
      context, receiver, '%TypedArray%.prototype.join');
  const length = Convert<Number>(typedArray.length);
621

622 623 624
  return CycleProtectedArrayJoin<JSTypedArray>(
      false, typedArray, length, separator, Undefined, Undefined);
}
625

626 627 628 629 630
// https://tc39.github.io/ecma262/#sec-%typedarray%.prototype.tolocalestring
transitioning javascript builtin TypedArrayPrototypeToLocaleString(
    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
  const locales: JSAny = arguments[0];
  const options: JSAny = arguments[1];
631

632 633 634 635 636
  // Spec: ValidateTypedArray is applied to the this value prior to evaluating
  // the algorithm.
  const typedArray: JSTypedArray = typed_array::ValidateTypedArray(
      context, receiver, '%TypedArray%.prototype.toLocaleString');
  const length = Convert<Number>(typedArray.length);
637

638 639 640
  return CycleProtectedArrayJoin<JSTypedArray>(
      true, typedArray, length, ',', locales, options);
}
641
}