// 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. module array { // Naming convention from elements.cc. We have a similar intent but implement // fastpaths using generics instead of using a class hierarchy for elements // kinds specific implementations. type GenericElementsAccessor; type FastPackedSmiElements; type FastPackedObjectElements; type FastPackedDoubleElements; type FastSmiOrObjectElements; type FastDoubleElements; type DictionaryElements; macro GetLengthProperty(context: Context, o: Object): Number { if (BranchIfFastJSArray(o, context)) { let a: JSArray = unsafe_cast(o); return a.length_fast; } else deferred { return ToLength_Inline(context, GetProperty(context, o, 'length')); } } macro FastArraySplice( context: Context, args: constexpr Arguments, o: Object, originalLengthNumber: Number, actualStartNumber: Number, insertCount: Smi, actualDeleteCountNumber: Number): Object labels Bailout { let originalLength: Smi = cast(originalLengthNumber) otherwise Bailout; let actualStart: Smi = cast(actualStartNumber) otherwise Bailout; let actualDeleteCount: Smi = cast(actualDeleteCountNumber) otherwise Bailout; let lengthDelta: Smi = insertCount - actualDeleteCount; let newLength: Smi = originalLength + lengthDelta; let a: JSArray = cast(o) otherwise Bailout; let map: Map = a.map; if (!IsPrototypeInitialArrayPrototype(context, 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; // For now, only support non-double fast elements if (!IsFastSmiOrTaggedElementsKind(elementsKind)) goto Bailout; if (IsFastSmiElementsKind(elementsKind)) { for (let e: Object of args [2: ]) { if (TaggedIsNotSmi(e)) goto Bailout; } } // Make sure that the length hasn't been changed by side-effect. let length: Smi = cast(a.length) otherwise Bailout; if (originalLength != length) goto Bailout; let deletedResult: JSArray = ExtractFastJSArray(context, a, actualStart, actualDeleteCount); if (newLength == 0) { a.elements = kEmptyFixedArray; a.length = 0; return deletedResult; } let elements: FixedArray = cast(a.elements) otherwise Bailout; let elementsMap: Map = elements.map; // If the source is a COW array or the spliced array is larger then the // source array, then allocate a new FixedArray to hold the result. let newElements: FixedArray = elements; if ((elementsMap == kCOWMap) || (lengthDelta > 0)) { newElements = ExtractFixedArray( elements, 0, actualStart, newLength, kAllFixedArrays); newElements.map = elementsMap; a.elements = newElements; } // Double check that the array is still in fast elements mode assert(IsFastSmiElementsKind(a.map.elements_kind)); // Copy over inserted elements. let k: Smi = actualStart; if (insertCount > 0) { for (let e: Object of args [2: ]) { newElements[k++] = e; } } // Copy over elements after deleted elements. let count: Smi = length - actualStart - actualDeleteCount; while (count > 0) { let e: Object = elements[k - lengthDelta]; newElements[k++] = e; count--; } // Fill rest of spliced FixedArray with the hole, but only if the // destination FixedArray is the original array's, since otherwise the array // is pre-filled with holes. if (elements == newElements) { let limit: Smi = elements.length; while (k < limit) { newElements[k++] = Hole; } } // Update the array's length after all the FixedArray shuffling is done. a.length = newLength; return deletedResult; } // https://tc39.github.io/ecma262/#sec-array.prototype.splice javascript builtin ArraySpliceTorque( context: Context, receiver: Object, ...arguments): Object { // 1. Let O be ? ToObject(this value). let o: JSReceiver = ToObject(context, receiver); // 2. Let len be ? ToLength(? Get(O, "length")). let len: Number = GetLengthProperty(context, o); // 3. Let relativeStart be ? ToInteger(start). let start: Object = arguments[0]; let relativeStart: Number = ToInteger_Inline(context, start); // 4. If relativeStart < 0, let actualStart be max((len + relativeStart), // 0); // else let actualStart be min(relativeStart, len). let 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(arguments.length) - 2; // b. Let dc be ? ToInteger(deleteCount). let deleteCount: Object = arguments[1]; let dc: Number = ToInteger_Inline(context, 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. if (len + insertCount - actualDeleteCount > kMaxSafeInteger) { ThrowRangeError(context, kInvalidArrayLength); } 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. // 9. Let A be ? ArraySpeciesCreate(O, actualDeleteCount). let a: Object = ArraySpeciesCreate(context, o, actualDeleteCount); // 10. Let k be 0. let k: Number = 0; // 11. Repeat, while k < actualDeleteCount while (k < actualDeleteCount) { // a. Let from be ! ToString(actualStart + k). let from: String = ToString_Inline(context, actualStart + k); // b. Let fromPresent be ? HasProperty(O, from). let fromPresent: Oddball = HasProperty(context, o, from); // c. If fromPresent is true, then if (fromPresent == True) { // i. Let fromValue be ? Get(O, from). let fromValue: Object = GetProperty(context, o, from); // ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(k), fromValue). CreateDataProperty(context, a, ToString_Inline(context, k), fromValue); } // d. Increment k by 1. k = k + 1; } // 12. Perform ? Set(A, "length", actualDeleteCount, true). SetProperty(context, a, 'length', actualDeleteCount); // 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. let itemCount: Number = insertCount; // 15. If itemCount < actualDeleteCount, then if (itemCount < actualDeleteCount) { // 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). let from: String = ToString_Inline(context, k + actualDeleteCount); // ii. Let to be ! ToString(k + itemCount). let to: String = ToString_Inline(context, k + itemCount); // iii. Let fromPresent be ? HasProperty(O, from). let fromPresent: Oddball = HasProperty(context, o, from); // iv. If fromPresent is true, then if (fromPresent == True) { // 1. Let fromValue be ? Get(O, from). let fromValue: Object = GetProperty(context, o, from); // 2. Perform ? Set(O, to, fromValue, true). SetProperty(context, o, to, fromValue); // v. Else fromPresent is false, } else { // 1. Perform ? DeletePropertyOrThrow(O, to). DeleteProperty(context, o, to, kStrict); } // vi. Increase k by 1. k = k + 1; } // 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(context, o, ToString_Inline(context, k - 1), kStrict); // ii. Decrease k by 1. k = k - 1; } // 16. Else if itemCount > actualDeleteCount, then } else if (itemCount > actualDeleteCount) { // 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). let from: String = ToString_Inline(context, k + actualDeleteCount - 1); // ii. Let to be ! ToString(k + itemCount - 1). let to: String = ToString_Inline(context, k + itemCount - 1); // iii. Let fromPresent be ? HasProperty(O, from). let fromPresent: Oddball = HasProperty(context, o, from); // iv. If fromPresent is true, then if (fromPresent == True) { // 1. Let fromValue be ? Get(O, from). let fromValue: Object = GetProperty(context, o, from); // 2. Perform ? Set(O, to, fromValue, true). SetProperty(context, o, to, fromValue); // v. Else fromPresent is false, } else { // 1. Perform ? DeletePropertyOrThrow(O, to). DeleteProperty(context, o, to, kStrict); } // vi. Decrease k by 1. k = k - 1; } } // 17. Let k be actualStart. k = 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 e: Object of arguments [2: ]) { // b. Perform ? Set(O, ! ToString(k), E, true). SetProperty(context, o, ToString_Inline(context, k), e); // c. Increase k by 1. k = k + 1; } } // 19. Perform ? Set(O, "length", len - actualDeleteCount + itemCount, // true). SetProperty(context, o, 'length', len - actualDeleteCount + itemCount); return a; } }