| // 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. |
| |
| namespace array { |
| type LoadJoinElementFn = builtin(Context, JSReceiver, uintptr) => JSAny; |
| |
| // Fast C call to write a fixed array (see Buffer.fixedArray) to a single |
| // string. |
| extern macro |
| ArrayBuiltinsAssembler::CallJSArrayArrayJoinConcatToSequentialString( |
| FixedArray, intptr, String, String): String; |
| |
| 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 { |
| return GetProperty(receiver, Convert<Number>(k)); |
| } label IfHole { |
| return kEmptyString; |
| } |
| } |
| |
| 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); |
| } |
| return ToString_Inline(result); |
| } label TypeError { |
| ThrowTypeError(MessageTemplate::kCalledNonCallable, prop); |
| } |
| } |
| |
| // 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; |
| } |
| |
| CannotUseSameArrayAccessor<JSTypedArray>(implicit context: Context)( |
| _loadFn: LoadJoinElementFn, receiver: JSReceiver, _initialMap: Map, |
| _initialLen: Number): bool { |
| const typedArray: JSTypedArray = UnsafeCast<JSTypedArray>(receiver); |
| return IsDetachedBuffer(typedArray.buffer); |
| } |
| |
| // 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); |
| } |
| } |
| |
| // 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; |
| } |
| } |
| |
| // 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; |
| } |
| |
| macro 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)); |
| } |
| } |
| |
| // 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; |
| } |
| |
| 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) |
| }; |
| } |
| |
| 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; |
| } |
| |
| // 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; |
| } |
| } |
| } |
| |
| 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); |
| } |
| |
| 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>; |
| } |
| |
| if (k > 0) { |
| // a. If k > 0, let R be the string-concatenation of R and sep. |
| nofSeparators = nofSeparators + 1; |
| } |
| |
| // b. Let element be ? Get(O, ! ToString(k)). |
| const element: JSAny = loadFn(context, receiver, k++); |
| |
| // c. If element is undefined or null, let next be the empty String; |
| // otherwise, let next be ? ToString(element). |
| let next: String; |
| if constexpr (useToLocaleString) { |
| next = ConvertToLocaleString(context, element, locales, options); |
| if (next == kEmptyString) continue; |
| } else { |
| typeswitch (element) { |
| case (str: String): { |
| if (str == kEmptyString) continue; |
| next = str; |
| } |
| case (num: Number): { |
| next = NumberToString(num); |
| } |
| case (obj: JSAny): { |
| if (IsNullOrUndefined(obj)) continue; |
| next = string::ToString(context, obj); |
| } |
| } |
| } |
| |
| // d. Set R to the string-concatenation of R and next. |
| buffer.Add(next, nofSeparators, separatorLength); |
| nofSeparators = 0; |
| } |
| |
| // Add any separators at the end. |
| buffer.AddSeparators(nofSeparators, separatorLength, true); |
| |
| // 8. Return R. |
| return BufferJoin(buffer, sep); |
| } |
| |
| 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); |
| } |
| } else { |
| loadFn = LoadJoinElement<array::DictionaryElements>; |
| } |
| } |
| else { |
| goto IfSlowPath; |
| } |
| } label IfSlowPath { |
| loadFn = LoadJoinElement<array::GenericElementsAccessor>; |
| } |
| return ArrayJoinImpl<JSArray>( |
| receiver, sep, lenNumber, useToLocaleString, locales, options, loadFn); |
| } |
| |
| 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>; |
| } else { |
| 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; |
| } |
| } |
| return ArrayJoinImpl<JSTypedArray>( |
| receiver, sep, lenNumber, useToLocaleString, locales, options, loadFn); |
| } |
| |
| // 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 { |
| typeswitch (*NativeContextSlot(ContextSlot::ARRAY_JOIN_STACK_INDEX)) { |
| case (Undefined): { |
| goto IfUninitialized; |
| } |
| case (stack: FixedArray): { |
| return stack; |
| } |
| } |
| } |
| |
| macro SetJoinStack(implicit context: Context)(stack: FixedArray): void { |
| *NativeContextSlot(ContextSlot::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++) { |
| const previouslyVisited: Object = stack.objects[i]; |
| |
| // Add `receiver` to the first open slot |
| if (previouslyVisited == TheHole) { |
| stack.objects[i] = receiver; |
| 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 = |
| 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) { |
| stack.objects[0] = receiver; |
| } else if (JoinStackPush(stack, receiver) == False) |
| deferred { |
| return false; |
| } |
| } label IfUninitialized { |
| const stack: FixedArray = |
| AllocateFixedArrayWithHoles(kMinJoinStackSize, AllocationFlag::kNone); |
| stack.objects[0] = receiver; |
| SetJoinStack(stack); |
| } |
| return true; |
| } |
| |
| // 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); |
| } |
| else { |
| stack.objects[i] = TheHole; |
| } |
| 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. |
| if (stack.objects[0] == receiver && len == kMinJoinStackSize) { |
| StoreFixedArrayElement(stack, 0, TheHole, SKIP_WRITE_BARRIER); |
| } else |
| deferred { |
| JoinStackPop(stack, receiver); |
| } |
| } |
| |
| // 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); |
| } |
| } else { |
| return kEmptyString; |
| } |
| } |
| |
| // 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]; |
| |
| // 1. Let O be ? ToObject(this value). |
| const o: JSReceiver = ToObject_Inline(context, receiver); |
| |
| // 2. Let len be ? ToLength(? Get(O, "length")). |
| const len: Number = GetLengthProperty(o); |
| |
| // Only handle valid array lengths. Although the spec allows larger |
| // values, this matches historical V8 behavior. |
| if (len > kMaxArrayIndex + 1) |
| ThrowTypeError(MessageTemplate::kInvalidArrayLength); |
| |
| return CycleProtectedArrayJoin<JSArray>( |
| false, o, len, separator, Undefined, Undefined); |
| } |
| |
| // 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]; |
| |
| // 1. Let O be ? ToObject(this value). |
| const o: JSReceiver = ToObject_Inline(context, receiver); |
| |
| // 2. Let len be ? ToLength(? Get(O, "length")). |
| const len: Number = GetLengthProperty(o); |
| |
| // Only handle valid array lengths. Although the spec allows larger |
| // values, this matches historical V8 behavior. |
| if (len > kMaxArrayIndex + 1) |
| ThrowTypeError(MessageTemplate::kInvalidArrayLength); |
| |
| 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); |
| } |
| } |
| |
| // 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]; |
| |
| // 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); |
| |
| return CycleProtectedArrayJoin<JSTypedArray>( |
| false, typedArray, length, separator, Undefined, Undefined); |
| } |
| |
| // 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]; |
| |
| // 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); |
| |
| return CycleProtectedArrayJoin<JSTypedArray>( |
| true, typedArray, length, ',', locales, options); |
| } |
| } |