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// Given {source}, we want to create a non-zero length array of type
7// FixedArrayType with the specified {result_capacity}. Starting from
8// {startIndex}, {count} number of elements are copied to the newly
9// created result array. Most of this behavior is outsourced to
10// ExtractFixedArray(). We handle the case where the {source} is
11// EmptyFixedArray but result is expected to be a FixedDoubleArray.
12macro Extract(implicit context: Context)(
13    source: FixedArray, startIndex: Smi, count: Smi,
14    resultCapacity: Smi): FixedArray {
15  return ExtractFixedArray(
16      source, Convert<intptr>(startIndex), Convert<intptr>(count),
17      Convert<intptr>(resultCapacity));
18}
19
20macro Extract(implicit context: Context)(
21    source: FixedDoubleArray|EmptyFixedArray, startIndex: Smi, count: Smi,
22    resultCapacity: Smi): FixedDoubleArray|EmptyFixedArray {
23  typeswitch (source) {
24    case (EmptyFixedArray): {
25      // ExtractFixedDoubleArray expects {source} to be a FixedDoubleArray.
26      // Handle the case where {source} is empty here.
27      return AllocateFixedDoubleArrayWithHoles(
28          Convert<intptr>(resultCapacity),
29          AllocationFlag::kAllowLargeObjectAllocation);
30    }
31    case (source: FixedDoubleArray): {
32      return ExtractFixedDoubleArray(
33          source, Convert<intptr>(startIndex), Convert<intptr>(count),
34          Convert<intptr>(resultCapacity));
35    }
36  }
37}
38
39macro DoMoveElements<FixedArrayType : type extends FixedArrayBase>(
40    elements: FixedArrayType, dstIndex: Smi, srcIndex: Smi, count: Smi): void {
41  TorqueMoveElements(
42      elements, Convert<intptr>(dstIndex), Convert<intptr>(srcIndex),
43      Convert<intptr>(count));
44}
45
46macro StoreHoles<FixedArrayType : type extends FixedArrayBase>(
47    elements: FixedArrayType, holeStartIndex: Smi, holeEndIndex: Smi): void {
48  for (let i: Smi = holeStartIndex; i < holeEndIndex; i++) {
49    array::StoreArrayHole(elements, i);
50  }
51}
52
53macro DoCopyElements<FixedArrayType : type extends FixedArrayBase>(
54    dstElements: FixedArrayType, dstIndex: Smi, srcElements: FixedArrayType,
55    srcIndex: Smi, count: Smi): void {
56  TorqueCopyElements(
57      dstElements, Convert<intptr>(dstIndex), srcElements,
58      Convert<intptr>(srcIndex), Convert<intptr>(count));
59}
60
61macro
62FastSplice<FixedArrayType : type extends FixedArrayBase, ElementType: type>(
63    implicit context: Context)(
64    args: Arguments, a: JSArray, length: Smi, newLength: Smi, actualStart: Smi,
65    insertCount: Smi, actualDeleteCount: Smi): void {
66  // Make sure elements are writable.
67  array::EnsureWriteableFastElements(a);
68
69  if (insertCount != actualDeleteCount) {
70    const elements = UnsafeCast<(FixedArrayType | EmptyFixedArray)>(a.elements);
71    const dstIndex: Smi = actualStart + insertCount;
72    const srcIndex: Smi = actualStart + actualDeleteCount;
73    const count: Smi = length - actualDeleteCount - actualStart;
74    if (insertCount < actualDeleteCount) {
75      // Shrink.
76      DoMoveElements(
77          UnsafeCast<FixedArrayType>(elements), dstIndex, srcIndex, count);
78      StoreHoles(UnsafeCast<FixedArrayType>(elements), newLength, length);
79    } else if (insertCount > actualDeleteCount) {
80      // If the backing store is big enough, then moving elements is enough.
81      if (newLength <= elements.length) {
82        DoMoveElements(
83            UnsafeCast<FixedArrayType>(elements), dstIndex, srcIndex, count);
84      } else {
85        // Grow.
86        const capacity: Smi = CalculateNewElementsCapacity(newLength);
87        const newElements: FixedArrayType = UnsafeCast<FixedArrayType>(
88            Extract(elements, 0, actualStart, capacity));
89        a.elements = newElements;
90        if (elements.length > 0) {
91          DoCopyElements(
92              newElements, dstIndex, UnsafeCast<FixedArrayType>(elements),
93              srcIndex, count);
94        }
95      }
96    }
97  }
98
99  // Copy arguments.
100  let k: Smi = actualStart;
101  if (insertCount > 0) {
102    const typedNewElements: FixedArrayType =
103        UnsafeCast<FixedArrayType>(a.elements);
104    for (let i: intptr = 2; i < args.length; ++i) {
105      const e: JSAny = args[i];
106      // The argument elements were already validated to be an appropriate
107      // {ElementType} to store in {FixedArrayType}.
108      typedNewElements[k++] = UnsafeCast<ElementType>(e);
109    }
110  }
111
112  // Update the array's length after all the FixedArray shuffling is done.
113  a.length = newLength;
114}
115
116transitioning macro FastArraySplice(
117    context: Context, args: Arguments, o: JSReceiver,
118    originalLengthNumber: Number, actualStartNumber: Number, insertCount: Smi,
119    actualDeleteCountNumber: Number): JSAny
120    labels Bailout {
121  const originalLength: Smi = Cast<Smi>(originalLengthNumber) otherwise Bailout;
122  const actualStart: Smi = Cast<Smi>(actualStartNumber) otherwise Bailout;
123  const actualDeleteCount: Smi =
124      Cast<Smi>(actualDeleteCountNumber) otherwise Bailout;
125  const lengthDelta: Smi = insertCount - actualDeleteCount;
126  const newLength: Smi = originalLength + lengthDelta;
127
128  const a: JSArray = Cast<JSArray>(o) otherwise Bailout;
129
130  const map: Map = a.map;
131  if (!IsPrototypeInitialArrayPrototype(map)) goto Bailout;
132  if (IsNoElementsProtectorCellInvalid()) goto Bailout;
133  if (IsArraySpeciesProtectorCellInvalid()) goto Bailout;
134
135  // Fast path only works on fast elements kind and with writable length.
136  let elementsKind: ElementsKind = EnsureArrayPushable(map) otherwise Bailout;
137  if (!IsFastElementsKind(elementsKind)) goto Bailout;
138
139  const oldElementsKind: ElementsKind = elementsKind;
140  for (let i: intptr = 2; i < args.length; ++i) {
141    const e: JSAny = args[i];
142    if (IsFastSmiElementsKind(elementsKind)) {
143      if (TaggedIsNotSmi(e)) {
144        const heapObject: HeapObject = UnsafeCast<HeapObject>(e);
145        elementsKind = IsHeapNumber(heapObject) ?
146            AllowDoubleElements(elementsKind) :
147            AllowNonNumberElements(elementsKind);
148      }
149    } else if (IsDoubleElementsKind(elementsKind)) {
150      if (!IsNumber(e)) {
151        elementsKind = AllowNonNumberElements(elementsKind);
152      }
153    }
154  }
155
156  if (elementsKind != oldElementsKind) {
157    const smiElementsKind: Smi = Convert<Smi>(Convert<int32>(elementsKind));
158    TransitionElementsKindWithKind(context, a, smiElementsKind);
159  }
160
161  // Make sure that the length hasn't been changed by side-effect.
162  const length: Smi = Cast<Smi>(a.length) otherwise Bailout;
163  if (originalLength != length) goto Bailout;
164
165  const deletedResult: JSArray =
166      ExtractFastJSArray(context, a, actualStart, actualDeleteCount);
167
168  if (newLength == 0) {
169    a.elements = kEmptyFixedArray;
170    a.length = 0;
171    return deletedResult;
172  }
173
174  if (IsFastSmiOrTaggedElementsKind(elementsKind)) {
175    FastSplice<FixedArray, JSAny>(
176        args, a, length, newLength, actualStart, insertCount,
177        actualDeleteCount);
178  } else {
179    FastSplice<FixedDoubleArray, Number>(
180        args, a, length, newLength, actualStart, insertCount,
181        actualDeleteCount);
182  }
183
184  return deletedResult;
185}
186
187transitioning macro FillDeletedElementsArray(
188    context: Context, o: JSReceiver, actualStart: Number,
189    actualDeleteCount: Number, a: JSReceiver): JSAny {
190  // 10. Let k be 0.
191  let k: Number = 0;
192
193  // 11. Repeat, while k < actualDeleteCount
194  while (k < actualDeleteCount) {
195    // a. Let from be ! ToString(actualStart + k).
196    const from: Number = actualStart + k;
197
198    // b. Let fromPresent be ? HasProperty(O, from).
199    const fromPresent: Boolean = HasProperty(o, from);
200
201    // c. If fromPresent is true, then
202    if (fromPresent == True) {
203      // i. Let fromValue be ? Get(O, from).
204      const fromValue: JSAny = GetProperty(o, from);
205
206      // ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(k), fromValue).
207      FastCreateDataProperty(a, k, fromValue);
208    }
209
210    // d. Increment k by 1.
211    k++;
212  }
213  // 12. Perform ? Set(A, "length", actualDeleteCount, true).
214  SetProperty(a, kLengthString, actualDeleteCount);
215  return a;
216}
217
218// HandleForwardCase implements step 15. "If itemCount < actualDeleteCount,
219// then...""
220transitioning macro HandleForwardCase(
221    context: Context, o: JSReceiver, len: Number, itemCount: Number,
222    actualStart: Number, actualDeleteCount: Number): void {
223  // 15. If itemCount < actualDeleteCount, then
224  // a. Let k be actualStart.
225  let k: Number = actualStart;
226
227  // b. Repeat, while k < (len - actualDeleteCount)
228  while (k < (len - actualDeleteCount)) {
229    // i. Let from be ! ToString(k + actualDeleteCount).
230    const from: Number = k + actualDeleteCount;
231    // ii. Let to be ! ToString(k + itemCount).
232    const to: Number = k + itemCount;
233
234    // iii. Let fromPresent be ? HasProperty(O, from).
235    const fromPresent: Boolean = HasProperty(o, from);
236
237    // iv. If fromPresent is true, then
238    if (fromPresent == True) {
239      // 1. Let fromValue be ? Get(O, from).
240      const fromValue: JSAny = GetProperty(o, from);
241
242      // 2. Perform ? Set(O, to, fromValue, true).
243      SetProperty(o, to, fromValue);
244
245      // v. Else fromPresent is false,
246    } else {
247      // 1. Perform ? DeletePropertyOrThrow(O, to).
248      DeleteProperty(o, to, LanguageMode::kStrict);
249    }
250    // vi. Increase k by 1.
251    k++;
252  }
253
254  // c. Let k be len.
255  k = len;
256
257  // d. Repeat, while k > (len - actualDeleteCount + itemCount)
258  while (k > (len - actualDeleteCount + itemCount)) {
259    // i. Perform ? DeletePropertyOrThrow(O, ! ToString(k - 1)).
260    DeleteProperty(o, k - 1, LanguageMode::kStrict);
261    // ii. Decrease k by 1.
262    k--;
263  }
264}
265
266// HandleBackwardCase implements step 16. "Else if itemCount >
267// actualDeleteCount, then..."
268transitioning macro HandleBackwardCase(
269    context: Context, o: JSReceiver, len: Number, itemCount: Number,
270    actualStart: Number, actualDeleteCount: Number): void {
271  // 16. Else if itemCount > actualDeleteCount, then
272  // a. Let k be (len - actualDeleteCount).
273  let k: Number = len - actualDeleteCount;
274
275  // b. Repeat, while k > actualStart
276  while (k > actualStart) {
277    // i. Let from be ! ToString(k + actualDeleteCount - 1).
278    const from: Number = k + actualDeleteCount - 1;
279
280    // ii. Let to be ! ToString(k + itemCount - 1).
281    const to: Number = k + itemCount - 1;
282
283    // iii. Let fromPresent be ? HasProperty(O, from).
284    const fromPresent: Boolean = HasProperty(o, from);
285
286    // iv. If fromPresent is true, then
287    if (fromPresent == True) {
288      // 1. Let fromValue be ? Get(O, from).
289      const fromValue: JSAny = GetProperty(o, from);
290
291      // 2. Perform ? Set(O, to, fromValue, true).
292      SetProperty(o, to, fromValue);
293
294      // v. Else fromPresent is false,
295    } else {
296      // 1. Perform ? DeletePropertyOrThrow(O, to).
297      DeleteProperty(o, to, LanguageMode::kStrict);
298    }
299
300    // vi. Decrease k by 1.
301    k--;
302  }
303}
304
305transitioning macro SlowSplice(
306    context: Context, arguments: Arguments, o: JSReceiver, len: Number,
307    actualStart: Number, insertCount: Smi, actualDeleteCount: Number): JSAny {
308  // 9. Let A be ? ArraySpeciesCreate(O, actualDeleteCount).
309  const a: JSReceiver = ArraySpeciesCreate(context, o, actualDeleteCount);
310  const itemCount: Number = insertCount;
311
312  // Steps 9 through 12: creating the array of deleted elements.
313  FillDeletedElementsArray(context, o, actualStart, actualDeleteCount, a);
314
315  // 13. Let items be a List whose elements are, in left-to-right order,
316  //     the portion of the actual argument list starting with the third
317  //     argument. The list is empty if fewer than three arguments were
318  //     passed.
319  // 14. Let itemCount be the Number of elements in items.
320  // (done above).
321
322  // 15. If itemCount < actualDeleteCount, then
323  if (itemCount < actualDeleteCount) {
324    HandleForwardCase(
325        context, o, len, itemCount, actualStart, actualDeleteCount);
326    // 16. Else if itemCount > actualDeleteCount, then
327  } else if (itemCount > actualDeleteCount) {
328    HandleBackwardCase(
329        context, o, len, itemCount, actualStart, actualDeleteCount);
330  }
331
332  // 17. Let k be actualStart.
333  let k: Number = actualStart;
334
335  // 18. Repeat, while items is not empty
336  //   a. Remove the first element from items and let E be the value of that
337  //   element.
338  if (arguments.length > 2) {
339    for (let i: intptr = 2; i < arguments.length; ++i) {
340      const e: JSAny = arguments[i];
341      // b. Perform ? Set(O, ! ToString(k), E, true).
342      SetProperty(o, k, e);
343
344      // c. Increase k by 1.
345      k = k + 1;
346    }
347  }
348
349  // 19. Perform ? Set(O, "length", len - actualDeleteCount + itemCount,
350  // true).
351  SetProperty(o, kLengthString, len - actualDeleteCount + itemCount);
352
353  return a;
354}
355
356// https://tc39.github.io/ecma262/#sec-array.prototype.splice
357transitioning javascript builtin
358ArrayPrototypeSplice(
359    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
360  // 1. Let O be ? ToObject(this value).
361  const o: JSReceiver = ToObject(context, receiver);
362
363  // 2. Let len be ? ToLength(? Get(O, "length")).
364  const len: Number = GetLengthProperty(o);
365
366  // 3. Let relativeStart be ? ToInteger(start).
367  const start: JSAny = arguments[0];
368  const relativeStart: Number = ToInteger_Inline(start);
369
370  // 4. If relativeStart < 0, let actualStart be max((len + relativeStart),
371  // 0);
372  //    else let actualStart be min(relativeStart, len).
373  const actualStart: Number = relativeStart < 0 ?
374      Max((len + relativeStart), 0) :
375      Min(relativeStart, len);
376
377  let insertCount: Smi;
378  let actualDeleteCount: Number;
379  // 5. If the Number of actual arguments is 0, then
380  if (arguments.length == 0) {
381    // a. Let insertCount be 0.
382    insertCount = 0;
383    // b. Let actualDeleteCount be 0.
384    actualDeleteCount = 0;
385    // 6. Else if the Number of actual arguments is 1, then
386  } else if (arguments.length == 1) {
387    // a. Let insertCount be 0.
388    insertCount = 0;
389    // b. Let actualDeleteCount be len - actualStart.
390    actualDeleteCount = len - actualStart;
391    // 7. Else,
392  } else {
393    // a. Let insertCount be the Number of actual arguments minus 2.
394    insertCount = Convert<Smi>(arguments.length) - 2;
395    // b. Let dc be ? ToInteger(deleteCount).
396    const deleteCount: JSAny = arguments[1];
397    const dc: Number = ToInteger_Inline(deleteCount);
398    // c. Let actualDeleteCount be min(max(dc, 0), len - actualStart).
399    actualDeleteCount = Min(Max(dc, 0), len - actualStart);
400  }
401
402  // 8. If len + insertCount - actualDeleteCount > 2^53-1, throw a
403  //    Bailout exception.
404  const newLength: Number = len + insertCount - actualDeleteCount;
405  if (newLength > kMaxSafeInteger) {
406    ThrowTypeError(MessageTemplate::kInvalidArrayLength, start);
407  }
408
409  try {
410    return FastArraySplice(
411        context, arguments, o, len, actualStart, insertCount, actualDeleteCount)
412        otherwise Bailout;
413  } label Bailout {}
414
415  // If the fast case fails, just continue with the slow, correct,
416  // spec-compliant case.
417  return SlowSplice(
418      context, arguments, o, len, actualStart, insertCount, actualDeleteCount);
419}
420}
421