| // 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 { |
| // Given {source}, we want to create a non-zero length array of type |
| // FixedArrayType with the specified {result_capacity}. Starting from |
| // {startIndex}, {count} number of elements are copied to the newly |
| // created result array. Most of this behavior is outsourced to |
| // ExtractFixedArray(). We handle the case where the {source} is |
| // EmptyFixedArray but result is expected to be a FixedDoubleArray. |
| macro Extract(implicit context: Context)( |
| source: FixedArray, startIndex: Smi, count: Smi, |
| resultCapacity: Smi): FixedArray { |
| return ExtractFixedArray( |
| source, Convert<intptr>(startIndex), Convert<intptr>(count), |
| Convert<intptr>(resultCapacity)); |
| } |
| |
| macro Extract(implicit context: Context)( |
| source: FixedDoubleArray|EmptyFixedArray, startIndex: Smi, count: Smi, |
| resultCapacity: Smi): FixedDoubleArray|EmptyFixedArray { |
| typeswitch (source) { |
| case (EmptyFixedArray): { |
| // ExtractFixedDoubleArray expects {source} to be a FixedDoubleArray. |
| // Handle the case where {source} is empty here. |
| return AllocateFixedDoubleArrayWithHoles( |
| Convert<intptr>(resultCapacity), |
| AllocationFlag::kAllowLargeObjectAllocation); |
| } |
| case (source: FixedDoubleArray): { |
| return ExtractFixedDoubleArray( |
| source, Convert<intptr>(startIndex), Convert<intptr>(count), |
| Convert<intptr>(resultCapacity)); |
| } |
| } |
| } |
| |
| macro DoMoveElements<FixedArrayType : type extends FixedArrayBase>( |
| elements: FixedArrayType, dstIndex: Smi, srcIndex: Smi, count: Smi): void { |
| TorqueMoveElements( |
| elements, Convert<intptr>(dstIndex), Convert<intptr>(srcIndex), |
| Convert<intptr>(count)); |
| } |
| |
| macro StoreHoles<FixedArrayType : type extends FixedArrayBase>( |
| elements: FixedArrayType, holeStartIndex: Smi, holeEndIndex: Smi): void { |
| for (let i: Smi = holeStartIndex; i < holeEndIndex; i++) { |
| array::StoreArrayHole(elements, i); |
| } |
| } |
| |
| macro DoCopyElements<FixedArrayType : type extends FixedArrayBase>( |
| dstElements: FixedArrayType, dstIndex: Smi, srcElements: FixedArrayType, |
| srcIndex: Smi, count: Smi): void { |
| TorqueCopyElements( |
| dstElements, Convert<intptr>(dstIndex), srcElements, |
| Convert<intptr>(srcIndex), Convert<intptr>(count)); |
| } |
| |
| macro |
| FastSplice<FixedArrayType : type extends FixedArrayBase, ElementType: type>( |
| implicit context: Context)( |
| args: Arguments, a: JSArray, length: Smi, newLength: Smi, actualStart: Smi, |
| insertCount: Smi, actualDeleteCount: Smi): void { |
| // Make sure elements are writable. |
| array::EnsureWriteableFastElements(a); |
| |
| if (insertCount != actualDeleteCount) { |
| const elements = UnsafeCast<(FixedArrayType | EmptyFixedArray)>(a.elements); |
| const dstIndex: Smi = actualStart + insertCount; |
| const srcIndex: Smi = actualStart + actualDeleteCount; |
| const count: Smi = length - actualDeleteCount - actualStart; |
| if (insertCount < actualDeleteCount) { |
| // Shrink. |
| DoMoveElements( |
| UnsafeCast<FixedArrayType>(elements), dstIndex, srcIndex, count); |
| StoreHoles(UnsafeCast<FixedArrayType>(elements), newLength, length); |
| } else if (insertCount > actualDeleteCount) { |
| // If the backing store is big enough, then moving elements is enough. |
| if (newLength <= elements.length) { |
| DoMoveElements( |
| UnsafeCast<FixedArrayType>(elements), dstIndex, srcIndex, count); |
| } else { |
| // Grow. |
| const capacity: Smi = CalculateNewElementsCapacity(newLength); |
| const newElements: FixedArrayType = UnsafeCast<FixedArrayType>( |
| Extract(elements, 0, actualStart, capacity)); |
| a.elements = newElements; |
| if (elements.length > 0) { |
| DoCopyElements( |
| newElements, dstIndex, UnsafeCast<FixedArrayType>(elements), |
| srcIndex, count); |
| } |
| } |
| } |
| } |
| |
| // Copy arguments. |
| let k: Smi = actualStart; |
| if (insertCount > 0) { |
| const typedNewElements: FixedArrayType = |
| UnsafeCast<FixedArrayType>(a.elements); |
| for (let i: intptr = 2; i < args.length; ++i) { |
| const e: JSAny = args[i]; |
| // The argument elements were already validated to be an appropriate |
| // {ElementType} to store in {FixedArrayType}. |
| typedNewElements[k++] = UnsafeCast<ElementType>(e); |
| } |
| } |
| |
| // Update the array's length after all the FixedArray shuffling is done. |
| a.length = newLength; |
| } |
| |
| transitioning macro FastArraySplice( |
| context: Context, args: Arguments, o: JSReceiver, |
| originalLengthNumber: Number, actualStartNumber: Number, insertCount: Smi, |
| actualDeleteCountNumber: Number): JSAny |
| labels Bailout { |
| const originalLength: Smi = Cast<Smi>(originalLengthNumber) otherwise Bailout; |
| const actualStart: Smi = Cast<Smi>(actualStartNumber) otherwise Bailout; |
| const actualDeleteCount: Smi = |
| Cast<Smi>(actualDeleteCountNumber) otherwise Bailout; |
| const lengthDelta: Smi = insertCount - actualDeleteCount; |
| const newLength: Smi = originalLength + lengthDelta; |
| |
| const a: JSArray = Cast<JSArray>(o) otherwise Bailout; |
| |
| const map: Map = a.map; |
| if (!IsPrototypeInitialArrayPrototype(map)) goto Bailout; |
| if (IsNoElementsProtectorCellInvalid()) goto Bailout; |
| if (IsArraySpeciesProtectorCellInvalid()) goto Bailout; |
| |
| // Fast path only works on fast elements kind and with writable length. |
| let elementsKind: ElementsKind = EnsureArrayPushable(map) otherwise Bailout; |
| if (!IsFastElementsKind(elementsKind)) goto Bailout; |
| |
| const oldElementsKind: ElementsKind = elementsKind; |
| for (let i: intptr = 2; i < args.length; ++i) { |
| const e: JSAny = args[i]; |
| if (IsFastSmiElementsKind(elementsKind)) { |
| if (TaggedIsNotSmi(e)) { |
| const heapObject: HeapObject = UnsafeCast<HeapObject>(e); |
| elementsKind = IsHeapNumber(heapObject) ? |
| AllowDoubleElements(elementsKind) : |
| AllowNonNumberElements(elementsKind); |
| } |
| } else if (IsDoubleElementsKind(elementsKind)) { |
| if (!IsNumber(e)) { |
| elementsKind = AllowNonNumberElements(elementsKind); |
| } |
| } |
| } |
| |
| if (elementsKind != oldElementsKind) { |
| const smiElementsKind: Smi = Convert<Smi>(Convert<int32>(elementsKind)); |
| TransitionElementsKindWithKind(context, a, smiElementsKind); |
| } |
| |
| // Make sure that the length hasn't been changed by side-effect. |
| const length: Smi = Cast<Smi>(a.length) otherwise Bailout; |
| if (originalLength != length) goto Bailout; |
| |
| const deletedResult: JSArray = |
| ExtractFastJSArray(context, a, actualStart, actualDeleteCount); |
| |
| if (newLength == 0) { |
| a.elements = kEmptyFixedArray; |
| a.length = 0; |
| return deletedResult; |
| } |
| |
| if (IsFastSmiOrTaggedElementsKind(elementsKind)) { |
| FastSplice<FixedArray, JSAny>( |
| args, a, length, newLength, actualStart, insertCount, |
| actualDeleteCount); |
| } else { |
| FastSplice<FixedDoubleArray, Number>( |
| args, a, length, newLength, actualStart, insertCount, |
| actualDeleteCount); |
| } |
| |
| return deletedResult; |
| } |
| |
| transitioning macro FillDeletedElementsArray( |
| context: Context, o: JSReceiver, actualStart: Number, |
| actualDeleteCount: Number, a: JSReceiver): JSAny { |
| // 10. Let k be 0. |
| let k: Number = 0; |
| |
| // 11. Repeat, while k < actualDeleteCount |
| while (k < actualDeleteCount) { |
| // a. Let from be ! ToString(actualStart + k). |
| const from: Number = actualStart + k; |
| |
| // b. Let fromPresent be ? HasProperty(O, from). |
| const fromPresent: Boolean = HasProperty(o, from); |
| |
| // c. If fromPresent is true, then |
| if (fromPresent == True) { |
| // i. Let fromValue be ? Get(O, from). |
| const fromValue: JSAny = GetProperty(o, from); |
| |
| // ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(k), fromValue). |
| FastCreateDataProperty(a, k, fromValue); |
| } |
| |
| // d. Increment k by 1. |
| k++; |
| } |
| // 12. Perform ? Set(A, "length", actualDeleteCount, true). |
| SetProperty(a, kLengthString, actualDeleteCount); |
| return a; |
| } |
| |
| // HandleForwardCase implements step 15. "If itemCount < actualDeleteCount, |
| // then..."" |
| transitioning macro HandleForwardCase( |
| context: Context, o: JSReceiver, len: Number, itemCount: Number, |
| actualStart: Number, actualDeleteCount: Number): void { |
| // 15. If itemCount < actualDeleteCount, then |
| // a. Let k be actualStart. |
| let k: Number = actualStart; |
| |
| // b. Repeat, while k < (len - actualDeleteCount) |
| while (k < (len - actualDeleteCount)) { |
| // i. Let from be ! ToString(k + actualDeleteCount). |
| const from: Number = k + actualDeleteCount; |
| // ii. Let to be ! ToString(k + itemCount). |
| const to: Number = k + itemCount; |
| |
| // iii. Let fromPresent be ? HasProperty(O, from). |
| const fromPresent: Boolean = HasProperty(o, from); |
| |
| // iv. If fromPresent is true, then |
| if (fromPresent == True) { |
| // 1. Let fromValue be ? Get(O, from). |
| const fromValue: JSAny = GetProperty(o, from); |
| |
| // 2. Perform ? Set(O, to, fromValue, true). |
| SetProperty(o, to, fromValue); |
| |
| // v. Else fromPresent is false, |
| } else { |
| // 1. Perform ? DeletePropertyOrThrow(O, to). |
| DeleteProperty(o, to, LanguageMode::kStrict); |
| } |
| // vi. Increase k by 1. |
| k++; |
| } |
| |
| // c. Let k be len. |
| k = len; |
| |
| // d. Repeat, while k > (len - actualDeleteCount + itemCount) |
| while (k > (len - actualDeleteCount + itemCount)) { |
| // i. Perform ? DeletePropertyOrThrow(O, ! ToString(k - 1)). |
| DeleteProperty(o, k - 1, LanguageMode::kStrict); |
| // ii. Decrease k by 1. |
| k--; |
| } |
| } |
| |
| // HandleBackwardCase implements step 16. "Else if itemCount > |
| // actualDeleteCount, then..." |
| transitioning macro HandleBackwardCase( |
| context: Context, o: JSReceiver, len: Number, itemCount: Number, |
| actualStart: Number, actualDeleteCount: Number): void { |
| // 16. Else if itemCount > actualDeleteCount, then |
| // a. Let k be (len - actualDeleteCount). |
| let k: Number = len - actualDeleteCount; |
| |
| // b. Repeat, while k > actualStart |
| while (k > actualStart) { |
| // i. Let from be ! ToString(k + actualDeleteCount - 1). |
| const from: Number = k + actualDeleteCount - 1; |
| |
| // ii. Let to be ! ToString(k + itemCount - 1). |
| const to: Number = k + itemCount - 1; |
| |
| // iii. Let fromPresent be ? HasProperty(O, from). |
| const fromPresent: Boolean = HasProperty(o, from); |
| |
| // iv. If fromPresent is true, then |
| if (fromPresent == True) { |
| // 1. Let fromValue be ? Get(O, from). |
| const fromValue: JSAny = GetProperty(o, from); |
| |
| // 2. Perform ? Set(O, to, fromValue, true). |
| SetProperty(o, to, fromValue); |
| |
| // v. Else fromPresent is false, |
| } else { |
| // 1. Perform ? DeletePropertyOrThrow(O, to). |
| DeleteProperty(o, to, LanguageMode::kStrict); |
| } |
| |
| // vi. Decrease k by 1. |
| k--; |
| } |
| } |
| |
| transitioning macro SlowSplice( |
| context: Context, arguments: Arguments, o: JSReceiver, len: Number, |
| actualStart: Number, insertCount: Smi, actualDeleteCount: Number): JSAny { |
| // 9. Let A be ? ArraySpeciesCreate(O, actualDeleteCount). |
| const a: JSReceiver = ArraySpeciesCreate(context, o, actualDeleteCount); |
| const itemCount: Number = insertCount; |
| |
| // Steps 9 through 12: creating the array of deleted elements. |
| FillDeletedElementsArray(context, o, actualStart, actualDeleteCount, a); |
| |
| // 13. Let items be a List whose elements are, in left-to-right order, |
| // the portion of the actual argument list starting with the third |
| // argument. The list is empty if fewer than three arguments were |
| // passed. |
| // 14. Let itemCount be the Number of elements in items. |
| // (done above). |
| |
| // 15. If itemCount < actualDeleteCount, then |
| if (itemCount < actualDeleteCount) { |
| HandleForwardCase( |
| context, o, len, itemCount, actualStart, actualDeleteCount); |
| // 16. Else if itemCount > actualDeleteCount, then |
| } else if (itemCount > actualDeleteCount) { |
| HandleBackwardCase( |
| context, o, len, itemCount, actualStart, actualDeleteCount); |
| } |
| |
| // 17. Let k be actualStart. |
| let k: Number = actualStart; |
| |
| // 18. Repeat, while items is not empty |
| // a. Remove the first element from items and let E be the value of that |
| // element. |
| if (arguments.length > 2) { |
| for (let i: intptr = 2; i < arguments.length; ++i) { |
| const e: JSAny = arguments[i]; |
| // b. Perform ? Set(O, ! ToString(k), E, true). |
| SetProperty(o, k, e); |
| |
| // c. Increase k by 1. |
| k = k + 1; |
| } |
| } |
| |
| // 19. Perform ? Set(O, "length", len - actualDeleteCount + itemCount, |
| // true). |
| SetProperty(o, kLengthString, len - actualDeleteCount + itemCount); |
| |
| return a; |
| } |
| |
| // https://tc39.github.io/ecma262/#sec-array.prototype.splice |
| transitioning javascript builtin |
| ArrayPrototypeSplice( |
| js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny { |
| // 1. Let O be ? ToObject(this value). |
| const o: JSReceiver = ToObject(context, receiver); |
| |
| // 2. Let len be ? ToLength(? Get(O, "length")). |
| const len: Number = GetLengthProperty(o); |
| |
| // 3. Let relativeStart be ? ToInteger(start). |
| const start: JSAny = arguments[0]; |
| const relativeStart: Number = ToInteger_Inline(start); |
| |
| // 4. If relativeStart < 0, let actualStart be max((len + relativeStart), |
| // 0); |
| // else let actualStart be min(relativeStart, len). |
| const actualStart: Number = relativeStart < 0 ? |
| Max((len + relativeStart), 0) : |
| Min(relativeStart, len); |
| |
| let insertCount: Smi; |
| let actualDeleteCount: Number; |
| // 5. If the Number of actual arguments is 0, then |
| if (arguments.length == 0) { |
| // a. Let insertCount be 0. |
| insertCount = 0; |
| // b. Let actualDeleteCount be 0. |
| actualDeleteCount = 0; |
| // 6. Else if the Number of actual arguments is 1, then |
| } else if (arguments.length == 1) { |
| // a. Let insertCount be 0. |
| insertCount = 0; |
| // b. Let actualDeleteCount be len - actualStart. |
| actualDeleteCount = len - actualStart; |
| // 7. Else, |
| } else { |
| // a. Let insertCount be the Number of actual arguments minus 2. |
| insertCount = Convert<Smi>(arguments.length) - 2; |
| // b. Let dc be ? ToInteger(deleteCount). |
| const deleteCount: JSAny = arguments[1]; |
| const dc: Number = ToInteger_Inline(deleteCount); |
| // c. Let actualDeleteCount be min(max(dc, 0), len - actualStart). |
| actualDeleteCount = Min(Max(dc, 0), len - actualStart); |
| } |
| |
| // 8. If len + insertCount - actualDeleteCount > 2^53-1, throw a |
| // Bailout exception. |
| const newLength: Number = len + insertCount - actualDeleteCount; |
| if (newLength > kMaxSafeInteger) { |
| ThrowTypeError(MessageTemplate::kInvalidArrayLength, start); |
| } |
| |
| try { |
| return FastArraySplice( |
| context, arguments, o, len, actualStart, insertCount, actualDeleteCount) |
| otherwise Bailout; |
| } label Bailout {} |
| |
| // If the fast case fails, just continue with the slow, correct, |
| // spec-compliant case. |
| return SlowSplice( |
| context, arguments, o, len, actualStart, insertCount, actualDeleteCount); |
| } |
| } |