1// Copyright 2018 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5namespace array {
6
7type LoadJoinElementFn = builtin(Context, JSReceiver, uintptr) => JSAny;
8
9const kMaxArrayLength:
10    constexpr uint32 generates 'JSArray::kMaxArrayLength';
11
12// Fast C call to write a fixed array (see Buffer.fixedArray) to a single
13// string.
14extern macro
15ArrayBuiltinsAssembler::CallJSArrayArrayJoinConcatToSequentialString(
16    FixedArray, intptr, String, String): String;
17
18transitioning builtin LoadJoinElement<T : type extends ElementsKind>(
19    context: Context, receiver: JSReceiver, k: uintptr): JSAny {
20  return GetProperty(receiver, Convert<Number>(k));
21}
22
23transitioning LoadJoinElement<array::DictionaryElements>(
24    context: Context, receiver: JSReceiver, k: uintptr): JSAny {
25  const array: JSArray = UnsafeCast<JSArray>(receiver);
26  const dict: NumberDictionary = UnsafeCast<NumberDictionary>(array.elements);
27  try {
28    return BasicLoadNumberDictionaryElement(dict, Signed(k))
29        otherwise IfNoData, IfHole;
30  } label IfNoData deferred {
31    return GetProperty(receiver, Convert<Number>(k));
32  } label IfHole {
33    return kEmptyString;
34  }
35}
36
37LoadJoinElement<array::FastSmiOrObjectElements>(
38    context: Context, receiver: JSReceiver, k: uintptr): JSAny {
39  const array: JSArray = UnsafeCast<JSArray>(receiver);
40  const fixedArray: FixedArray = UnsafeCast<FixedArray>(array.elements);
41  const element: Object = fixedArray.objects[k];
42  return element == TheHole ? kEmptyString : UnsafeCast<JSAny>(element);
43}
44
45LoadJoinElement<array::FastDoubleElements>(
46    context: Context, receiver: JSReceiver, k: uintptr): JSAny {
47  const array: JSArray = UnsafeCast<JSArray>(receiver);
48  const fixedDoubleArray: FixedDoubleArray =
49      UnsafeCast<FixedDoubleArray>(array.elements);
50  const element: float64 =
51      fixedDoubleArray.floats[k].Value() otherwise return kEmptyString;
52  return AllocateHeapNumberWithValue(element);
53}
54
55builtin LoadJoinTypedElement<T : type extends ElementsKind>(
56    context: Context, receiver: JSReceiver, k: uintptr): JSAny {
57  const typedArray: JSTypedArray = UnsafeCast<JSTypedArray>(receiver);
58  dcheck(!typed_array::IsJSArrayBufferViewDetachedOrOutOfBoundsBoolean(
59      typedArray));
60  return typed_array::LoadFixedTypedArrayElementAsTagged(
61      typedArray.data_ptr, k, typed_array::KindForArrayType<T>());
62}
63
64transitioning builtin ConvertToLocaleString(
65    context: Context, element: JSAny, locales: JSAny, options: JSAny): String {
66  if (IsNullOrUndefined(element)) return kEmptyString;
67
68  const prop: JSAny = GetProperty(element, 'toLocaleString');
69  try {
70    const callable: Callable = Cast<Callable>(prop) otherwise TypeError;
71    let result: JSAny;
72    if (IsNullOrUndefined(locales)) {
73      result = Call(context, callable, element);
74    } else if (IsNullOrUndefined(options)) {
75      result = Call(context, callable, element, locales);
76    } else {
77      result = Call(context, callable, element, locales, options);
78    }
79    return ToString_Inline(result);
80  } label TypeError {
81    ThrowTypeError(MessageTemplate::kCalledNonCallable, prop);
82  }
83}
84
85// Verifies the current element JSArray accessor can still be safely used
86// (see LoadJoinElement<ElementsAccessor>).
87macro CannotUseSameArrayAccessor<T: type>(implicit context: Context)(
88    loadFn: LoadJoinElementFn, receiver: JSReceiver, originalMap: Map,
89    originalLen: Number): bool;
90
91CannotUseSameArrayAccessor<JSArray>(implicit context: Context)(
92    loadFn: LoadJoinElementFn, receiver: JSReceiver, originalMap: Map,
93    originalLen: Number): bool {
94  if (loadFn == LoadJoinElement<array::GenericElementsAccessor>) return false;
95
96  const array: JSArray = UnsafeCast<JSArray>(receiver);
97  if (originalMap != array.map) return true;
98  if (originalLen != array.length) return true;
99  if (IsNoElementsProtectorCellInvalid()) return true;
100  return false;
101}
102
103CannotUseSameArrayAccessor<JSTypedArray>(implicit context: Context)(
104    _loadFn: LoadJoinElementFn, receiver: JSReceiver, _initialMap: Map,
105    _initialLen: Number): bool {
106  const typedArray: JSTypedArray = UnsafeCast<JSTypedArray>(receiver);
107  // When this is called from toLocaleString(), the underlying buffer might get
108  // detached / resized (in the case of RAB / GSAB) during iterating the
109  // elements. When this is called from join(), it can happen only before the
110  // first element (during parameter conversion). The code below doesn't
111  // differentiate between these two cases, but does the checks in both cases.
112  if (IsDetachedBuffer(typedArray.buffer)) {
113    return true;
114  }
115  if (IsVariableLengthJSArrayBufferView(typedArray)) {
116    // TODO(v8:11111): Add a fast(er) path here.
117    return true;
118  }
119  return false;
120}
121
122// Calculates the running total length of the resulting string.  If the
123// calculated length exceeds the maximum string length (see
124// String::kMaxLength), throws a range error.
125macro AddStringLength(implicit context: Context)(
126    lenA: intptr, lenB: intptr): intptr {
127  try {
128    const length: intptr = TryIntPtrAdd(lenA, lenB) otherwise IfOverflow;
129    if (length > kStringMaxLength) goto IfOverflow;
130    return length;
131  } label IfOverflow deferred {
132    ThrowInvalidStringLength(context);
133  }
134}
135
136// Stores an element to a fixed array and return the fixed array. If the fixed
137// array is not large enough, create and return a new, larger fixed array that
138// contains all previously elements and the new element.
139macro StoreAndGrowFixedArray<T: type>(
140    fixedArray: FixedArray, index: intptr, element: T): FixedArray {
141  const length: intptr = fixedArray.length_intptr;
142  dcheck(index <= length);
143  if (index < length) {
144    fixedArray.objects[index] = element;
145    return fixedArray;
146  } else
147    deferred {
148      const newLength: intptr = CalculateNewElementsCapacity(length);
149      dcheck(index < newLength);
150      const newfixedArray: FixedArray =
151          ExtractFixedArray(fixedArray, 0, length, newLength);
152      newfixedArray.objects[index] = element;
153      return newfixedArray;
154    }
155}
156
157// Contains the information necessary to create a single, separator delimited,
158// flattened one or two byte string.
159// The buffer is maintained and updated by Buffer.constructor, Buffer.Add(),
160// Buffer.AddSeparators().
161struct Buffer {
162  macro Add(implicit context: Context)(
163      str: String, nofSeparators: intptr, separatorLength: intptr): void {
164    // Add separators if necessary (at the beginning or more than one)
165    const writeSeparators: bool = this.index == 0 | nofSeparators > 1;
166    this.AddSeparators(nofSeparators, separatorLength, writeSeparators);
167
168    this.totalStringLength =
169        AddStringLength(this.totalStringLength, str.length_intptr);
170    this.fixedArray =
171        StoreAndGrowFixedArray(this.fixedArray, this.index++, str);
172    this.isOneByte =
173        IsOneByteStringInstanceType(str.instanceType) & this.isOneByte;
174  }
175
176  macro AddSeparators(implicit context: Context)(
177      nofSeparators: intptr, separatorLength: intptr, write: bool): void {
178    if (nofSeparators == 0 || separatorLength == 0) return;
179
180    const nofSeparatorsInt: intptr = nofSeparators;
181    const sepsLen: intptr = separatorLength * nofSeparatorsInt;
182    // Detect integer overflow
183    // TODO(turbofan): Replace with overflow-checked multiplication.
184    if (sepsLen / separatorLength != nofSeparatorsInt) deferred {
185        ThrowInvalidStringLength(context);
186      }
187
188    this.totalStringLength = AddStringLength(this.totalStringLength, sepsLen);
189    if (write) deferred {
190        this.fixedArray = StoreAndGrowFixedArray(
191            this.fixedArray, this.index++, Convert<Smi>(nofSeparatorsInt));
192      }
193  }
194
195  // Fixed array holding elements that are either:
196  //   1) String result of `ToString(next)`.
197  //   2) Smi representing the number of consecutive separators.
198  // `BufferJoin()` will iterate and writes these entries to a flat string.
199  //
200  // To save space, reduce reads and writes, only separators at the beginning,
201  // end, or more than one are written.
202  //
203  // No hole example
204  //   receiver:   ['hello', 'world']
205  //   fixedArray: ['hello', 'world']
206  //
207  // Hole example
208  //   receiver:   [<hole>, 'hello', <hole>, 'world', <hole>]
209  //   fixedArray: [1, 'hello', 2, 'world', 1]
210  fixedArray: FixedArray;
211
212  // Index to insert a new entry into `fixedArray`.
213  index: intptr;
214
215  // Running total of the resulting string length.
216  totalStringLength: intptr;
217
218  // `true` if the separator and all strings in the buffer are one-byte,
219  // otherwise `false`.
220  isOneByte: bool;
221}
222
223macro NewBuffer(len: uintptr, sep: String): Buffer {
224  const cappedBufferSize: intptr = len > kMaxNewSpaceFixedArrayElements ?
225      kMaxNewSpaceFixedArrayElements :
226      Signed(len);
227  dcheck(cappedBufferSize > 0);
228  return Buffer{
229    fixedArray: AllocateZeroedFixedArray(cappedBufferSize),
230    index: 0,
231    totalStringLength: 0,
232    isOneByte: IsOneByteStringInstanceType(sep.instanceType)
233  };
234}
235
236macro BufferJoin(implicit context: Context)(
237    buffer: Buffer, sep: String): String {
238  dcheck(IsValidPositiveSmi(buffer.totalStringLength));
239  if (buffer.totalStringLength == 0) return kEmptyString;
240
241  // Fast path when there's only one buffer element.
242  if (buffer.index == 1) {
243    const fixedArray: FixedArray = buffer.fixedArray;
244    typeswitch (fixedArray.objects[0]) {
245      // When the element is a string, just return it and completely avoid
246      // allocating another string.
247      case (str: String): {
248        return str;
249      }
250
251      // When the element is a smi, use StringRepeat to quickly build a memory
252      // efficient separator repeated string.
253      case (nofSeparators: Number): {
254        return StringRepeat(context, sep, nofSeparators);
255      }
256      case (Object): {
257        unreachable;
258      }
259    }
260  }
261
262  const length: uint32 = Convert<uint32>(Unsigned(buffer.totalStringLength));
263  const r: String = buffer.isOneByte ? AllocateSeqOneByteString(length) :
264                                       AllocateSeqTwoByteString(length);
265  return CallJSArrayArrayJoinConcatToSequentialString(
266      buffer.fixedArray, buffer.index, sep, r);
267}
268
269transitioning macro ArrayJoinImpl<T: type>(implicit context: Context)(
270    receiver: JSReceiver, sep: String, lengthNumber: Number,
271    useToLocaleString: constexpr bool, locales: JSAny, options: JSAny,
272    initialLoadFn: LoadJoinElementFn): String {
273  const initialMap: Map = receiver.map;
274  const len: uintptr = Convert<uintptr>(lengthNumber);
275  const separatorLength: intptr = sep.length_intptr;
276  let nofSeparators: intptr = 0;
277  let loadFn: LoadJoinElementFn = initialLoadFn;
278  let buffer: Buffer = NewBuffer(len, sep);
279
280  // 6. Let k be 0.
281  let k: uintptr = 0;
282
283  // 7. Repeat, while k < len
284  while (k < len) {
285    if (CannotUseSameArrayAccessor<T>(
286            loadFn, receiver, initialMap, lengthNumber))
287      deferred {
288        loadFn = LoadJoinElement<array::GenericElementsAccessor>;
289      }
290
291    if (k > 0) {
292      // a. If k > 0, let R be the string-concatenation of R and sep.
293      nofSeparators = nofSeparators + 1;
294    }
295
296    // b. Let element be ? Get(O, ! ToString(k)).
297    const element: JSAny = loadFn(context, receiver, k++);
298
299    // c. If element is undefined or null, let next be the empty String;
300    //    otherwise, let next be ? ToString(element).
301    let next: String;
302    if constexpr (useToLocaleString) {
303      next = ConvertToLocaleString(context, element, locales, options);
304      if (next == kEmptyString) continue;
305    } else {
306      typeswitch (element) {
307        case (str: String): {
308          if (str == kEmptyString) continue;
309          next = str;
310        }
311        case (num: Number): {
312          next = NumberToString(num);
313        }
314        case (obj: JSAny): {
315          if (IsNullOrUndefined(obj)) continue;
316          next = string::ToString(context, obj);
317        }
318      }
319    }
320
321    // d. Set R to the string-concatenation of R and next.
322    buffer.Add(next, nofSeparators, separatorLength);
323    nofSeparators = 0;
324  }
325
326  // Add any separators at the end.
327  buffer.AddSeparators(nofSeparators, separatorLength, true);
328
329  // 8. Return R.
330  return BufferJoin(buffer, sep);
331}
332
333transitioning macro ArrayJoin<T: type>(implicit context: Context)(
334    useToLocaleString: constexpr bool, receiver: JSReceiver, sep: String,
335    lenNumber: Number, locales: JSAny, options: JSAny): JSAny;
336
337transitioning ArrayJoin<JSArray>(implicit context: Context)(
338    useToLocaleString: constexpr bool, receiver: JSReceiver, sep: String,
339    lenNumber: Number, locales: JSAny, options: JSAny): JSAny {
340  const map: Map = receiver.map;
341  const kind: ElementsKind = map.elements_kind;
342  let loadFn: LoadJoinElementFn;
343
344  try {
345    const array: JSArray = Cast<JSArray>(receiver) otherwise IfSlowPath;
346    if (array.length != lenNumber) goto IfSlowPath;
347    if (!IsPrototypeInitialArrayPrototype(map)) goto IfSlowPath;
348    if (IsNoElementsProtectorCellInvalid()) goto IfSlowPath;
349
350    if (IsElementsKindLessThanOrEqual(kind, ElementsKind::HOLEY_ELEMENTS)) {
351      loadFn = LoadJoinElement<array::FastSmiOrObjectElements>;
352    } else if (IsElementsKindLessThanOrEqual(
353                   kind, ElementsKind::HOLEY_DOUBLE_ELEMENTS)) {
354      loadFn = LoadJoinElement<array::FastDoubleElements>;
355    } else if (kind == ElementsKind::DICTIONARY_ELEMENTS)
356      deferred {
357        const dict: NumberDictionary =
358            UnsafeCast<NumberDictionary>(array.elements);
359        const nofElements: Smi = GetNumberDictionaryNumberOfElements(dict);
360        if (nofElements == 0) {
361          if (sep == kEmptyString) return kEmptyString;
362          try {
363            const nofSeparators: Smi =
364                Cast<Smi>(lenNumber - 1) otherwise IfNotSmi;
365            return StringRepeat(context, sep, nofSeparators);
366          } label IfNotSmi {
367            ThrowInvalidStringLength(context);
368          }
369        } else {
370          loadFn = LoadJoinElement<array::DictionaryElements>;
371        }
372      }
373    else {
374      goto IfSlowPath;
375    }
376  } label IfSlowPath {
377    loadFn = LoadJoinElement<array::GenericElementsAccessor>;
378  }
379  return ArrayJoinImpl<JSArray>(
380      receiver, sep, lenNumber, useToLocaleString, locales, options, loadFn);
381}
382
383transitioning ArrayJoin<JSTypedArray>(implicit context: Context)(
384    useToLocaleString: constexpr bool, receiver: JSReceiver, sep: String,
385    lenNumber: Number, locales: JSAny, options: JSAny): JSAny {
386  const map: Map = receiver.map;
387  const kind: ElementsKind = map.elements_kind;
388  let loadFn: LoadJoinElementFn;
389
390  if (IsElementsKindGreaterThan(kind, ElementsKind::UINT32_ELEMENTS)) {
391    if (kind == ElementsKind::INT32_ELEMENTS) {
392      loadFn = LoadJoinTypedElement<typed_array::Int32Elements>;
393    } else if (kind == ElementsKind::FLOAT32_ELEMENTS) {
394      loadFn = LoadJoinTypedElement<typed_array::Float32Elements>;
395    } else if (kind == ElementsKind::FLOAT64_ELEMENTS) {
396      loadFn = LoadJoinTypedElement<typed_array::Float64Elements>;
397    } else if (kind == ElementsKind::UINT8_CLAMPED_ELEMENTS) {
398      loadFn = LoadJoinTypedElement<typed_array::Uint8ClampedElements>;
399    } else if (kind == ElementsKind::BIGUINT64_ELEMENTS) {
400      loadFn = LoadJoinTypedElement<typed_array::BigUint64Elements>;
401    } else if (kind == ElementsKind::BIGINT64_ELEMENTS) {
402      loadFn = LoadJoinTypedElement<typed_array::BigInt64Elements>;
403    } else if (kind == ElementsKind::RAB_GSAB_UINT8_ELEMENTS) {
404      loadFn = LoadJoinTypedElement<typed_array::Uint8Elements>;
405    } else if (kind == ElementsKind::RAB_GSAB_INT8_ELEMENTS) {
406      loadFn = LoadJoinTypedElement<typed_array::Int8Elements>;
407    } else if (kind == ElementsKind::RAB_GSAB_UINT16_ELEMENTS) {
408      loadFn = LoadJoinTypedElement<typed_array::Uint16Elements>;
409    } else if (kind == ElementsKind::RAB_GSAB_INT16_ELEMENTS) {
410      loadFn = LoadJoinTypedElement<typed_array::Int16Elements>;
411    } else if (kind == ElementsKind::RAB_GSAB_UINT32_ELEMENTS) {
412      loadFn = LoadJoinTypedElement<typed_array::Uint32Elements>;
413    } else if (kind == ElementsKind::RAB_GSAB_INT32_ELEMENTS) {
414      loadFn = LoadJoinTypedElement<typed_array::Int32Elements>;
415    } else if (kind == ElementsKind::RAB_GSAB_FLOAT32_ELEMENTS) {
416      loadFn = LoadJoinTypedElement<typed_array::Float32Elements>;
417    } else if (kind == ElementsKind::RAB_GSAB_FLOAT64_ELEMENTS) {
418      loadFn = LoadJoinTypedElement<typed_array::Float64Elements>;
419    } else if (kind == ElementsKind::RAB_GSAB_UINT8_CLAMPED_ELEMENTS) {
420      loadFn = LoadJoinTypedElement<typed_array::Uint8ClampedElements>;
421    } else if (kind == ElementsKind::RAB_GSAB_BIGUINT64_ELEMENTS) {
422      loadFn = LoadJoinTypedElement<typed_array::BigUint64Elements>;
423    } else if (kind == ElementsKind::RAB_GSAB_BIGINT64_ELEMENTS) {
424      loadFn = LoadJoinTypedElement<typed_array::BigInt64Elements>;
425    } else {
426      unreachable;
427    }
428  } else {
429    if (kind == ElementsKind::UINT8_ELEMENTS) {
430      loadFn = LoadJoinTypedElement<typed_array::Uint8Elements>;
431    } else if (kind == ElementsKind::INT8_ELEMENTS) {
432      loadFn = LoadJoinTypedElement<typed_array::Int8Elements>;
433    } else if (kind == ElementsKind::UINT16_ELEMENTS) {
434      loadFn = LoadJoinTypedElement<typed_array::Uint16Elements>;
435    } else if (kind == ElementsKind::INT16_ELEMENTS) {
436      loadFn = LoadJoinTypedElement<typed_array::Int16Elements>;
437    } else if (kind == ElementsKind::UINT32_ELEMENTS) {
438      loadFn = LoadJoinTypedElement<typed_array::Uint32Elements>;
439    } else {
440      unreachable;
441    }
442  }
443  return ArrayJoinImpl<JSTypedArray>(
444      receiver, sep, lenNumber, useToLocaleString, locales, options, loadFn);
445}
446
447// The Join Stack detects cyclical calls to Array Join builtins
448// (Array.p.join(), Array.p.toString(), Array.p.toLocaleString()). This
449// FixedArray holds a stack of receivers to the current call.
450// CycleProtectedArrayJoin() is responsible for calling JoinStackPush and
451// JoinStackPop when visiting and leaving a receiver, respectively.
452const kMinJoinStackSize:
453    constexpr int31 generates 'JSArray::kMinJoinStackSize';
454macro LoadJoinStack(implicit context: Context)(): FixedArray
455    labels IfUninitialized {
456  typeswitch (*NativeContextSlot(ContextSlot::ARRAY_JOIN_STACK_INDEX)) {
457    case (Undefined): {
458      goto IfUninitialized;
459    }
460    case (stack: FixedArray): {
461      return stack;
462    }
463  }
464}
465
466macro SetJoinStack(implicit context: Context)(stack: FixedArray): void {
467  *NativeContextSlot(ContextSlot::ARRAY_JOIN_STACK_INDEX) = stack;
468}
469
470// Adds a receiver to the stack. The FixedArray will automatically grow to
471// accommodate the receiver. If the receiver already exists on the stack,
472// this indicates a cyclical call and False is returned.
473builtin JoinStackPush(implicit context: Context)(
474    stack: FixedArray, receiver: JSReceiver): Boolean {
475  const capacity: intptr = stack.length_intptr;
476  for (let i: intptr = 0; i < capacity; i++) {
477    const previouslyVisited: Object = stack.objects[i];
478
479    // Add `receiver` to the first open slot
480    if (previouslyVisited == TheHole) {
481      stack.objects[i] = receiver;
482      return True;
483    }
484
485    // Detect cycles
486    if (receiver == previouslyVisited) return False;
487  }
488
489  // If no open slots were found, grow the stack and add receiver to the end.
490  const newStack: FixedArray =
491      StoreAndGrowFixedArray(stack, capacity, receiver);
492  SetJoinStack(newStack);
493  return True;
494}
495
496// Fast path the common non-nested calls. If the receiver is not already on
497// the stack, add it to the stack and return true. Otherwise return false.
498macro JoinStackPushInline(implicit context: Context)(receiver: JSReceiver):
499    bool {
500  try {
501    const stack: FixedArray = LoadJoinStack()
502        otherwise IfUninitialized;
503    if (stack.objects[0] == TheHole) {
504      stack.objects[0] = receiver;
505    } else if (JoinStackPush(stack, receiver) == False)
506      deferred {
507        return false;
508      }
509  } label IfUninitialized {
510    const stack: FixedArray =
511        AllocateFixedArrayWithHoles(kMinJoinStackSize, AllocationFlag::kNone);
512    stack.objects[0] = receiver;
513    SetJoinStack(stack);
514  }
515  return true;
516}
517
518// Removes a receiver from the stack. The FixedArray will automatically shrink
519// to Heap::kMinJoinStackSize once the stack becomes empty.
520builtin JoinStackPop(implicit context: Context)(
521    stack: FixedArray, receiver: JSReceiver): JSAny {
522  const len: intptr = stack.length_intptr;
523  for (let i: intptr = 0; i < len; i++) {
524    if (stack.objects[i] == receiver) {
525      // Shrink the Join Stack if the stack will be empty and is larger than
526      // the minimum size.
527      if (i == 0 && len > kMinJoinStackSize) deferred {
528          const newStack: FixedArray = AllocateFixedArrayWithHoles(
529              kMinJoinStackSize, AllocationFlag::kNone);
530          SetJoinStack(newStack);
531        }
532      else {
533        stack.objects[i] = TheHole;
534      }
535      return Undefined;
536    }
537  }
538  unreachable;
539}
540
541// Fast path the common non-nested calls.
542macro JoinStackPopInline(implicit context: Context)(receiver: JSReceiver):
543    void {
544  const stack: FixedArray = LoadJoinStack()
545      otherwise unreachable;
546  const len: intptr = stack.length_intptr;
547
548  // Builtin call was not nested (receiver is the first entry) and
549  // did not contain other nested arrays that expanded the stack.
550  if (stack.objects[0] == receiver && len == kMinJoinStackSize) {
551    stack.objects[0] = TheHole;
552  } else
553    deferred {
554      JoinStackPop(stack, receiver);
555    }
556}
557
558// Main entry point for all builtins using Array Join functionality.
559transitioning macro CycleProtectedArrayJoin<T: type>(
560    implicit context: Context)(
561    useToLocaleString: constexpr bool, o: JSReceiver, len: Number,
562    sepObj: JSAny, locales: JSAny, options: JSAny): JSAny {
563  // 3. If separator is undefined, let sep be the single-element String ",".
564  // 4. Else, let sep be ? ToString(separator).
565  const sep: String = sepObj == Undefined ? ',' : ToString_Inline(sepObj);
566
567  // If the receiver is not empty and not already being joined, continue with
568  // the normal join algorithm.
569  if (len > 0 && JoinStackPushInline(o)) {
570    try {
571      const result: JSAny =
572          ArrayJoin<T>(useToLocaleString, o, sep, len, locales, options);
573      JoinStackPopInline(o);
574      return result;
575    } catch (e, message) deferred {
576      JoinStackPopInline(o);
577      ReThrowWithMessage(context, e, message);
578    }
579  } else {
580    return kEmptyString;
581  }
582}
583
584// https://tc39.github.io/ecma262/#sec-array.prototype.join
585transitioning javascript builtin
586ArrayPrototypeJoin(
587    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
588  const separator: JSAny = arguments[0];
589
590  // 1. Let O be ? ToObject(this value).
591  const o: JSReceiver = ToObject_Inline(context, receiver);
592
593  // 2. Let len be ? ToLength(? Get(O, "length")).
594  const len: Number = GetLengthProperty(o);
595
596  // Only handle valid array lengths. Although the spec allows larger
597  // values, this matches historical V8 behavior.
598  if (len > kMaxArrayLength) {
599    ThrowTypeError(MessageTemplate::kInvalidArrayLength);
600  }
601
602  return CycleProtectedArrayJoin<JSArray>(
603      false, o, len, separator, Undefined, Undefined);
604}
605
606// https://tc39.github.io/ecma262/#sec-array.prototype.tolocalestring
607transitioning javascript builtin ArrayPrototypeToLocaleString(
608    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
609  const locales: JSAny = arguments[0];
610  const options: JSAny = arguments[1];
611
612  // 1. Let O be ? ToObject(this value).
613  const o: JSReceiver = ToObject_Inline(context, receiver);
614
615  // 2. Let len be ? ToLength(? Get(O, "length")).
616  const len: Number = GetLengthProperty(o);
617
618  // Only handle valid array lengths. Although the spec allows larger
619  // values, this matches historical V8 behavior.
620  if (len > kMaxArrayLength) {
621    ThrowTypeError(MessageTemplate::kInvalidArrayLength);
622  }
623
624  return CycleProtectedArrayJoin<JSArray>(true, o, len, ',', locales, options);
625}
626
627// https://tc39.github.io/ecma262/#sec-array.prototype.tostring
628transitioning javascript builtin ArrayPrototypeToString(
629    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
630  // 1. Let array be ? ToObject(this value).
631  const array: JSReceiver = ToObject_Inline(context, receiver);
632
633  // 2. Let func be ? Get(array, "join").
634  const prop: JSAny = GetProperty(array, 'join');
635  try {
636    // 3. If IsCallable(func) is false, let func be the intrinsic function
637    //    %ObjProto_toString%.
638    const func: Callable = Cast<Callable>(prop) otherwise NotCallable;
639
640    // 4. Return ? Call(func, array).
641    return Call(context, func, array);
642  } label NotCallable {
643    return ObjectToString(context, array);
644  }
645}
646
647// https://tc39.github.io/ecma262/#sec-%typedarray%.prototype.join
648transitioning javascript builtin TypedArrayPrototypeJoin(
649    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
650  const separator: JSAny = arguments[0];
651
652  // Spec: ValidateTypedArray is applied to the this value prior to evaluating
653  // the algorithm.
654  const length = typed_array::ValidateTypedArrayAndGetLength(
655      context, receiver, '%TypedArray%.prototype.join');
656  const typedArray: JSTypedArray = UnsafeCast<JSTypedArray>(receiver);
657
658  return CycleProtectedArrayJoin<JSTypedArray>(
659      false, typedArray, Convert<Number>(length), separator, Undefined,
660      Undefined);
661}
662
663// https://tc39.github.io/ecma262/#sec-%typedarray%.prototype.tolocalestring
664transitioning javascript builtin TypedArrayPrototypeToLocaleString(
665    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
666  const locales: JSAny = arguments[0];
667  const options: JSAny = arguments[1];
668
669  // Spec: ValidateTypedArray is applied to the this value prior to evaluating
670  // the algorithm.
671  const length = typed_array::ValidateTypedArrayAndGetLength(
672      context, receiver, '%TypedArray%.prototype.toLocaleString');
673  const typedArray: JSTypedArray = UnsafeCast<JSTypedArray>(receiver);
674
675  return CycleProtectedArrayJoin<JSTypedArray>(
676      true, typedArray, Convert<Number>(length), ',', locales, options);
677}
678}
679