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