array-splice.tq 15.1 KB
Newer Older
1 2 3 4
// 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.

5
namespace array {
6 7 8 9
  // 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.
10 11 12 13 14
  macro Extract(implicit context: Context)(
      elements: FixedArray, first: Smi, count: Smi, capacity: Smi): FixedArray {
    return ExtractFixedArray(
        elements, Convert<intptr>(first), Convert<intptr>(count),
        Convert<intptr>(capacity));
15 16
  }

17 18 19 20 21 22 23 24 25 26 27 28
  macro Extract(implicit context: Context)(
      elements: FixedDoubleArray|EmptyFixedArray, first: Smi, count: Smi,
      capacity: Smi): FixedDoubleArray|EmptyFixedArray {
    typeswitch (elements) {
      case (EmptyFixedArray): {
        return AllocateZeroedFixedDoubleArray(Convert<intptr>(capacity));
      }
      case (elements: FixedDoubleArray): {
        return ExtractFixedDoubleArray(
            elements, Convert<intptr>(first), Convert<intptr>(count),
            Convert<intptr>(capacity));
      }
29 30 31
    }
  }

32
  macro DoMoveElements<FixedArrayType : type extends FixedArrayBase>(
33 34 35 36 37 38 39
      elements: FixedArrayType, dstIndex: Smi, srcIndex: Smi,
      count: Smi): void {
    TorqueMoveElements(
        elements, Convert<intptr>(dstIndex), Convert<intptr>(srcIndex),
        Convert<intptr>(count));
  }

40
  macro StoreHoles<FixedArrayType : type extends FixedArrayBase>(
41 42
      elements: FixedArrayType, holeStartIndex: Smi, holeEndIndex: Smi): void {
    for (let i: Smi = holeStartIndex; i < holeEndIndex; i++) {
43
      array::StoreArrayHole(elements, i);
44 45 46
    }
  }

47
  macro DoCopyElements<FixedArrayType : type extends FixedArrayBase>(
48 49 50 51 52 53 54
      dstElements: FixedArrayType, dstIndex: Smi, srcElements: FixedArrayType,
      srcIndex: Smi, count: Smi): void {
    TorqueCopyElements(
        dstElements, Convert<intptr>(dstIndex), srcElements,
        Convert<intptr>(srcIndex), Convert<intptr>(count));
  }

55 56 57
  macro
  FastSplice<FixedArrayType : type extends FixedArrayBase, ElementType: type>(
      implicit context: Context)(
58
      args: Arguments, a: JSArray, length: Smi, newLength: Smi,
59
      actualStart: Smi, insertCount: Smi, actualDeleteCount: Smi): void {
60
    // Make sure elements are writable.
61
    array::EnsureWriteableFastElements(a);
62

63
    if (insertCount != actualDeleteCount) {
64 65
      const elements =
          UnsafeCast<(FixedArrayType | EmptyFixedArray)>(a.elements);
66 67 68 69 70
      const dstIndex: Smi = actualStart + insertCount;
      const srcIndex: Smi = actualStart + actualDeleteCount;
      const count: Smi = length - actualDeleteCount - actualStart;
      if (insertCount < actualDeleteCount) {
        // Shrink.
71
        DoMoveElements(
72
            UnsafeCast<FixedArrayType>(elements), dstIndex, srcIndex, count);
73
        StoreHoles(UnsafeCast<FixedArrayType>(elements), newLength, length);
74 75 76
      } else if (insertCount > actualDeleteCount) {
        // If the backing store is big enough, then moving elements is enough.
        if (newLength <= elements.length) {
77
          DoMoveElements(
78 79 80
              UnsafeCast<FixedArrayType>(elements), dstIndex, srcIndex, count);
        } else {
          // Grow.
81
          const capacity: Smi = CalculateNewElementsCapacity(newLength);
82 83
          const newElements: FixedArrayType = UnsafeCast<FixedArrayType>(
              Extract(elements, 0, actualStart, capacity));
84 85
          a.elements = newElements;
          if (elements.length > 0) {
86
            DoCopyElements(
87 88 89 90
                newElements, dstIndex, UnsafeCast<FixedArrayType>(elements),
                srcIndex, count);
          }
        }
91 92 93
      }
    }

94
    // Copy arguments.
95 96
    let k: Smi = actualStart;
    if (insertCount > 0) {
97
      const typedNewElements: FixedArrayType =
98
          UnsafeCast<FixedArrayType>(a.elements);
99
      for (let i: intptr = 2; i < args.length; ++i) {
100
        const e: JSAny = args[i];
101 102
        // The argument elements were already validated to be an appropriate
        // {ElementType} to store in {FixedArrayType}.
103
        typedNewElements[k++] = UnsafeCast<ElementType>(e);
104 105 106 107 108 109 110
      }
    }

    // Update the array's length after all the FixedArray shuffling is done.
    a.length = newLength;
  }

111
  transitioning macro FastArraySplice(
112
      context: Context, args: Arguments, o: JSReceiver,
113
      originalLengthNumber: Number, actualStartNumber: Number, insertCount: Smi,
114
      actualDeleteCountNumber: Number): JSAny
115
      labels Bailout {
116
    const originalLength: Smi =
117 118
        Cast<Smi>(originalLengthNumber) otherwise Bailout;
    const actualStart: Smi = Cast<Smi>(actualStartNumber) otherwise Bailout;
119
    const actualDeleteCount: Smi =
120
        Cast<Smi>(actualDeleteCountNumber) otherwise Bailout;
121 122
    const lengthDelta: Smi = insertCount - actualDeleteCount;
    const newLength: Smi = originalLength + lengthDelta;
123

124
    const a: JSArray = Cast<JSArray>(o) otherwise Bailout;
125

126
    const map: Map = a.map;
127
    if (!IsPrototypeInitialArrayPrototype(map)) goto Bailout;
128 129 130 131 132 133 134
    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;

135
    const oldElementsKind: ElementsKind = elementsKind;
136
    for (let i: intptr = 2; i < args.length; ++i) {
137
      const e: JSAny = args[i];
138 139
      if (IsFastSmiElementsKind(elementsKind)) {
        if (TaggedIsNotSmi(e)) {
140
          const heapObject: HeapObject = UnsafeCast<HeapObject>(e);
141 142 143 144 145 146 147 148 149 150 151 152
          elementsKind = IsHeapNumber(heapObject) ?
              AllowDoubleElements(elementsKind) :
              AllowNonNumberElements(elementsKind);
        }
      } else if (IsDoubleElementsKind(elementsKind)) {
        if (!IsNumber(e)) {
          elementsKind = AllowNonNumberElements(elementsKind);
        }
      }
    }

    if (elementsKind != oldElementsKind) {
153 154
      const smiElementsKind: Smi = Convert<Smi>(Convert<int32>(elementsKind));
      TransitionElementsKindWithKind(context, a, smiElementsKind);
155 156 157
    }

    // Make sure that the length hasn't been changed by side-effect.
158
    const length: Smi = Cast<Smi>(a.length) otherwise Bailout;
159 160
    if (originalLength != length) goto Bailout;

161
    const deletedResult: JSArray =
162 163 164 165 166 167 168 169 170
        ExtractFastJSArray(context, a, actualStart, actualDeleteCount);

    if (newLength == 0) {
      a.elements = kEmptyFixedArray;
      a.length = 0;
      return deletedResult;
    }

    if (IsFastSmiOrTaggedElementsKind(elementsKind)) {
171
      FastSplice<FixedArray, JSAny>(
172 173
          args, a, length, newLength, actualStart, insertCount,
          actualDeleteCount);
174 175
    } else {
      FastSplice<FixedDoubleArray, Number>(
176 177
          args, a, length, newLength, actualStart, insertCount,
          actualDeleteCount);
178 179 180 181 182
    }

    return deletedResult;
  }

183
  transitioning macro FillDeletedElementsArray(
184
      context: Context, o: JSReceiver, actualStart: Number,
185
      actualDeleteCount: Number, a: JSReceiver): JSAny {
186 187 188 189 190 191
    // 10. Let k be 0.
    let k: Number = 0;

    // 11. Repeat, while k < actualDeleteCount
    while (k < actualDeleteCount) {
      // a. Let from be ! ToString(actualStart + k).
192
      const from: Number = actualStart + k;
193 194

      // b. Let fromPresent be ? HasProperty(O, from).
195
      const fromPresent: Boolean = HasProperty(o, from);
196 197 198 199

      // c. If fromPresent is true, then
      if (fromPresent == True) {
        // i. Let fromValue be ? Get(O, from).
200
        const fromValue: JSAny = GetProperty(o, from);
201 202

        // ii. Perform ? CreateDataPropertyOrThrow(A, ! ToString(k), fromValue).
203
        FastCreateDataProperty(a, k, fromValue);
204 205 206 207 208 209
      }

      // d. Increment k by 1.
      k++;
    }
    // 12. Perform ? Set(A, "length", actualDeleteCount, true).
210
    SetProperty(a, kLengthString, actualDeleteCount);
211 212 213 214 215
    return a;
  }

  // HandleForwardCase implements step 15. "If itemCount < actualDeleteCount,
  // then...""
216
  transitioning macro HandleForwardCase(
217 218 219 220 221 222 223 224 225
      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).
226
      const from: Number = k + actualDeleteCount;
227
      // ii. Let to be ! ToString(k + itemCount).
228
      const to: Number = k + itemCount;
229 230

      // iii. Let fromPresent be ? HasProperty(O, from).
231
      const fromPresent: Boolean = HasProperty(o, from);
232 233 234 235

      // iv. If fromPresent is true, then
      if (fromPresent == True) {
        // 1. Let fromValue be ? Get(O, from).
236
        const fromValue: JSAny = GetProperty(o, from);
237 238

        // 2. Perform ? Set(O, to, fromValue, true).
239
        SetProperty(o, to, fromValue);
240 241 242 243

        // v. Else fromPresent is false,
      } else {
        // 1. Perform ? DeletePropertyOrThrow(O, to).
244
        DeleteProperty(o, to, LanguageMode::kStrict);
245 246 247 248 249 250 251 252 253 254 255
      }
      // 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)).
256
      DeleteProperty(o, k - 1, LanguageMode::kStrict);
257 258 259 260 261 262 263
      // ii. Decrease k by 1.
      k--;
    }
  }

  // HandleBackwardCase implements step 16. "Else if itemCount >
  // actualDeleteCount, then..."
264
  transitioning macro HandleBackwardCase(
265 266 267 268 269 270 271 272 273
      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).
274
      const from: Number = k + actualDeleteCount - 1;
275 276

      // ii. Let to be ! ToString(k + itemCount - 1).
277
      const to: Number = k + itemCount - 1;
278 279

      // iii. Let fromPresent be ? HasProperty(O, from).
280
      const fromPresent: Boolean = HasProperty(o, from);
281 282 283 284

      // iv. If fromPresent is true, then
      if (fromPresent == True) {
        // 1. Let fromValue be ? Get(O, from).
285
        const fromValue: JSAny = GetProperty(o, from);
286 287

        // 2. Perform ? Set(O, to, fromValue, true).
288
        SetProperty(o, to, fromValue);
289 290 291 292

        // v. Else fromPresent is false,
      } else {
        // 1. Perform ? DeletePropertyOrThrow(O, to).
293
        DeleteProperty(o, to, LanguageMode::kStrict);
294 295 296 297 298 299 300
      }

      // vi. Decrease k by 1.
      k--;
    }
  }

301
  transitioning macro SlowSplice(
302
      context: Context, arguments: Arguments, o: JSReceiver, len: Number,
303
      actualStart: Number, insertCount: Smi, actualDeleteCount: Number): JSAny {
304
    // 9. Let A be ? ArraySpeciesCreate(O, actualDeleteCount).
305 306
    const a: JSReceiver = ArraySpeciesCreate(context, o, actualDeleteCount);
    const itemCount: Number = insertCount;
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334

    // 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) {
335
      for (let i: intptr = 2; i < arguments.length; ++i) {
336
        const e: JSAny = arguments[i];
337
        // b. Perform ? Set(O, ! ToString(k), E, true).
338
        SetProperty(o, k, e);
339 340 341 342 343 344 345 346

        // c. Increase k by 1.
        k = k + 1;
      }
    }

    // 19. Perform ? Set(O, "length", len - actualDeleteCount + itemCount,
    // true).
347
    SetProperty(o, kLengthString, len - actualDeleteCount + itemCount);
348 349 350 351 352

    return a;
  }

  // https://tc39.github.io/ecma262/#sec-array.prototype.splice
353
  transitioning javascript builtin
354
  ArrayPrototypeSplice(js-implicit context: NativeContext, receiver: JSAny)(
355
      ...arguments): JSAny {
356
    // 1. Let O be ? ToObject(this value).
357
    const o: JSReceiver = ToObject(context, receiver);
358 359

    // 2. Let len be ? ToLength(? Get(O, "length")).
360
    const len: Number = GetLengthProperty(o);
361 362

    // 3. Let relativeStart be ? ToInteger(start).
363
    const start: JSAny = arguments[0];
364
    const relativeStart: Number = ToInteger_Inline(start);
365 366 367 368

    // 4. If relativeStart < 0, let actualStart be max((len + relativeStart),
    // 0);
    //    else let actualStart be min(relativeStart, len).
369
    const actualStart: Number = relativeStart < 0 ?
370 371
        Max((len + relativeStart), 0) :
        Min(relativeStart, len);
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389

    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.
390
      insertCount = Convert<Smi>(arguments.length) - 2;
391
      // b. Let dc be ? ToInteger(deleteCount).
392
      const deleteCount: JSAny = arguments[1];
393
      const dc: Number = ToInteger_Inline(deleteCount);
394
      // c. Let actualDeleteCount be min(max(dc, 0), len - actualStart).
395
      actualDeleteCount = Min(Max(dc, 0), len - actualStart);
396 397 398 399
    }

    // 8. If len + insertCount - actualDeleteCount > 2^53-1, throw a
    //    Bailout exception.
400 401
    const newLength: Number = len + insertCount - actualDeleteCount;
    if (newLength > kMaxSafeInteger) {
402
      ThrowTypeError(MessageTemplate::kInvalidArrayLength, start);
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418
    }

    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);
  }
}