blob: 42e8b7de94fb5faef263b1c743221789e325c52b [file] [log] [blame]
// 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 {
// Given {elements}, we want to create a non-zero length array of type
// FixedArrayType. Most of this behavior is outsourced to ExtractFixedArray(),
// but the special case of wanting to have a FixedDoubleArray when given a
// zero-length input FixedArray is handled here.
macro Extract<FixedArrayType : type>(
elements: FixedArrayBase, first: Smi, count: Smi,
capacity: Smi): FixedArrayType {
return unsafe_cast<FixedArrayType>(
ExtractFixedArray(elements, first, count, capacity));
}
Extract<FixedDoubleArray>(
elements: FixedArrayBase, first: Smi, count: Smi,
capacity: Smi): FixedDoubleArray {
if (elements == kEmptyFixedArray) {
let newArray: FixedDoubleArray =
AllocateZeroedFixedDoubleArray(convert<intptr>(capacity));
return newArray;
}
return unsafe_cast<FixedDoubleArray>(
ExtractFixedArray(elements, first, count, capacity));
}
macro FastSplice<FixedArrayType : type, ElementType : type>(
args: constexpr Arguments, a: JSArray, length: Smi, newLength: Smi,
lengthDelta: Smi, actualStart: Smi, insertCount: Smi,
actualDeleteCount: Smi): void labels Bailout {
let elements: FixedArrayBase = a.elements;
let elementsMap: Map = elements.map;
// If the spliced array is larger then the
// source array, then allocate a new FixedArrayType to hold the result.
let newElements: FixedArrayBase = elements;
if (elementsMap == kCOWMap || lengthDelta > 0) {
newElements =
Extract<FixedArrayType>(elements, 0, actualStart, newLength);
if (elementsMap == kCOWMap) {
newElements.map = elementsMap;
}
a.elements = newElements;
}
// Copy over inserted elements.
let k: Smi = actualStart;
if (insertCount > 0) {
let typedNewElements: FixedArrayType =
unsafe_cast<FixedArrayType>(newElements);
for (let e: Object of args [2: ]) {
// The argument elements were already validated to be an appropriate
// {ElementType} to store in {FixedArrayType}.
typedNewElements[k++] = unsafe_cast<ElementType>(e);
}
}
// Copy over elements after deleted elements.
let count: Smi = length - actualStart - actualDeleteCount;
while (count > 0) {
let typedElements: FixedArrayType = unsafe_cast<FixedArrayType>(elements);
let typedNewElements: FixedArrayType =
unsafe_cast<FixedArrayType>(newElements);
CopyArrayElement(typedElements, typedNewElements, k - lengthDelta, k);
k++;
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 typedNewElements: FixedArrayType =
unsafe_cast<FixedArrayType>(newElements);
let limit: Smi = elements.length;
while (k < limit) {
StoreArrayHole(typedNewElements, k);
k++;
}
}
// Update the array's length after all the FixedArray shuffling is done.
a.length = newLength;
}
macro FastArraySplice(
context: Context, args: constexpr Arguments, o: JSReceiver,
originalLengthNumber: Number, actualStartNumber: Number, insertCount: Smi,
actualDeleteCountNumber: Number): Object
labels Bailout {
let originalLength: Smi = cast<Smi>(originalLengthNumber) otherwise Bailout;
let actualStart: Smi = cast<Smi>(actualStartNumber) otherwise Bailout;
let actualDeleteCount: Smi =
cast<Smi>(actualDeleteCountNumber) otherwise Bailout;
let lengthDelta: Smi = insertCount - actualDeleteCount;
let newLength: Smi = originalLength + lengthDelta;
let a: JSArray = cast<JSArray>(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;
let oldElementsKind: ElementsKind = elementsKind;
for (let e: Object of args [2: ]) {
if (IsFastSmiElementsKind(elementsKind)) {
if (TaggedIsNotSmi(e)) {
let heapObject: HeapObject = unsafe_cast<HeapObject>(e);
elementsKind = IsHeapNumber(heapObject) ?
AllowDoubleElements(elementsKind) :
AllowNonNumberElements(elementsKind);
}
} else if (IsDoubleElementsKind(elementsKind)) {
if (!IsNumber(e)) {
elementsKind = AllowNonNumberElements(elementsKind);
}
}
}
if (elementsKind != oldElementsKind) {
let smi_elements_kind: Smi = convert<Smi>(convert<int32>(elementsKind));
TransitionElementsKindWithKind(context, a, smi_elements_kind);
}
// Make sure that the length hasn't been changed by side-effect.
let length: Smi = cast<Smi>(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;
}
if (IsFastSmiOrTaggedElementsKind(elementsKind)) {
FastSplice<FixedArray, Object>(
args, a, length, newLength, lengthDelta, actualStart, insertCount,
actualDeleteCount) otherwise Bailout;
} else {
FastSplice<FixedDoubleArray, Number>(
args, a, length, newLength, lengthDelta, actualStart, insertCount,
actualDeleteCount) otherwise Bailout;
}
return deletedResult;
}
macro FillDeletedElementsArray(
context: Context, o: JSReceiver, actualStart: Number,
actualDeleteCount: Number, a: JSReceiver): Object {
// 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: Number = actualStart + k;
// b. Let fromPresent be ? HasProperty(O, from).
let fromPresent: Boolean = 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, k, fromValue);
}
// d. Increment k by 1.
k++;
}
// 12. Perform ? Set(A, "length", actualDeleteCount, true).
SetProperty(context, a, 'length', actualDeleteCount);
return a;
}
// HandleForwardCase implements step 15. "If itemCount < actualDeleteCount,
// then...""
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).
let from: Number = k + actualDeleteCount;
// ii. Let to be ! ToString(k + itemCount).
let to: Number = k + itemCount;
// iii. Let fromPresent be ? HasProperty(O, from).
let fromPresent: Boolean = 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++;
}
// 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, k - 1, kStrict);
// ii. Decrease k by 1.
k--;
}
}
// HandleBackwardCase implements step 16. "Else if itemCount >
// actualDeleteCount, then..."
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).
let from: Number = k + actualDeleteCount - 1;
// ii. Let to be ! ToString(k + itemCount - 1).
let to: Number = k + itemCount - 1;
// iii. Let fromPresent be ? HasProperty(O, from).
let fromPresent: Boolean = 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--;
}
}
macro SlowSplice(
context: Context, arguments: constexpr Arguments, o: JSReceiver,
len: Number, actualStart: Number, insertCount: Smi,
actualDeleteCount: Number): Object {
let affected: Number = len - actualStart - actualDeleteCount;
// 9. Let A be ? ArraySpeciesCreate(O, actualDeleteCount).
let a: JSReceiver = ArraySpeciesCreate(context, o, actualDeleteCount);
let 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 e: Object of arguments [2: ]) {
// b. Perform ? Set(O, ! ToString(k), E, true).
SetProperty(context, o, 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;
}
// https://tc39.github.io/ecma262/#sec-array.prototype.splice
javascript builtin ArraySplice(
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<Smi>(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.
let new_length: Number = len + insertCount - actualDeleteCount;
if (new_length > kMaxSafeInteger) {
ThrowTypeError(context, 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);
}
}