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
5module array {
6  macro FastArraySplice(
7      context: Context, args: Arguments, o: Object,
8      originalLengthNumber: Number, actualStartNumber: Number, insertCount: Smi,
9      actualDeleteCountNumber: Number): Object
10  labels Bailout {
11    let originalLength: Smi = cast<Smi>(originalLengthNumber) otherwise Bailout;
12    let actualStart: Smi = cast<Smi>(actualStartNumber) otherwise Bailout;
13    let actualDeleteCount: Smi =
14        cast<Smi>(actualDeleteCountNumber) otherwise Bailout;
15    let lengthDelta: Smi = insertCount - actualDeleteCount;
16    let newLength: Smi = originalLength + lengthDelta;
17
18    let a: JSArray = cast<JSArray>(o) otherwise Bailout;
19
20    let map: Map = a.map;
21    if (!IsPrototypeInitialArrayPrototype(context, map)) goto Bailout;
22    if (IsNoElementsProtectorCellInvalid()) goto Bailout;
23    if (IsArraySpeciesProtectorCellInvalid()) goto Bailout;
24
25    // Fast path only works on fast elements kind and with writable length.
26    let elementsKind: ElementsKind = EnsureArrayPushable(map) otherwise Bailout;
27    if (!IsFastElementsKind(elementsKind)) goto Bailout;
28
29    // For now, only support non-double fast elements
30    if (!IsFastSmiOrTaggedElementsKind(elementsKind)) goto Bailout;
31
32    if (IsFastSmiElementsKind(elementsKind)) {
33      for (let e: Object of args [2: ]) {
34        if (TaggedIsNotSmi(e)) goto Bailout;
35      }
36    }
37
38    // Make sure that the length hasn't been changed by side-effect.
39    let length: Smi = cast<Smi>(a.length) otherwise Bailout;
40    if (originalLength != length) goto Bailout;
41
42    let deletedResult: JSArray =
43        ExtractFastJSArray(context, a, actualStart, actualDeleteCount);
44
45    if (newLength == 0) {
46      a.elements = kEmptyFixedArray;
47      a.length = 0;
48      return deletedResult;
49    }
50
51    let elements: FixedArray = cast<FixedArray>(a.elements) otherwise Bailout;
52    let elementsMap: Map = elements.map;
53
54    // If the source is a COW array or the spliced array is larger then the
55    // source array, then allocate a new FixedArray to hold the result.
56    let newElements: FixedArray = elements;
57    if ((elementsMap == kCOWMap) || (lengthDelta > 0)) {
58      newElements = ExtractFixedArray(
59          elements, 0, actualStart, newLength, kAllFixedArrays);
60      newElements.map = elementsMap;
61      a.elements = newElements;
62    }
63
64    // Double check that the array is still in fast elements mode
65    assert(IsFastSmiElementsKind(a.map.elements_kind));
66
67    // Copy over inserted elements.
68    let k: Smi = actualStart;
69    if (insertCount > 0) {
70      for (let e: Object of args [2: ]) {
71        newElements[k++] = e;
72      }
73    }
74
75    // Copy over elements after deleted elements.
76    let count: Smi = length - actualStart - actualDeleteCount;
77    while (count > 0) {
78      let e: Object = elements[k - lengthDelta];
79      newElements[k++] = e;
80      count--;
81    }
82
83    // Fill rest of spliced FixedArray with the hole, but only if the
84    // destination FixedArray is the original array's, since otherwise the array
85    // is pre-filled with holes.
86    if (elements == newElements) {
87      let limit: Smi = elements.length;
88      while (k < limit) {
89        newElements[k++] = Hole;
90      }
91    }
92
93    // Update the array's length after all the FixedArray shuffling is done.
94    a.length = newLength;
95
96    return deletedResult;
97  }
98
99  // https://tc39.github.io/ecma262/#sec-array.prototype.splice
100  javascript builtin ArraySpliceTorque(
101      context: Context, receiver: Object, ...arguments): Object {
102    // 1. Let O be ? ToObject(this value).
103    let o: Object = ToObject(context, receiver);
104
105    // 2. Let len be ? ToLength(? Get(O, "length")).
106    let len: Number =
107        ToLength_Inline(context, GetProperty(context, o, 'length'));
108
109    // 3. Let relativeStart be ? ToInteger(start).
110    let start: Object = arguments[0];
111    let relativeStart: Number = ToInteger_Inline(context, start);
112
113    // 4. If relativeStart < 0, let actualStart be max((len + relativeStart),
114    // 0);
115    //    else let actualStart be min(relativeStart, len).
116    let actualStart: Number = relativeStart < 0 ?
117        max((len + relativeStart), 0) :
118        min(relativeStart, len);
119
120    let insertCount: Smi;
121    let actualDeleteCount: Number;
122    // 5. If the Number of actual arguments is 0, then
123    if (arguments.length == 0) {
124      // a. Let insertCount be 0.
125      insertCount = 0;
126      // b. Let actualDeleteCount be 0.
127      actualDeleteCount = 0;
128      // 6. Else if the Number of actual arguments is 1, then
129    } else if (arguments.length == 1) {
130      // a. Let insertCount be 0.
131      insertCount = 0;
132      // b. Let actualDeleteCount be len - actualStart.
133      actualDeleteCount = len - actualStart;
134      // 7. Else,
135    } else {
136      // a. Let insertCount be the Number of actual arguments minus 2.
137      insertCount = convert<Smi>(arguments.length) - 2;
138      // b. Let dc be ? ToInteger(deleteCount).
139      let deleteCount: Object = arguments[1];
140      let dc: Number = ToInteger_Inline(context, deleteCount);
141      // c. Let actualDeleteCount be min(max(dc, 0), len - actualStart).
142      actualDeleteCount = min(max(dc, 0), len - actualStart);
143    }
144
145    // 8. If len + insertCount - actualDeleteCount > 2^53-1, throw a
146    //    Bailout exception.
147    if (len + insertCount - actualDeleteCount > kMaxSafeInteger) {
148      ThrowRangeError(context, kInvalidArrayLengthMessage);
149    }
150
151    try {
152      return FastArraySplice(
153          context, arguments, o, len, actualStart, insertCount,
154          actualDeleteCount) otherwise Bailout;
155    }
156    label Bailout {}
157    // If the fast case fails, just continue with the slow, correct,
158    // spec-compliant case.
159
160    // 9. Let A be ? ArraySpeciesCreate(O, actualDeleteCount).
161    let a: Object = ArraySpeciesCreate(context, o, actualDeleteCount);
162
163    // 10. Let k be 0.
164    let k: Number = 0;
165
166    // 11. Repeat, while k < actualDeleteCount
167    while (k < actualDeleteCount) {
168      // a. Let from be ! ToString(actualStart + k).
169      let from: String = ToString_Inline(context, actualStart + k);
170
171      // b. Let fromPresent be ? HasProperty(O, from).
172      let fromPresent: Oddball =
173          HasPropertyObject(o, from, context, kHasProperty);
174
175      // c. If fromPresent is true, then
176      if (fromPresent == True) {
177        // i. Let fromValue be ? Get(O, from).
178        let fromValue: Object = GetProperty(context, o, from);
179
180        // ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(k), fromValue).
181        CreateDataProperty(context, a, ToString_Inline(context, k), fromValue);
182      }
183
184      // d. Increment k by 1.
185      k = k + 1;
186    }
187
188    // 12. Perform ? Set(A, "length", actualDeleteCount, true).
189    SetProperty(context, a, 'length', actualDeleteCount, strict);
190
191    // 13. Let items be a List whose elements are, in left-to-right order,
192    //     the portion of the actual argument list starting with the third
193    //     argument. The list is empty if fewer than three arguments were
194    //     passed.
195    // 14. Let itemCount be the Number of elements in items.
196    let itemCount: Number = insertCount;
197
198    // 15. If itemCount < actualDeleteCount, then
199    if (itemCount < actualDeleteCount) {
200      // a. Let k be actualStart.
201      let k: Number = actualStart;
202
203      // b. Repeat, while k < (len - actualDeleteCount)
204      while (k < (len - actualDeleteCount)) {
205        // i. Let from be ! ToString(k + actualDeleteCount).
206        let from: String = ToString_Inline(context, k + actualDeleteCount);
207        // ii. Let to be ! ToString(k + itemCount).
208        let to: String = ToString_Inline(context, k + itemCount);
209
210        // iii. Let fromPresent be ? HasProperty(O, from).
211        let fromPresent: Oddball =
212            HasPropertyObject(o, from, context, kHasProperty);
213
214        // iv. If fromPresent is true, then
215        if (fromPresent == True) {
216          // 1. Let fromValue be ? Get(O, from).
217          let fromValue: Object = GetProperty(context, o, from);
218
219          // 2. Perform ? Set(O, to, fromValue, true).
220          SetProperty(context, o, to, fromValue, strict);
221
222          // v. Else fromPresent is false,
223        } else {
224          // 1. Perform ? DeletePropertyOrThrow(O, to).
225          DeleteProperty(context, o, to, strict);
226        }
227        // vi. Increase k by 1.
228        k = k + 1;
229      }
230
231      // c. Let k be len.
232      k = len;
233      // d. Repeat, while k > (len - actualDeleteCount + itemCount)
234      while (k > (len - actualDeleteCount + itemCount)) {
235        // i. Perform ? DeletePropertyOrThrow(O, ! ToString(k - 1)).
236        DeleteProperty(context, o, ToString_Inline(context, k - 1), strict);
237
238        // ii. Decrease k by 1.
239        k = k - 1;
240      }
241      // 16. Else if itemCount > actualDeleteCount, then
242    } else if (itemCount > actualDeleteCount) {
243      // a. Let k be (len - actualDeleteCount).
244      let k: Number = len - actualDeleteCount;
245
246      // b. Repeat, while k > actualStart
247      while (k > actualStart) {
248        // i. Let from be ! ToString(k + actualDeleteCount - 1).
249        let from: String = ToString_Inline(context, k + actualDeleteCount - 1);
250
251        // ii. Let to be ! ToString(k + itemCount - 1).
252        let to: String = ToString_Inline(context, k + itemCount - 1);
253
254        // iii. Let fromPresent be ? HasProperty(O, from).
255        let fromPresent: Oddball =
256            HasPropertyObject(o, from, context, kHasProperty);
257
258        // iv. If fromPresent is true, then
259        if (fromPresent == True) {
260          // 1. Let fromValue be ? Get(O, from).
261          let fromValue: Object = GetProperty(context, o, from);
262
263          // 2. Perform ? Set(O, to, fromValue, true).
264          SetProperty(context, o, to, fromValue, strict);
265
266          // v. Else fromPresent is false,
267        } else {
268          // 1. Perform ? DeletePropertyOrThrow(O, to).
269          DeleteProperty(context, o, to, strict);
270        }
271
272        // vi. Decrease k by 1.
273        k = k - 1;
274      }
275    }
276
277    // 17. Let k be actualStart.
278    k = actualStart;
279
280    // 18. Repeat, while items is not empty
281    //   a. Remove the first element from items and let E be the value of that
282    //   element.
283    if (arguments.length > 2) {
284      for (let e: Object of arguments [2: ]) {
285        // b. Perform ? Set(O, ! ToString(k), E, true).
286        SetProperty(context, o, ToString_Inline(context, k), e, strict);
287
288        // c. Increase k by 1.
289        k = k + 1;
290      }
291    }
292
293    // 19. Perform ? Set(O, "length", len - actualDeleteCount + itemCount,
294    // true).
295    SetProperty(
296        context, o, 'length', len - actualDeleteCount + itemCount, strict);
297
298    return a;
299  }
300
301  macro ArrayForEachTorqueContinuation(
302      context: Context, o: Object, len: Number, callbackfn: Callable,
303      thisArg: Object, initial_k: Number): Object {
304    // 5. Let k be 0.
305    // 6. Repeat, while k < len
306    for (let k: Number = initial_k; k < len; k = k + 1) {
307      // 6a. Let Pk be ! ToString(k).
308      // k is guaranteed to be a positive integer, hence ToString is
309      // side-effect free and HasProperty/GetProperty do the conversion inline.
310
311      // 6b. Let kPresent be ? HasProperty(O, Pk).
312      let kPresent: Oddball = HasPropertyObject(o, k, context, kHasProperty);
313
314      // 6c. If kPresent is true, then
315      if (kPresent == True) {
316        // 6c. i. Let kValue be ? Get(O, Pk).
317        let kValue: Object = GetProperty(context, o, k);
318
319        // 6c. ii. Perform ? Call(callbackfn, T, <kValue, k, O>).
320        Call(context, callbackfn, thisArg, kValue, k, o);
321      }
322
323      // 6d. Increase k by 1. (done by the loop).
324    }
325    return Undefined;
326  }
327
328  javascript builtin ArrayForEachLoopEagerDeoptContinuation(
329      context: Context, receiver: Object, callback: Object, thisArg: Object,
330      initialK: Object, length: Object): Object {
331    return ArrayForEachLoopContinuation(
332        context, receiver, callback, thisArg, Undefined, receiver, initialK,
333        length, Undefined);
334  }
335
336  javascript builtin ArrayForEachLoopLazyDeoptContinuation(
337      context: Context, receiver: Object, callback: Object, thisArg: Object,
338      initialK: Object, length: Object, result: Object): Object {
339    return ArrayForEachLoopContinuation(
340        context, receiver, callback, thisArg, Undefined, receiver, initialK,
341        length, Undefined);
342  }
343
344  builtin ArrayForEachLoopContinuation(
345      context: Context, receiver: Object, callback: Object, thisArg: Object,
346      array: Object, object: Object, initialK: Object, length: Object,
347      to: Object): Object {
348    try {
349      let callbackfn: Callable = cast<Callable>(callback) otherwise Unexpected;
350      let k: Number = cast<Number>(initialK) otherwise Unexpected;
351      let number_length: Number = cast<Number>(length) otherwise Unexpected;
352
353      return ArrayForEachTorqueContinuation(
354          context, object, number_length, callbackfn, thisArg, k);
355    }
356    label Unexpected {
357      unreachable;
358    }
359  }
360
361  macro VisitAllElements<FixedArrayType : type>(
362      context: Context, a: JSArray, len: Smi, callbackfn: Callable,
363      thisArg: Object): void labels
364  Bailout(Smi) {
365    let k: Smi = 0;
366    let map: Map = a.map;
367
368    try {
369      // Build a fast loop over the smi array.
370      for (; k < len; k = k + 1) {
371        // Ensure that the map didn't change.
372        if (map != a.map) goto Slow;
373        // Ensure that we haven't walked beyond a possibly updated length.
374        if (k >= a.length) goto Slow;
375
376        try {
377          let value: Object =
378              LoadElementNoHole<FixedArrayType>(a, k) otherwise FoundHole;
379          Call(context, callbackfn, thisArg, value, k, a);
380        }
381        label FoundHole {
382          // If we found the hole, we need to bail out if the initial
383          // array prototype has had elements inserted. This is preferable
384          // to walking the prototype chain looking for elements.
385
386          if (IsNoElementsProtectorCellInvalid()) goto Bailout(k);
387        }
388      }
389    }
390    label Slow {
391      goto Bailout(k);
392    }
393  }
394
395  macro FastArrayForEach(
396      context: Context, o: Object, len: Number, callbackfn: Callable,
397      thisArg: Object): Object labels
398  Bailout(Smi) {
399    let k: Smi = 0;
400    try {
401      let smi_len: Smi = cast<Smi>(len) otherwise Slow;
402      let a: JSArray = cast<JSArray>(o) otherwise Slow;
403      let map: Map = a.map;
404
405      if (!IsPrototypeInitialArrayPrototype(context, map)) goto Slow;
406      let elementsKind: ElementsKind = map.elements_kind;
407      if (!IsFastElementsKind(elementsKind)) goto Slow;
408
409      if (IsElementsKindGreaterThan(elementsKind, HOLEY_ELEMENTS)) {
410        VisitAllElements<FixedDoubleArray>(
411            context, a, smi_len, callbackfn, thisArg)
412        otherwise Bailout;
413      } else {
414        VisitAllElements<FixedArray>(context, a, smi_len, callbackfn, thisArg)
415        otherwise Bailout;
416      }
417    }
418    label Slow {
419      goto Bailout(k);
420    }
421    return Undefined;
422  }
423
424  // https://tc39.github.io/ecma262/#sec-array.prototype.foreach
425  javascript builtin ArrayForEach(
426      context: Context, receiver: Object, ...arguments): Object {
427    try {
428      if (IsNullOrUndefined(receiver)) {
429        goto NullOrUndefinedError;
430      }
431
432      // 1. Let O be ? ToObject(this value).
433      let o: Object = ToObject(context, receiver);
434
435      // 2. Let len be ? ToLength(? Get(O, "length")).
436      let len: Number =
437          ToLength_Inline(context, GetProperty(context, o, 'length'));
438
439      // 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
440      if (arguments.length == 0) {
441        goto TypeError;
442      }
443      let callbackfn: Callable =
444          cast<Callable>(arguments[0]) otherwise TypeError;
445
446      // 4. If thisArg is present, let T be thisArg; else let T be undefined.
447      let thisArg: Object = arguments.length > 1 ? arguments[1] : Undefined;
448
449      // Special cases.
450      let k: Number = 0;
451      try {
452        return FastArrayForEach(context, o, len, callbackfn, thisArg)
453        otherwise Bailout;
454      }
455      label Bailout(k_value: Smi) {
456        k = k_value;
457      }
458
459      return ArrayForEachTorqueContinuation(
460          context, o, len, callbackfn, thisArg, k);
461    }
462    label TypeError {
463      ThrowTypeError(context, kCalledNonCallable, arguments[0]);
464    }
465    label NullOrUndefinedError {
466      ThrowTypeError(
467          context, kCalledOnNullOrUndefined, 'Array.prototype.forEach');
468    }
469  }
470}
471