array.js 44.6 KB
Newer Older
1
// Copyright 2012 the V8 project authors. All rights reserved.
2 3
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
4

5 6
"use strict";

7 8
// This file relies on the fact that the following declarations have been made
// in runtime.js:
9
// var $Array = global.Array;
10 11 12 13 14

// -------------------------------------------------------------------

// Global list of arrays visited during toString, toLocaleString and
// join invocations.
15
var visited_arrays = new InternalArray();
16 17 18 19


// Gets a sorted array of array keys.  Useful for operations on sparse
// arrays.  Dupes have not been removed.
20 21 22 23 24 25 26 27 28
function GetSortedArrayKeys(array, indices) {
  var keys = new InternalArray();
  if (IS_NUMBER(indices)) {
    // It's an interval
    var limit = indices;
    for (var i = 0; i < limit; ++i) {
      var e = array[i];
      if (!IS_UNDEFINED(e) || i in array) {
        keys.push(i);
29
      }
30 31 32 33 34
    }
  } else {
    var length = indices.length;
    for (var k = 0; k < length; ++k) {
      var key = indices[k];
35 36 37 38 39 40 41
      if (!IS_UNDEFINED(key)) {
        var e = array[key];
        if (!IS_UNDEFINED(e) || key in array) {
          keys.push(key);
        }
      }
    }
42
    %_CallFunction(keys, function(a, b) { return a - b; }, ArraySort);
43 44 45 46 47
  }
  return keys;
}


48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
function SparseJoinWithSeparator(array, len, convert, separator) {
  var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
  var totalLength = 0;
  var elements = new InternalArray(keys.length * 2);
  var previousKey = -1;
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    if (key != previousKey) {  // keys may contain duplicates.
      var e = array[key];
      if (!IS_STRING(e)) e = convert(e);
      elements[i * 2] = key;
      elements[i * 2 + 1] = e;
      previousKey = key;
    }
  }
  return %SparseJoinWithSeparator(elements, len, separator);
}


67 68 69 70 71
// Optimized for sparse arrays if separator is ''.
function SparseJoin(array, len, convert) {
  var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
  var last_key = -1;
  var keys_length = keys.length;
72

73
  var elements = new InternalArray(keys_length);
74 75
  var elements_length = 0;

76 77 78 79
  for (var i = 0; i < keys_length; i++) {
    var key = keys[i];
    if (key != last_key) {
      var e = array[key];
80 81
      if (!IS_STRING(e)) e = convert(e);
      elements[elements_length++] = e;
82 83 84
      last_key = key;
    }
  }
85
  return %StringBuilderConcat(elements, elements_length, '');
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
}


function UseSparseVariant(object, length, is_array) {
   return is_array &&
       length > 1000 &&
       (!%_IsSmi(length) ||
        %EstimateNumberOfElements(object) < (length >> 2));
}


function Join(array, length, separator, convert) {
  if (length == 0) return '';

  var is_array = IS_ARRAY(array);

  if (is_array) {
    // If the array is cyclic, return the empty string for already
    // visited arrays.
105
    if (!%PushIfAbsent(visited_arrays, array)) return '';
106 107 108 109
  }

  // Attempt to convert the elements.
  try {
110 111 112 113 114 115
    if (UseSparseVariant(array, length, is_array)) {
      if (separator.length == 0) {
        return SparseJoin(array, length, convert);
      } else {
        return SparseJoinWithSeparator(array, length, convert, separator);
      }
116 117
    }

118 119 120
    // Fast case for one-element arrays.
    if (length == 1) {
      var e = array[0];
121 122
      if (IS_STRING(e)) return e;
      return convert(e);
123 124
    }

125
    // Construct an array for the elements.
126
    var elements = new InternalArray(length);
127

128 129
    // We pull the empty separator check outside the loop for speed!
    if (separator.length == 0) {
130
      var elements_length = 0;
131 132
      for (var i = 0; i < length; i++) {
        var e = array[i];
133 134
        if (!IS_STRING(e)) e = convert(e);
        elements[elements_length++] = e;
135
      }
136 137 138 139 140
      elements.length = elements_length;
      var result = %_FastAsciiArrayJoin(elements, '');
      if (!IS_UNDEFINED(result)) return result;
      return %StringBuilderConcat(elements, elements_length, '');
    }
141
    // Non-empty separator case.
142
    // If the first element is a number then use the heuristic that the
143 144 145 146
    // remaining elements are also likely to be numbers.
    if (!IS_NUMBER(array[0])) {
      for (var i = 0; i < length; i++) {
        var e = array[i];
147 148
        if (!IS_STRING(e)) e = convert(e);
        elements[i] = e;
149
      }
150
    } else {
151 152
      for (var i = 0; i < length; i++) {
        var e = array[i];
153 154 155 156 157 158
        if (IS_NUMBER(e)) {
          e = %_NumberToString(e);
        } else if (!IS_STRING(e)) {
          e = convert(e);
        }
        elements[i] = e;
159
      }
160
    }
161
    var result = %_FastAsciiArrayJoin(elements, separator);
162
    if (!IS_UNDEFINED(result)) return result;
163

164
    return %StringBuilderJoin(elements, length, separator);
165
  } finally {
166 167 168
    // Make sure to remove the last element of the visited array no
    // matter what happens.
    if (is_array) visited_arrays.length = visited_arrays.length - 1;
169
  }
170
}
171 172


173
function ConvertToString(x) {
174
  // Assumes x is a non-string.
175 176 177
  if (IS_NUMBER(x)) return %_NumberToString(x);
  if (IS_BOOLEAN(x)) return x ? 'true' : 'false';
  return (IS_NULL_OR_UNDEFINED(x)) ? '' : %ToString(%DefaultString(x));
178
}
179 180 181


function ConvertToLocaleString(e) {
182
  if (IS_NULL_OR_UNDEFINED(e)) {
183 184
    return '';
  } else {
185
    // According to ES5, section 15.4.4.3, the toLocaleString conversion
186 187
    // must throw a TypeError if ToObject(e).toLocaleString isn't
    // callable.
188
    var e_obj = ToObject(e);
189
    return %ToString(e_obj.toLocaleString());
190
  }
191
}
192 193 194 195 196 197


// This function implements the optimized splice implementation that can use
// special array operations to handle sparse arrays in a sensible fashion.
function SmartSlice(array, start_i, del_count, len, deleted_elements) {
  // Move deleted elements to a new array (the return value from splice).
198 199 200 201 202 203 204
  var indices = %GetArrayKeys(array, start_i + del_count);
  if (IS_NUMBER(indices)) {
    var limit = indices;
    for (var i = start_i; i < limit; ++i) {
      var current = array[i];
      if (!IS_UNDEFINED(current) || i in array) {
        deleted_elements[i - start_i] = current;
205
      }
206 207 208 209 210
    }
  } else {
    var length = indices.length;
    for (var k = 0; k < length; ++k) {
      var key = indices[k];
211 212 213 214 215 216 217 218 219 220
      if (!IS_UNDEFINED(key)) {
        if (key >= start_i) {
          var current = array[key];
          if (!IS_UNDEFINED(current) || key in array) {
            deleted_elements[key - start_i] = current;
          }
        }
      }
    }
  }
221
}
222 223 224 225 226 227


// This function implements the optimized splice implementation that can use
// special array operations to handle sparse arrays in a sensible fashion.
function SmartMove(array, start_i, del_count, len, num_additional_args) {
  // Move data to new array.
228
  var new_array = new InternalArray(len - del_count + num_additional_args);
229 230 231 232 233 234 235
  var indices = %GetArrayKeys(array, len);
  if (IS_NUMBER(indices)) {
    var limit = indices;
    for (var i = 0; i < start_i && i < limit; ++i) {
      var current = array[i];
      if (!IS_UNDEFINED(current) || i in array) {
        new_array[i] = current;
236
      }
237 238 239 240 241
    }
    for (var i = start_i + del_count; i < limit; ++i) {
      var current = array[i];
      if (!IS_UNDEFINED(current) || i in array) {
        new_array[i - del_count + num_additional_args] = current;
242
      }
243 244 245 246 247
    }
  } else {
    var length = indices.length;
    for (var k = 0; k < length; ++k) {
      var key = indices[k];
248 249 250
      if (!IS_UNDEFINED(key)) {
        if (key < start_i) {
          var current = array[key];
251
          if (!IS_UNDEFINED(current) || key in array) {
252
            new_array[key] = current;
253
          }
254 255
        } else if (key >= start_i + del_count) {
          var current = array[key];
256
          if (!IS_UNDEFINED(current) || key in array) {
257
            new_array[key - del_count + num_additional_args] = current;
258
          }
259 260 261 262 263 264
        }
      }
    }
  }
  // Move contents of new_array into this array
  %MoveArrayContents(new_array, array);
265
}
266 267 268 269 270 271 272 273 274 275 276 277


// This is part of the old simple-minded splice.  We are using it either
// because the receiver is not an array (so we have no choice) or because we
// know we are not deleting or moving a lot of elements.
function SimpleSlice(array, start_i, del_count, len, deleted_elements) {
  for (var i = 0; i < del_count; i++) {
    var index = start_i + i;
    // The spec could also be interpreted such that %HasLocalProperty
    // would be the appropriate test.  We follow KJS in consulting the
    // prototype.
    var current = array[index];
278
    if (!IS_UNDEFINED(current) || index in array) {
279
      deleted_elements[i] = current;
280
    }
281
  }
282
}
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321


function SimpleMove(array, start_i, del_count, len, num_additional_args) {
  if (num_additional_args !== del_count) {
    // Move the existing elements after the elements to be deleted
    // to the right position in the resulting array.
    if (num_additional_args > del_count) {
      for (var i = len - del_count; i > start_i; i--) {
        var from_index = i + del_count - 1;
        var to_index = i + num_additional_args - 1;
        // The spec could also be interpreted such that
        // %HasLocalProperty would be the appropriate test.  We follow
        // KJS in consulting the prototype.
        var current = array[from_index];
        if (!IS_UNDEFINED(current) || from_index in array) {
          array[to_index] = current;
        } else {
          delete array[to_index];
        }
      }
    } else {
      for (var i = start_i; i < len - del_count; i++) {
        var from_index = i + del_count;
        var to_index = i + num_additional_args;
        // The spec could also be interpreted such that
        // %HasLocalProperty would be the appropriate test.  We follow
        // KJS in consulting the prototype.
        var current = array[from_index];
        if (!IS_UNDEFINED(current) || from_index in array) {
          array[to_index] = current;
        } else {
          delete array[to_index];
        }
      }
      for (var i = len; i > len - del_count + num_additional_args; i--) {
        delete array[i - 1];
      }
    }
  }
322
}
323 324 325 326 327 328


// -------------------------------------------------------------------


function ArrayToString() {
329 330 331 332 333 334 335 336 337 338 339
  var array;
  var func;
  if (IS_ARRAY(this)) {
    func = this.join;
    if (func === ArrayJoin) {
      return Join(this, this.length, ',', ConvertToString);
    }
    array = this;
  } else {
    array = ToObject(this);
    func = array.join;
340
  }
341 342 343 344
  if (!IS_SPEC_FUNCTION(func)) {
    return %_CallFunction(array, ObjectToString);
  }
  return %_CallFunction(array, func);
345
}
346 347 348


function ArrayToLocaleString() {
349 350 351 352 353
  var array = ToObject(this);
  var arrayLen = array.length;
  var len = TO_UINT32(arrayLen);
  if (len === 0) return "";
  return Join(array, len, ',', ConvertToLocaleString);
354
}
355 356 357


function ArrayJoin(separator) {
358
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.join");
359

360 361
  var array = TO_OBJECT_INLINE(this);
  var length = TO_UINT32(array.length);
362 363 364
  if (IS_UNDEFINED(separator)) {
    separator = ',';
  } else if (!IS_STRING(separator)) {
365
    separator = NonStringToString(separator);
366
  }
367

368
  var result = %_FastAsciiArrayJoin(array, separator);
369
  if (!IS_UNDEFINED(result)) return result;
370

371
  return Join(array, length, separator, ConvertToString);
372
}
373 374


375 376 377 378 379 380 381 382 383 384
function ObservedArrayPop(n) {
  n--;
  var value = this[n];

  try {
    BeginPerformSplice(this);
    delete this[n];
    this.length = n;
  } finally {
    EndPerformSplice(this);
385
    EnqueueSpliceRecord(this, n, [value], 0);
386 387 388 389 390
  }

  return value;
}

391 392 393
// Removes the last element from the array and returns it. See
// ECMA-262, section 15.4.4.6.
function ArrayPop() {
394
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.pop");
395

396 397
  var array = TO_OBJECT_INLINE(this);
  var n = TO_UINT32(array.length);
398
  if (n == 0) {
399
    array.length = n;
400 401
    return;
  }
402

403 404
  if (%IsObserved(array))
    return ObservedArrayPop.call(array, n);
405

406
  n--;
407 408 409
  var value = array[n];
  Delete(array, ToName(n), true);
  array.length = n;
410
  return value;
411
}
412 413


414 415 416 417 418 419 420 421 422
function ObservedArrayPush() {
  var n = TO_UINT32(this.length);
  var m = %_ArgumentsLength();

  try {
    BeginPerformSplice(this);
    for (var i = 0; i < m; i++) {
      this[i+n] = %_Arguments(i);
    }
423 424
    var new_length = n + m;
    this.length = new_length;
425 426
  } finally {
    EndPerformSplice(this);
427
    EnqueueSpliceRecord(this, n, [], m);
428 429
  }

430
  return new_length;
431 432
}

433 434 435
// Appends the arguments to the end of the array and returns the new
// length of the array. See ECMA-262, section 15.4.4.7.
function ArrayPush() {
436
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.push");
437

438 439 440
  if (%IsObserved(this))
    return ObservedArrayPush.apply(this, arguments);

441 442 443 444
  var array = TO_OBJECT_INLINE(this);
  var n = TO_UINT32(array.length);
  var m = %_ArgumentsLength();

445
  for (var i = 0; i < m; i++) {
446 447
    // Use SetProperty rather than a direct keyed store to ensure that the store
    // site doesn't become poisened with an elements transition KeyedStoreIC.
448
    %SetProperty(array, i+n, %_Arguments(i), 0, kStrictMode);
449
  }
450 451

  var new_length = n + m;
452
  array.length = new_length;
453
  return new_length;
454
}
455 456


457 458 459
// Returns an array containing the array elements of the object followed
// by the array elements of each argument in order. See ECMA-262,
// section 15.4.4.7.
460
function ArrayConcat(arg1) {  // length == 1
461
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.concat");
462

463
  var array = ToObject(this);
464
  var arg_count = %_ArgumentsLength();
465
  var arrays = new InternalArray(1 + arg_count);
466
  arrays[0] = array;
467 468
  for (var i = 0; i < arg_count; i++) {
    arrays[i + 1] = %_Arguments(i);
469 470
  }

471
  return %ArrayConcat(arrays);
472
}
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488


// For implementing reverse() on large, sparse arrays.
function SparseReverse(array, len) {
  var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
  var high_counter = keys.length - 1;
  var low_counter = 0;
  while (low_counter <= high_counter) {
    var i = keys[low_counter];
    var j = keys[high_counter];

    var j_complement = len - j - 1;
    var low, high;

    if (j_complement <= i) {
      high = j;
489
      while (keys[--high_counter] == j) { }
490 491 492 493
      low = j_complement;
    }
    if (j_complement >= i) {
      low = i;
494
      while (keys[++low_counter] == i) { }
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519
      high = len - i - 1;
    }

    var current_i = array[low];
    if (!IS_UNDEFINED(current_i) || low in array) {
      var current_j = array[high];
      if (!IS_UNDEFINED(current_j) || high in array) {
        array[low] = current_j;
        array[high] = current_i;
      } else {
        array[high] = current_i;
        delete array[low];
      }
    } else {
      var current_j = array[high];
      if (!IS_UNDEFINED(current_j) || high in array) {
        array[low] = current_j;
        delete array[high];
      }
    }
  }
}


function ArrayReverse() {
520
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reverse");
521

522 523
  var array = TO_OBJECT_INLINE(this);
  var j = TO_UINT32(array.length) - 1;
524

525 526 527
  if (UseSparseVariant(array, j, IS_ARRAY(array))) {
    SparseReverse(array, j+1);
    return array;
528 529 530
  }

  for (var i = 0; i < j; i++, j--) {
531 532 533 534 535 536
    var current_i = array[i];
    if (!IS_UNDEFINED(current_i) || i in array) {
      var current_j = array[j];
      if (!IS_UNDEFINED(current_j) || j in array) {
        array[i] = current_j;
        array[j] = current_i;
537
      } else {
538 539
        array[j] = current_i;
        delete array[i];
540 541
      }
    } else {
542 543 544 545
      var current_j = array[j];
      if (!IS_UNDEFINED(current_j) || j in array) {
        array[i] = current_j;
        delete array[j];
546 547 548
      }
    }
  }
549
  return array;
550
}
551 552


553 554 555 556 557 558 559 560 561
function ObservedArrayShift(len) {
  var first = this[0];

  try {
    BeginPerformSplice(this);
    SimpleMove(this, 0, 1, len, 0);
    this.length = len - 1;
  } finally {
    EndPerformSplice(this);
562
    EnqueueSpliceRecord(this, 0, [first], 0);
563 564 565 566 567
  }

  return first;
}

568
function ArrayShift() {
569
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.shift");
570

571 572
  var array = TO_OBJECT_INLINE(this);
  var len = TO_UINT32(array.length);
573

574
  if (len === 0) {
575
    array.length = 0;
576 577
    return;
  }
578

579
  if (ObjectIsSealed(array)) {
580 581 582 583
    throw MakeTypeError("array_functions_change_sealed",
                        ["Array.prototype.shift"]);
  }

584 585
  if (%IsObserved(array))
    return ObservedArrayShift.call(array, len);
586

587
  var first = array[0];
588

589 590
  if (IS_ARRAY(array)) {
    SmartMove(array, 0, 1, len, 0);
591
  } else {
592
    SimpleMove(array, 0, 1, len, 0);
593
  }
594

595
  array.length = len - 1;
596

597
  return first;
598
}
599

600 601 602 603 604 605 606 607 608 609
function ObservedArrayUnshift() {
  var len = TO_UINT32(this.length);
  var num_arguments = %_ArgumentsLength();

  try {
    BeginPerformSplice(this);
    SimpleMove(this, 0, 0, len, num_arguments);
    for (var i = 0; i < num_arguments; i++) {
      this[i] = %_Arguments(i);
    }
610 611
    var new_length = len + num_arguments;
    this.length = new_length;
612 613
  } finally {
    EndPerformSplice(this);
614
    EnqueueSpliceRecord(this, 0, [], num_arguments);
615 616
  }

617
  return new_length;
618
}
619 620

function ArrayUnshift(arg1) {  // length == 1
621
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.unshift");
622

623 624 625
  if (%IsObserved(this))
    return ObservedArrayUnshift.apply(this, arguments);

626 627 628 629 630 631 632
  var array = TO_OBJECT_INLINE(this);
  var len = TO_UINT32(array.length);
  var num_arguments = %_ArgumentsLength();
  var is_sealed = ObjectIsSealed(array);

  if (IS_ARRAY(array) && !is_sealed) {
    SmartMove(array, 0, 0, len, num_arguments);
633
  } else {
634
    SimpleMove(array, 0, 0, len, num_arguments);
635
  }
636

637
  for (var i = 0; i < num_arguments; i++) {
638
    array[i] = %_Arguments(i);
639
  }
640

641
  var new_length = len + num_arguments;
642
  array.length = new_length;
643
  return new_length;
644
}
645 646 647


function ArraySlice(start, end) {
648
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.slice");
649

650 651
  var array = TO_OBJECT_INLINE(this);
  var len = TO_UINT32(array.length);
652 653
  var start_i = TO_INTEGER(start);
  var end_i = len;
654

655
  if (!IS_UNDEFINED(end)) end_i = TO_INTEGER(end);
656

657 658 659 660 661 662
  if (start_i < 0) {
    start_i += len;
    if (start_i < 0) start_i = 0;
  } else {
    if (start_i > len) start_i = len;
  }
663

664 665 666 667 668 669
  if (end_i < 0) {
    end_i += len;
    if (end_i < 0) end_i = 0;
  } else {
    if (end_i > len) end_i = len;
  }
670

671
  var result = [];
672 673 674

  if (end_i < start_i) return result;

675 676
  if (IS_ARRAY(array) &&
      !%IsObserved(array) &&
677
      (end_i > 1000) &&
678 679
      (%EstimateNumberOfElements(array) < end_i)) {
    SmartSlice(array, start_i, end_i - start_i, len, result);
680
  } else {
681
    SimpleSlice(array, start_i, end_i - start_i, len, result);
682 683
  }

684
  result.length = end_i - start_i;
685

686
  return result;
687
}
688 689


690
function ComputeSpliceStartIndex(start_i, len) {
691 692
  if (start_i < 0) {
    start_i += len;
693
    return start_i < 0 ? 0 : start_i;
694
  }
695

696 697 698 699 700
  return start_i > len ? len : start_i;
}


function ComputeSpliceDeleteCount(delete_count, num_arguments, len, start_i) {
701
  // SpiderMonkey, TraceMonkey and JSC treat the case where no delete count is
702 703
  // given as a request to delete all the elements from the start.
  // And it differs from the case of undefined delete count.
704 705 706
  // This does not follow ECMA-262, but we do the same for
  // compatibility.
  var del_count = 0;
707 708 709 710 711 712 713 714 715 716 717 718
  if (num_arguments == 1)
    return len - start_i;

  del_count = TO_INTEGER(delete_count);
  if (del_count < 0)
    return 0;

  if (del_count > len - start_i)
    return len - start_i;

  return del_count;
}
719

720 721 722 723 724 725 726

function ObservedArraySplice(start, delete_count) {
  var num_arguments = %_ArgumentsLength();
  var len = TO_UINT32(this.length);
  var start_i = ComputeSpliceStartIndex(TO_INTEGER(start), len);
  var del_count = ComputeSpliceDeleteCount(delete_count, num_arguments, len,
                                           start_i);
727 728
  var deleted_elements = [];
  deleted_elements.length = del_count;
729 730 731 732
  var num_elements_to_add = num_arguments > 2 ? num_arguments - 2 : 0;

  try {
    BeginPerformSplice(this);
733

734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
    SimpleSlice(this, start_i, del_count, len, deleted_elements);
    SimpleMove(this, start_i, del_count, len, num_elements_to_add);

    // Insert the arguments into the resulting array in
    // place of the deleted elements.
    var i = start_i;
    var arguments_index = 2;
    var arguments_length = %_ArgumentsLength();
    while (arguments_index < arguments_length) {
      this[i++] = %_Arguments(arguments_index++);
    }
    this.length = len - del_count + num_elements_to_add;

  } finally {
    EndPerformSplice(this);
    if (deleted_elements.length || num_elements_to_add) {
       EnqueueSpliceRecord(this,
                           start_i,
                           deleted_elements.slice(),
                           num_elements_to_add);
    }
755
  }
756

757 758 759 760 761 762
  // Return the deleted elements.
  return deleted_elements;
}


function ArraySplice(start, delete_count) {
763
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.splice");
764

765 766 767 768
  if (%IsObserved(this))
    return ObservedArraySplice.apply(this, arguments);

  var num_arguments = %_ArgumentsLength();
769 770
  var array = TO_OBJECT_INLINE(this);
  var len = TO_UINT32(array.length);
771 772 773 774 775 776 777
  var start_i = ComputeSpliceStartIndex(TO_INTEGER(start), len);
  var del_count = ComputeSpliceDeleteCount(delete_count, num_arguments, len,
                                           start_i);
  var deleted_elements = [];
  deleted_elements.length = del_count;
  var num_elements_to_add = num_arguments > 2 ? num_arguments - 2 : 0;

778
  if (del_count != num_elements_to_add && ObjectIsSealed(array)) {
779 780
    throw MakeTypeError("array_functions_change_sealed",
                        ["Array.prototype.splice"]);
781
  } else if (del_count > 0 && ObjectIsFrozen(array)) {
782 783 784 785
    throw MakeTypeError("array_functions_on_frozen",
                        ["Array.prototype.splice"]);
  }

786
  var use_simple_splice = true;
787
  if (IS_ARRAY(array) &&
788
      num_elements_to_add !== del_count) {
789 790 791
    // If we are only deleting/moving a few things near the end of the
    // array then the simple version is going to be faster, because it
    // doesn't touch most of the array.
792
    var estimated_non_hole_elements = %EstimateNumberOfElements(array);
793 794 795 796
    if (len > 20 && (estimated_non_hole_elements >> 2) < (len - start_i)) {
      use_simple_splice = false;
    }
  }
797

798
  if (use_simple_splice) {
799 800
    SimpleSlice(array, start_i, del_count, len, deleted_elements);
    SimpleMove(array, start_i, del_count, len, num_elements_to_add);
801
  } else {
802 803
    SmartSlice(array, start_i, del_count, len, deleted_elements);
    SmartMove(array, start_i, del_count, len, num_elements_to_add);
804
  }
805

806 807 808 809 810 811
  // Insert the arguments into the resulting array in
  // place of the deleted elements.
  var i = start_i;
  var arguments_index = 2;
  var arguments_length = %_ArgumentsLength();
  while (arguments_index < arguments_length) {
812
    array[i++] = %_Arguments(arguments_index++);
813
  }
814
  array.length = len - del_count + num_elements_to_add;
815

816 817
  // Return the deleted elements.
  return deleted_elements;
818
}
819 820 821


function ArraySort(comparefn) {
822
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.sort");
823

olehougaard's avatar
olehougaard committed
824
  // In-place QuickSort algorithm.
olehougaard's avatar
olehougaard committed
825
  // For short (length <= 22) arrays, insertion sort is used for efficiency.
826

827
  if (!IS_SPEC_FUNCTION(comparefn)) {
828 829 830 831
    comparefn = function (x, y) {
      if (x === y) return 0;
      if (%_IsSmi(x) && %_IsSmi(y)) {
        return %SmiLexicographicCompare(x, y);
832
      }
833 834 835 836 837
      x = ToString(x);
      y = ToString(y);
      if (x == y) return 0;
      else return x < y ? -1 : 1;
    };
838
  }
839
  var receiver = %GetDefaultReceiver(comparefn);
840

841
  var InsertionSort = function InsertionSort(a, from, to) {
olehougaard's avatar
olehougaard committed
842 843
    for (var i = from + 1; i < to; i++) {
      var element = a[i];
844 845
      for (var j = i - 1; j >= from; j--) {
        var tmp = a[j];
846
        var order = %_CallFunction(receiver, tmp, element, comparefn);
847 848
        if (order > 0) {
          a[j + 1] = tmp;
olehougaard's avatar
olehougaard committed
849
        } else {
850
          break;
olehougaard's avatar
olehougaard committed
851 852
        }
      }
853
      a[j + 1] = element;
olehougaard's avatar
olehougaard committed
854
    }
855
  };
856

857 858 859 860 861 862 863 864 865 866 867 868 869
  var GetThirdIndex = function(a, from, to) {
    var t_array = [];
    // Use both 'from' and 'to' to determine the pivot candidates.
    var increment = 200 + ((to - from) & 15);
    for (var i = from + 1; i < to - 1; i += increment) {
      t_array.push([i, a[i]]);
    }
    t_array.sort(function(a, b) {
        return %_CallFunction(receiver, a[1], b[1], comparefn) } );
    var third_index = t_array[t_array.length >> 1][0];
    return third_index;
  }

870
  var QuickSort = function QuickSort(a, from, to) {
871
    var third_index = 0;
872 873 874 875 876
    while (true) {
      // Insertion sort is faster for short arrays.
      if (to - from <= 10) {
        InsertionSort(a, from, to);
        return;
877
      }
878 879 880 881 882
      if (to - from > 1000) {
        third_index = GetThirdIndex(a, from, to);
      } else {
        third_index = from + ((to - from) >> 1);
      }
883 884 885
      // Find a pivot as the median of first, last and middle element.
      var v0 = a[from];
      var v1 = a[to - 1];
886
      var v2 = a[third_index];
887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916
      var c01 = %_CallFunction(receiver, v0, v1, comparefn);
      if (c01 > 0) {
        // v1 < v0, so swap them.
        var tmp = v0;
        v0 = v1;
        v1 = tmp;
      } // v0 <= v1.
      var c02 = %_CallFunction(receiver, v0, v2, comparefn);
      if (c02 >= 0) {
        // v2 <= v0 <= v1.
        var tmp = v0;
        v0 = v2;
        v2 = v1;
        v1 = tmp;
      } else {
        // v0 <= v1 && v0 < v2
        var c12 = %_CallFunction(receiver, v1, v2, comparefn);
        if (c12 > 0) {
          // v0 <= v2 < v1
          var tmp = v1;
          v1 = v2;
          v2 = tmp;
        }
      }
      // v0 <= v1 <= v2
      a[from] = v0;
      a[to - 1] = v2;
      var pivot = v1;
      var low_end = from + 1;   // Upper bound of elements lower than pivot.
      var high_start = to - 1;  // Lower bound of elements greater than pivot.
917
      a[third_index] = a[low_end];
918 919 920 921 922 923 924
      a[low_end] = pivot;

      // From low_end to i are elements equal to pivot.
      // From i to high_start are elements that haven't been compared yet.
      partition: for (var i = low_end + 1; i < high_start; i++) {
        var element = a[i];
        var order = %_CallFunction(receiver, element, pivot, comparefn);
925
        if (order < 0) {
926 927
          a[i] = a[low_end];
          a[low_end] = element;
928
          low_end++;
929 930 931 932 933 934 935 936 937 938 939 940 941 942 943
        } else if (order > 0) {
          do {
            high_start--;
            if (high_start == i) break partition;
            var top_elem = a[high_start];
            order = %_CallFunction(receiver, top_elem, pivot, comparefn);
          } while (order > 0);
          a[i] = a[high_start];
          a[high_start] = element;
          if (order < 0) {
            element = a[i];
            a[i] = a[low_end];
            a[low_end] = element;
            low_end++;
          }
944
        }
945
      }
946 947 948 949 950 951 952
      if (to - high_start < low_end - from) {
        QuickSort(a, high_start, to);
        to = low_end;
      } else {
        QuickSort(a, from, low_end);
        from = high_start;
      }
953
    }
954
  };
955

956 957
  // Copy elements in the range 0..length from obj's prototype chain
  // to obj itself, if obj has holes. Return one more than the maximal index
958
  // of a prototype property.
959
  var CopyFromPrototype = function CopyFromPrototype(obj, length) {
960
    var max = 0;
961
    for (var proto = %GetPrototype(obj); proto; proto = %GetPrototype(proto)) {
962
      var indices = %GetArrayKeys(proto, length);
963 964 965 966 967 968 969
      if (IS_NUMBER(indices)) {
        // It's an interval.
        var proto_length = indices;
        for (var i = 0; i < proto_length; i++) {
          if (!obj.hasOwnProperty(i) && proto.hasOwnProperty(i)) {
            obj[i] = proto[i];
            if (i >= max) { max = i + 1; }
970
          }
971 972 973 974 975 976 977 978
        }
      } else {
        for (var i = 0; i < indices.length; i++) {
          var index = indices[i];
          if (!IS_UNDEFINED(index) &&
              !obj.hasOwnProperty(index) && proto.hasOwnProperty(index)) {
            obj[index] = proto[index];
            if (index >= max) { max = index + 1; }
979 980 981 982 983
          }
        }
      }
    }
    return max;
984
  };
985 986 987 988

  // Set a value of "undefined" on all indices in the range from..to
  // where a prototype of obj has an element. I.e., shadow all prototype
  // elements in that range.
989
  var ShadowPrototypeElements = function(obj, from, to) {
990
    for (var proto = %GetPrototype(obj); proto; proto = %GetPrototype(proto)) {
991
      var indices = %GetArrayKeys(proto, to);
992 993 994 995 996
      if (IS_NUMBER(indices)) {
        // It's an interval.
        var proto_length = indices;
        for (var i = from; i < proto_length; i++) {
          if (proto.hasOwnProperty(i)) {
997
            obj[i] = UNDEFINED;
998
          }
999 1000 1001 1002 1003 1004
        }
      } else {
        for (var i = 0; i < indices.length; i++) {
          var index = indices[i];
          if (!IS_UNDEFINED(index) && from <= index &&
              proto.hasOwnProperty(index)) {
1005
            obj[index] = UNDEFINED;
1006 1007 1008 1009
          }
        }
      }
    }
1010
  };
1011

1012
  var SafeRemoveArrayHoles = function SafeRemoveArrayHoles(obj) {
1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041
    // Copy defined elements from the end to fill in all holes and undefineds
    // in the beginning of the array.  Write undefineds and holes at the end
    // after loop is finished.
    var first_undefined = 0;
    var last_defined = length - 1;
    var num_holes = 0;
    while (first_undefined < last_defined) {
      // Find first undefined element.
      while (first_undefined < last_defined &&
             !IS_UNDEFINED(obj[first_undefined])) {
        first_undefined++;
      }
      // Maintain the invariant num_holes = the number of holes in the original
      // array with indices <= first_undefined or > last_defined.
      if (!obj.hasOwnProperty(first_undefined)) {
        num_holes++;
      }

      // Find last defined element.
      while (first_undefined < last_defined &&
             IS_UNDEFINED(obj[last_defined])) {
        if (!obj.hasOwnProperty(last_defined)) {
          num_holes++;
        }
        last_defined--;
      }
      if (first_undefined < last_defined) {
        // Fill in hole or undefined.
        obj[first_undefined] = obj[last_defined];
1042
        obj[last_defined] = UNDEFINED;
1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053
      }
    }
    // If there were any undefineds in the entire array, first_undefined
    // points to one past the last defined element.  Make this true if
    // there were no undefineds, as well, so that first_undefined == number
    // of defined elements.
    if (!IS_UNDEFINED(obj[first_undefined])) first_undefined++;
    // Fill in the undefineds and the holes.  There may be a hole where
    // an undefined should be and vice versa.
    var i;
    for (i = first_undefined; i < length - num_holes; i++) {
1054
      obj[i] = UNDEFINED;
1055 1056 1057
    }
    for (i = length - num_holes; i < length; i++) {
      // For compatability with Webkit, do not expose elements in the prototype.
1058
      if (i in %GetPrototype(obj)) {
1059
        obj[i] = UNDEFINED;
1060 1061 1062 1063 1064 1065 1066
      } else {
        delete obj[i];
      }
    }

    // Return the number of defined elements.
    return first_undefined;
1067
  };
1068

1069
  var length = TO_UINT32(this.length);
1070
  if (length < 2) return this;
1071

1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085
  var is_array = IS_ARRAY(this);
  var max_prototype_element;
  if (!is_array) {
    // For compatibility with JSC, we also sort elements inherited from
    // the prototype chain on non-Array objects.
    // We do this by copying them to this object and sorting only
    // local elements. This is not very efficient, but sorting with
    // inherited elements happens very, very rarely, if at all.
    // The specification allows "implementation dependent" behavior
    // if an element on the prototype chain has an element that
    // might interact with sorting.
    max_prototype_element = CopyFromPrototype(this, length);
  }

1086 1087
  // %RemoveArrayHoles returns -1 if fast removal is not supported.
  var num_non_undefined = %RemoveArrayHoles(this, length);
1088

1089
  if (num_non_undefined == -1) {
1090 1091 1092
    // The array is observed, or there were indexed accessors in the array.
    // Move array holes and undefineds to the end using a Javascript function
    // that is safe in the presence of accessors and is observable.
1093 1094
    num_non_undefined = SafeRemoveArrayHoles(this);
  }
1095

1096
  QuickSort(this, 0, num_non_undefined);
1097

1098 1099 1100 1101 1102 1103
  if (!is_array && (num_non_undefined + 1 < max_prototype_element)) {
    // For compatibility with JSC, we shadow any elements in the prototype
    // chain that has become exposed by sort moving a hole to its position.
    ShadowPrototypeElements(this, num_non_undefined, max_prototype_element);
  }

1104
  return this;
1105
}
1106 1107 1108 1109 1110 1111


// The following functions cannot be made efficient on sparse arrays while
// preserving the semantics, since the calls to the receiver function can add
// or delete elements from the array.
function ArrayFilter(f, receiver) {
1112
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.filter");
1113

1114 1115 1116 1117 1118
  // Pull out the length so that modifications to the length in the
  // loop will not affect the looping and side effects are visible.
  var array = ToObject(this);
  var length = ToUint32(array.length);

1119
  if (!IS_SPEC_FUNCTION(f)) {
1120 1121
    throw MakeTypeError('called_non_callable', [ f ]);
  }
1122 1123
  if (IS_NULL_OR_UNDEFINED(receiver)) {
    receiver = %GetDefaultReceiver(f) || receiver;
1124
  } else if (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(f)) {
1125
    receiver = ToObject(receiver);
1126
  }
1127

1128 1129 1130
  var result = new $Array();
  var accumulator = new InternalArray();
  var accumulator_length = 0;
1131 1132 1133 1134 1135 1136 1137 1138
  var stepping = %_DebugCallbackSupportsStepping(f);
  for (var i = 0; i < length; i++) {
    if (i in array) {
      var element = array[i];
      // Prepare break slots for debugger step in.
      if (stepping) %DebugPrepareStepInIfStepping(f);
      if (%_CallFunction(receiver, element, i, array, f)) {
        accumulator[accumulator_length++] = element;
1139
      }
1140 1141
    }
  }
1142
  %MoveArrayContents(accumulator, result);
1143
  return result;
1144
}
1145 1146 1147


function ArrayForEach(f, receiver) {
1148
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.forEach");
1149

1150 1151 1152 1153 1154
  // Pull out the length so that modifications to the length in the
  // loop will not affect the looping and side effects are visible.
  var array = ToObject(this);
  var length = TO_UINT32(array.length);

1155
  if (!IS_SPEC_FUNCTION(f)) {
1156 1157
    throw MakeTypeError('called_non_callable', [ f ]);
  }
1158 1159
  if (IS_NULL_OR_UNDEFINED(receiver)) {
    receiver = %GetDefaultReceiver(f) || receiver;
1160
  } else if (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(f)) {
1161
    receiver = ToObject(receiver);
1162
  }
1163

1164 1165 1166 1167 1168 1169 1170
  var stepping = %_DebugCallbackSupportsStepping(f);
  for (var i = 0; i < length; i++) {
    if (i in array) {
      var element = array[i];
      // Prepare break slots for debugger step in.
      if (stepping) %DebugPrepareStepInIfStepping(f);
      %_CallFunction(receiver, element, i, array, f);
1171 1172
    }
  }
1173
}
1174 1175 1176 1177 1178


// Executes the function once for each element present in the
// array until it finds one where callback returns true.
function ArraySome(f, receiver) {
1179
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.some");
1180

1181 1182 1183 1184 1185
  // Pull out the length so that modifications to the length in the
  // loop will not affect the looping and side effects are visible.
  var array = ToObject(this);
  var length = TO_UINT32(array.length);

1186
  if (!IS_SPEC_FUNCTION(f)) {
1187 1188
    throw MakeTypeError('called_non_callable', [ f ]);
  }
1189 1190
  if (IS_NULL_OR_UNDEFINED(receiver)) {
    receiver = %GetDefaultReceiver(f) || receiver;
1191
  } else if (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(f)) {
1192
    receiver = ToObject(receiver);
1193
  }
1194

1195 1196 1197 1198 1199 1200 1201
  var stepping = %_DebugCallbackSupportsStepping(f);
  for (var i = 0; i < length; i++) {
    if (i in array) {
      var element = array[i];
      // Prepare break slots for debugger step in.
      if (stepping) %DebugPrepareStepInIfStepping(f);
      if (%_CallFunction(receiver, element, i, array, f)) return true;
1202
    }
1203 1204
  }
  return false;
1205
}
1206 1207 1208


function ArrayEvery(f, receiver) {
1209
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.every");
1210

1211 1212 1213 1214 1215
  // Pull out the length so that modifications to the length in the
  // loop will not affect the looping and side effects are visible.
  var array = ToObject(this);
  var length = TO_UINT32(array.length);

1216
  if (!IS_SPEC_FUNCTION(f)) {
1217 1218
    throw MakeTypeError('called_non_callable', [ f ]);
  }
1219 1220
  if (IS_NULL_OR_UNDEFINED(receiver)) {
    receiver = %GetDefaultReceiver(f) || receiver;
1221
  } else if (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(f)) {
1222
    receiver = ToObject(receiver);
1223
  }
1224

1225 1226 1227 1228 1229 1230 1231
  var stepping = %_DebugCallbackSupportsStepping(f);
  for (var i = 0; i < length; i++) {
    if (i in array) {
      var element = array[i];
      // Prepare break slots for debugger step in.
      if (stepping) %DebugPrepareStepInIfStepping(f);
      if (!%_CallFunction(receiver, element, i, array, f)) return false;
1232
    }
1233 1234
  }
  return true;
1235
}
1236 1237

function ArrayMap(f, receiver) {
1238
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map");
1239

1240 1241 1242 1243 1244
  // Pull out the length so that modifications to the length in the
  // loop will not affect the looping and side effects are visible.
  var array = ToObject(this);
  var length = TO_UINT32(array.length);

1245
  if (!IS_SPEC_FUNCTION(f)) {
1246 1247
    throw MakeTypeError('called_non_callable', [ f ]);
  }
1248 1249
  if (IS_NULL_OR_UNDEFINED(receiver)) {
    receiver = %GetDefaultReceiver(f) || receiver;
1250
  } else if (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(f)) {
1251
    receiver = ToObject(receiver);
1252
  }
1253

1254 1255
  var result = new $Array();
  var accumulator = new InternalArray(length);
1256 1257 1258 1259 1260 1261 1262
  var stepping = %_DebugCallbackSupportsStepping(f);
  for (var i = 0; i < length; i++) {
    if (i in array) {
      var element = array[i];
      // Prepare break slots for debugger step in.
      if (stepping) %DebugPrepareStepInIfStepping(f);
      accumulator[i] = %_CallFunction(receiver, element, i, array, f);
1263 1264
    }
  }
1265
  %MoveArrayContents(accumulator, result);
1266
  return result;
1267
}
1268 1269 1270


function ArrayIndexOf(element, index) {
1271
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.indexOf");
1272

1273 1274
  var length = TO_UINT32(this.length);
  if (length == 0) return -1;
1275
  if (IS_UNDEFINED(index)) {
1276 1277 1278 1279
    index = 0;
  } else {
    index = TO_INTEGER(index);
    // If index is negative, index from the end of the array.
1280 1281 1282 1283 1284
    if (index < 0) {
      index = length + index;
      // If index is still negative, search the entire array.
      if (index < 0) index = 0;
    }
1285
  }
1286 1287
  var min = index;
  var max = length;
lrn@chromium.org's avatar
lrn@chromium.org committed
1288
  if (UseSparseVariant(this, length, IS_ARRAY(this))) {
1289 1290 1291 1292
    var indices = %GetArrayKeys(this, length);
    if (IS_NUMBER(indices)) {
      // It's an interval.
      max = indices;  // Capped by length already.
1293 1294
      // Fall through to loop below.
    } else {
1295
      if (indices.length == 0) return -1;
1296
      // Get all the keys in sorted order.
1297
      var sortedKeys = GetSortedArrayKeys(this, indices);
1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308
      var n = sortedKeys.length;
      var i = 0;
      while (i < n && sortedKeys[i] < index) i++;
      while (i < n) {
        var key = sortedKeys[i];
        if (!IS_UNDEFINED(key) && this[key] === element) return key;
        i++;
      }
      return -1;
    }
  }
1309
  // Lookup through the array.
1310
  if (!IS_UNDEFINED(element)) {
1311
    for (var i = min; i < max; i++) {
1312 1313 1314 1315
      if (this[i] === element) return i;
    }
    return -1;
  }
1316 1317
  // Lookup through the array.
  for (var i = min; i < max; i++) {
1318 1319
    if (IS_UNDEFINED(this[i]) && i in this) {
      return i;
1320 1321 1322
    }
  }
  return -1;
1323
}
1324 1325 1326


function ArrayLastIndexOf(element, index) {
1327
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.lastIndexOf");
1328

1329 1330
  var length = TO_UINT32(this.length);
  if (length == 0) return -1;
1331
  if (%_ArgumentsLength() < 2) {
1332 1333 1334 1335
    index = length - 1;
  } else {
    index = TO_INTEGER(index);
    // If index is negative, index from end of the array.
1336
    if (index < 0) index += length;
1337
    // If index is still negative, do not search the array.
1338
    if (index < 0) return -1;
1339 1340
    else if (index >= length) index = length - 1;
  }
1341 1342
  var min = 0;
  var max = index;
lrn@chromium.org's avatar
lrn@chromium.org committed
1343
  if (UseSparseVariant(this, length, IS_ARRAY(this))) {
1344 1345 1346 1347
    var indices = %GetArrayKeys(this, index + 1);
    if (IS_NUMBER(indices)) {
      // It's an interval.
      max = indices;  // Capped by index already.
1348 1349
      // Fall through to loop below.
    } else {
1350
      if (indices.length == 0) return -1;
1351
      // Get all the keys in sorted order.
1352
      var sortedKeys = GetSortedArrayKeys(this, indices);
1353 1354 1355 1356 1357 1358 1359 1360 1361
      var i = sortedKeys.length - 1;
      while (i >= 0) {
        var key = sortedKeys[i];
        if (!IS_UNDEFINED(key) && this[key] === element) return key;
        i--;
      }
      return -1;
    }
  }
1362
  // Lookup through the array.
1363
  if (!IS_UNDEFINED(element)) {
1364
    for (var i = max; i >= min; i--) {
1365 1366 1367 1368
      if (this[i] === element) return i;
    }
    return -1;
  }
1369
  for (var i = max; i >= min; i--) {
1370 1371
    if (IS_UNDEFINED(this[i]) && i in this) {
      return i;
1372 1373 1374
    }
  }
  return -1;
1375
}
1376 1377


1378
function ArrayReduce(callback, current) {
1379
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduce");
1380

1381 1382 1383 1384 1385
  // Pull out the length so that modifications to the length in the
  // loop will not affect the looping and side effects are visible.
  var array = ToObject(this);
  var length = ToUint32(array.length);

1386
  if (!IS_SPEC_FUNCTION(callback)) {
1387 1388
    throw MakeTypeError('called_non_callable', [callback]);
  }
1389

1390 1391 1392
  var i = 0;
  find_initial: if (%_ArgumentsLength() < 2) {
    for (; i < length; i++) {
1393 1394
      current = array[i];
      if (!IS_UNDEFINED(current) || i in array) {
1395 1396 1397 1398 1399 1400 1401
        i++;
        break find_initial;
      }
    }
    throw MakeTypeError('reduce_no_initial', []);
  }

1402
  var receiver = %GetDefaultReceiver(callback);
1403 1404 1405 1406 1407 1408 1409
  var stepping = %_DebugCallbackSupportsStepping(callback);
  for (; i < length; i++) {
    if (i in array) {
      var element = array[i];
      // Prepare break slots for debugger step in.
      if (stepping) %DebugPrepareStepInIfStepping(callback);
      current = %_CallFunction(receiver, current, element, i, array, callback);
1410
    }
1411 1412 1413 1414 1415
  }
  return current;
}

function ArrayReduceRight(callback, current) {
1416
  CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduceRight");
1417

1418 1419 1420 1421 1422
  // Pull out the length so that side effects are visible before the
  // callback function is checked.
  var array = ToObject(this);
  var length = ToUint32(array.length);

1423
  if (!IS_SPEC_FUNCTION(callback)) {
1424 1425 1426
    throw MakeTypeError('called_non_callable', [callback]);
  }

1427
  var i = length - 1;
1428 1429
  find_initial: if (%_ArgumentsLength() < 2) {
    for (; i >= 0; i--) {
1430 1431
      current = array[i];
      if (!IS_UNDEFINED(current) || i in array) {
1432 1433 1434 1435 1436 1437 1438
        i--;
        break find_initial;
      }
    }
    throw MakeTypeError('reduce_no_initial', []);
  }

1439
  var receiver = %GetDefaultReceiver(callback);
1440 1441 1442 1443 1444 1445 1446
  var stepping = %_DebugCallbackSupportsStepping(callback);
  for (; i >= 0; i--) {
    if (i in array) {
      var element = array[i];
      // Prepare break slots for debugger step in.
      if (stepping) %DebugPrepareStepInIfStepping(callback);
      current = %_CallFunction(receiver, current, element, i, array, callback);
1447 1448 1449 1450 1451
    }
  }
  return current;
}

1452 1453 1454 1455
// ES5, 15.4.3.2
function ArrayIsArray(obj) {
  return IS_ARRAY(obj);
}
1456

1457 1458

// -------------------------------------------------------------------
1459

1460 1461
function SetUpArray() {
  %CheckIsBootstrapping();
1462

1463
  // Set up non-enumerable constructor property on the Array.prototype
1464
  // object.
1465
  %SetProperty($Array.prototype, "constructor", $Array, DONT_ENUM);
1466

1467
  // Set up non-enumerable functions on the Array object.
1468 1469 1470 1471
  InstallFunctions($Array, DONT_ENUM, $Array(
    "isArray", ArrayIsArray
  ));

1472
  var specialFunctions = %SpecialArrayFunctions();
1473

1474
  var getFunction = function(name, jsBuiltin, len) {
1475 1476 1477 1478
    var f = jsBuiltin;
    if (specialFunctions.hasOwnProperty(name)) {
      f = specialFunctions[name];
    }
1479
    if (!IS_UNDEFINED(len)) {
1480 1481 1482
      %FunctionSetLength(f, len);
    }
    return f;
1483
  };
1484

1485
  // Set up non-enumerable functions of the Array.prototype object and
1486
  // set their names.
1487 1488
  // Manipulate the length of some of the functions to meet
  // expectations set by ECMA-262 or Mozilla.
1489
  InstallFunctions($Array.prototype, DONT_ENUM, $Array(
1490 1491 1492 1493 1494
    "toString", getFunction("toString", ArrayToString),
    "toLocaleString", getFunction("toLocaleString", ArrayToLocaleString),
    "join", getFunction("join", ArrayJoin),
    "pop", getFunction("pop", ArrayPop),
    "push", getFunction("push", ArrayPush, 1),
1495
    "concat", getFunction("concat", ArrayConcat, 1),
1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510
    "reverse", getFunction("reverse", ArrayReverse),
    "shift", getFunction("shift", ArrayShift),
    "unshift", getFunction("unshift", ArrayUnshift, 1),
    "slice", getFunction("slice", ArraySlice, 2),
    "splice", getFunction("splice", ArraySplice, 2),
    "sort", getFunction("sort", ArraySort),
    "filter", getFunction("filter", ArrayFilter, 1),
    "forEach", getFunction("forEach", ArrayForEach, 1),
    "some", getFunction("some", ArraySome, 1),
    "every", getFunction("every", ArrayEvery, 1),
    "map", getFunction("map", ArrayMap, 1),
    "indexOf", getFunction("indexOf", ArrayIndexOf, 1),
    "lastIndexOf", getFunction("lastIndexOf", ArrayLastIndexOf, 1),
    "reduce", getFunction("reduce", ArrayReduce, 1),
    "reduceRight", getFunction("reduceRight", ArrayReduceRight, 1)
1511
  ));
1512

1513
  %FinishArrayPrototypeSetup($Array.prototype);
1514 1515

  // The internal Array prototype doesn't need to be fancy, since it's never
1516 1517 1518
  // exposed to user code.
  // Adding only the functions that are actually used.
  SetUpLockedPrototype(InternalArray, $Array(), $Array(
1519
    "concat", getFunction("concat", ArrayConcat),
1520
    "indexOf", getFunction("indexOf", ArrayIndexOf),
1521 1522
    "join", getFunction("join", ArrayJoin),
    "pop", getFunction("pop", ArrayPop),
1523 1524
    "push", getFunction("push", ArrayPush),
    "splice", getFunction("splice", ArraySplice)
1525
  ));
1526 1527 1528 1529 1530 1531

  SetUpLockedPrototype(InternalPackedArray, $Array(), $Array(
    "join", getFunction("join", ArrayJoin),
    "pop", getFunction("pop", ArrayPop),
    "push", getFunction("push", ArrayPush)
  ));
1532
}
1533

1534
SetUpArray();