Home | History | Annotate | Download | only in src
      1 // Copyright 2012 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 
      5 "use strict";
      6 
      7 // This file relies on the fact that the following declarations have been made
      8 // in runtime.js:
      9 // var $Array = global.Array;
     10 
     11 // -------------------------------------------------------------------
     12 
     13 // Global list of arrays visited during toString, toLocaleString and
     14 // join invocations.
     15 var visited_arrays = new InternalArray();
     16 
     17 
     18 // Gets a sorted array of array keys.  Useful for operations on sparse
     19 // arrays.  Dupes have not been removed.
     20 function GetSortedArrayKeys(array, indices) {
     21   var keys = new InternalArray();
     22   if (IS_NUMBER(indices)) {
     23     // It's an interval
     24     var limit = indices;
     25     for (var i = 0; i < limit; ++i) {
     26       var e = array[i];
     27       if (!IS_UNDEFINED(e) || i in array) {
     28         keys.push(i);
     29       }
     30     }
     31   } else {
     32     var length = indices.length;
     33     for (var k = 0; k < length; ++k) {
     34       var key = indices[k];
     35       if (!IS_UNDEFINED(key)) {
     36         var e = array[key];
     37         if (!IS_UNDEFINED(e) || key in array) {
     38           keys.push(key);
     39         }
     40       }
     41     }
     42     %_CallFunction(keys, function(a, b) { return a - b; }, ArraySort);
     43   }
     44   return keys;
     45 }
     46 
     47 
     48 function SparseJoinWithSeparatorJS(array, len, convert, separator) {
     49   var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
     50   var totalLength = 0;
     51   var elements = new InternalArray(keys.length * 2);
     52   var previousKey = -1;
     53   for (var i = 0; i < keys.length; i++) {
     54     var key = keys[i];
     55     if (key != previousKey) {  // keys may contain duplicates.
     56       var e = array[key];
     57       if (!IS_STRING(e)) e = convert(e);
     58       elements[i * 2] = key;
     59       elements[i * 2 + 1] = e;
     60       previousKey = key;
     61     }
     62   }
     63   return %SparseJoinWithSeparator(elements, len, separator);
     64 }
     65 
     66 
     67 // Optimized for sparse arrays if separator is ''.
     68 function SparseJoin(array, len, convert) {
     69   var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
     70   var last_key = -1;
     71   var keys_length = keys.length;
     72 
     73   var elements = new InternalArray(keys_length);
     74   var elements_length = 0;
     75 
     76   for (var i = 0; i < keys_length; i++) {
     77     var key = keys[i];
     78     if (key != last_key) {
     79       var e = array[key];
     80       if (!IS_STRING(e)) e = convert(e);
     81       elements[elements_length++] = e;
     82       last_key = key;
     83     }
     84   }
     85   return %StringBuilderConcat(elements, elements_length, '');
     86 }
     87 
     88 
     89 function UseSparseVariant(array, length, is_array, touched) {
     90   // Only use the sparse variant on arrays that are likely to be sparse and the
     91   // number of elements touched in the operation is relatively small compared to
     92   // the overall size of the array.
     93   if (!is_array || length < 1000 || %IsObserved(array)) {
     94     return false;
     95   }
     96   if (!%_IsSmi(length)) {
     97     return true;
     98   }
     99   var elements_threshold = length >> 2;  // No more than 75% holes
    100   var estimated_elements = %EstimateNumberOfElements(array);
    101   return (estimated_elements < elements_threshold) &&
    102     (touched > estimated_elements * 4);
    103 }
    104 
    105 
    106 function Join(array, length, separator, convert) {
    107   if (length == 0) return '';
    108 
    109   var is_array = IS_ARRAY(array);
    110 
    111   if (is_array) {
    112     // If the array is cyclic, return the empty string for already
    113     // visited arrays.
    114     if (!%PushIfAbsent(visited_arrays, array)) return '';
    115   }
    116 
    117   // Attempt to convert the elements.
    118   try {
    119     if (UseSparseVariant(array, length, is_array, length)) {
    120       %NormalizeElements(array);
    121       if (separator.length == 0) {
    122         return SparseJoin(array, length, convert);
    123       } else {
    124         return SparseJoinWithSeparatorJS(array, length, convert, separator);
    125       }
    126     }
    127 
    128     // Fast case for one-element arrays.
    129     if (length == 1) {
    130       var e = array[0];
    131       if (IS_STRING(e)) return e;
    132       return convert(e);
    133     }
    134 
    135     // Construct an array for the elements.
    136     var elements = new InternalArray(length);
    137 
    138     // We pull the empty separator check outside the loop for speed!
    139     if (separator.length == 0) {
    140       var elements_length = 0;
    141       for (var i = 0; i < length; i++) {
    142         var e = array[i];
    143         if (!IS_STRING(e)) e = convert(e);
    144         elements[elements_length++] = e;
    145       }
    146       elements.length = elements_length;
    147       var result = %_FastOneByteArrayJoin(elements, '');
    148       if (!IS_UNDEFINED(result)) return result;
    149       return %StringBuilderConcat(elements, elements_length, '');
    150     }
    151     // Non-empty separator case.
    152     // If the first element is a number then use the heuristic that the
    153     // remaining elements are also likely to be numbers.
    154     if (!IS_NUMBER(array[0])) {
    155       for (var i = 0; i < length; i++) {
    156         var e = array[i];
    157         if (!IS_STRING(e)) e = convert(e);
    158         elements[i] = e;
    159       }
    160     } else {
    161       for (var i = 0; i < length; i++) {
    162         var e = array[i];
    163         if (IS_NUMBER(e)) {
    164           e = %_NumberToString(e);
    165         } else if (!IS_STRING(e)) {
    166           e = convert(e);
    167         }
    168         elements[i] = e;
    169       }
    170     }
    171     var result = %_FastOneByteArrayJoin(elements, separator);
    172     if (!IS_UNDEFINED(result)) return result;
    173 
    174     return %StringBuilderJoin(elements, length, separator);
    175   } finally {
    176     // Make sure to remove the last element of the visited array no
    177     // matter what happens.
    178     if (is_array) visited_arrays.length = visited_arrays.length - 1;
    179   }
    180 }
    181 
    182 
    183 function ConvertToString(x) {
    184   // Assumes x is a non-string.
    185   if (IS_NUMBER(x)) return %_NumberToString(x);
    186   if (IS_BOOLEAN(x)) return x ? 'true' : 'false';
    187   return (IS_NULL_OR_UNDEFINED(x)) ? '' : %ToString(%DefaultString(x));
    188 }
    189 
    190 
    191 function ConvertToLocaleString(e) {
    192   if (IS_NULL_OR_UNDEFINED(e)) {
    193     return '';
    194   } else {
    195     // According to ES5, section 15.4.4.3, the toLocaleString conversion
    196     // must throw a TypeError if ToObject(e).toLocaleString isn't
    197     // callable.
    198     var e_obj = ToObject(e);
    199     return %ToString(e_obj.toLocaleString());
    200   }
    201 }
    202 
    203 
    204 // This function implements the optimized splice implementation that can use
    205 // special array operations to handle sparse arrays in a sensible fashion.
    206 function SmartSlice(array, start_i, del_count, len, deleted_elements) {
    207   // Move deleted elements to a new array (the return value from splice).
    208   var indices = %GetArrayKeys(array, start_i + del_count);
    209   if (IS_NUMBER(indices)) {
    210     var limit = indices;
    211     for (var i = start_i; i < limit; ++i) {
    212       var current = array[i];
    213       if (!IS_UNDEFINED(current) || i in array) {
    214         deleted_elements[i - start_i] = current;
    215       }
    216     }
    217   } else {
    218     var length = indices.length;
    219     for (var k = 0; k < length; ++k) {
    220       var key = indices[k];
    221       if (!IS_UNDEFINED(key)) {
    222         if (key >= start_i) {
    223           var current = array[key];
    224           if (!IS_UNDEFINED(current) || key in array) {
    225             deleted_elements[key - start_i] = current;
    226           }
    227         }
    228       }
    229     }
    230   }
    231 }
    232 
    233 
    234 // This function implements the optimized splice implementation that can use
    235 // special array operations to handle sparse arrays in a sensible fashion.
    236 function SmartMove(array, start_i, del_count, len, num_additional_args) {
    237   // Move data to new array.
    238   var new_array = new InternalArray(len - del_count + num_additional_args);
    239   var indices = %GetArrayKeys(array, len);
    240   if (IS_NUMBER(indices)) {
    241     var limit = indices;
    242     for (var i = 0; i < start_i && i < limit; ++i) {
    243       var current = array[i];
    244       if (!IS_UNDEFINED(current) || i in array) {
    245         new_array[i] = current;
    246       }
    247     }
    248     for (var i = start_i + del_count; i < limit; ++i) {
    249       var current = array[i];
    250       if (!IS_UNDEFINED(current) || i in array) {
    251         new_array[i - del_count + num_additional_args] = current;
    252       }
    253     }
    254   } else {
    255     var length = indices.length;
    256     for (var k = 0; k < length; ++k) {
    257       var key = indices[k];
    258       if (!IS_UNDEFINED(key)) {
    259         if (key < start_i) {
    260           var current = array[key];
    261           if (!IS_UNDEFINED(current) || key in array) {
    262             new_array[key] = current;
    263           }
    264         } else if (key >= start_i + del_count) {
    265           var current = array[key];
    266           if (!IS_UNDEFINED(current) || key in array) {
    267             new_array[key - del_count + num_additional_args] = current;
    268           }
    269         }
    270       }
    271     }
    272   }
    273   // Move contents of new_array into this array
    274   %MoveArrayContents(new_array, array);
    275 }
    276 
    277 
    278 // This is part of the old simple-minded splice.  We are using it either
    279 // because the receiver is not an array (so we have no choice) or because we
    280 // know we are not deleting or moving a lot of elements.
    281 function SimpleSlice(array, start_i, del_count, len, deleted_elements) {
    282   for (var i = 0; i < del_count; i++) {
    283     var index = start_i + i;
    284     // The spec could also be interpreted such that %HasOwnProperty
    285     // would be the appropriate test.  We follow KJS in consulting the
    286     // prototype.
    287     var current = array[index];
    288     if (!IS_UNDEFINED(current) || index in array) {
    289       deleted_elements[i] = current;
    290     }
    291   }
    292 }
    293 
    294 
    295 function SimpleMove(array, start_i, del_count, len, num_additional_args) {
    296   if (num_additional_args !== del_count) {
    297     // Move the existing elements after the elements to be deleted
    298     // to the right position in the resulting array.
    299     if (num_additional_args > del_count) {
    300       for (var i = len - del_count; i > start_i; i--) {
    301         var from_index = i + del_count - 1;
    302         var to_index = i + num_additional_args - 1;
    303         // The spec could also be interpreted such that
    304         // %HasOwnProperty would be the appropriate test.  We follow
    305         // KJS in consulting the prototype.
    306         var current = array[from_index];
    307         if (!IS_UNDEFINED(current) || from_index in array) {
    308           array[to_index] = current;
    309         } else {
    310           delete array[to_index];
    311         }
    312       }
    313     } else {
    314       for (var i = start_i; i < len - del_count; i++) {
    315         var from_index = i + del_count;
    316         var to_index = i + num_additional_args;
    317         // The spec could also be interpreted such that
    318         // %HasOwnProperty would be the appropriate test.  We follow
    319         // KJS in consulting the prototype.
    320         var current = array[from_index];
    321         if (!IS_UNDEFINED(current) || from_index in array) {
    322           array[to_index] = current;
    323         } else {
    324           delete array[to_index];
    325         }
    326       }
    327       for (var i = len; i > len - del_count + num_additional_args; i--) {
    328         delete array[i - 1];
    329       }
    330     }
    331   }
    332 }
    333 
    334 
    335 // -------------------------------------------------------------------
    336 
    337 
    338 function ArrayToString() {
    339   var array;
    340   var func;
    341   if (IS_ARRAY(this)) {
    342     func = this.join;
    343     if (func === ArrayJoin) {
    344       return Join(this, this.length, ',', ConvertToString);
    345     }
    346     array = this;
    347   } else {
    348     array = ToObject(this);
    349     func = array.join;
    350   }
    351   if (!IS_SPEC_FUNCTION(func)) {
    352     return %_CallFunction(array, ObjectToString);
    353   }
    354   return %_CallFunction(array, func);
    355 }
    356 
    357 
    358 function ArrayToLocaleString() {
    359   var array = ToObject(this);
    360   var arrayLen = array.length;
    361   var len = TO_UINT32(arrayLen);
    362   if (len === 0) return "";
    363   return Join(array, len, ',', ConvertToLocaleString);
    364 }
    365 
    366 
    367 function ArrayJoin(separator) {
    368   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.join");
    369 
    370   var array = TO_OBJECT_INLINE(this);
    371   var length = TO_UINT32(array.length);
    372   if (IS_UNDEFINED(separator)) {
    373     separator = ',';
    374   } else if (!IS_STRING(separator)) {
    375     separator = NonStringToString(separator);
    376   }
    377 
    378   var result = %_FastOneByteArrayJoin(array, separator);
    379   if (!IS_UNDEFINED(result)) return result;
    380 
    381   return Join(array, length, separator, ConvertToString);
    382 }
    383 
    384 
    385 function ObservedArrayPop(n) {
    386   n--;
    387   var value = this[n];
    388 
    389   try {
    390     BeginPerformSplice(this);
    391     delete this[n];
    392     this.length = n;
    393   } finally {
    394     EndPerformSplice(this);
    395     EnqueueSpliceRecord(this, n, [value], 0);
    396   }
    397 
    398   return value;
    399 }
    400 
    401 // Removes the last element from the array and returns it. See
    402 // ECMA-262, section 15.4.4.6.
    403 function ArrayPop() {
    404   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.pop");
    405 
    406   var array = TO_OBJECT_INLINE(this);
    407   var n = TO_UINT32(array.length);
    408   if (n == 0) {
    409     array.length = n;
    410     return;
    411   }
    412 
    413   if (%IsObserved(array))
    414     return ObservedArrayPop.call(array, n);
    415 
    416   n--;
    417   var value = array[n];
    418   Delete(array, ToName(n), true);
    419   array.length = n;
    420   return value;
    421 }
    422 
    423 
    424 function ObservedArrayPush() {
    425   var n = TO_UINT32(this.length);
    426   var m = %_ArgumentsLength();
    427 
    428   try {
    429     BeginPerformSplice(this);
    430     for (var i = 0; i < m; i++) {
    431       this[i+n] = %_Arguments(i);
    432     }
    433     var new_length = n + m;
    434     this.length = new_length;
    435   } finally {
    436     EndPerformSplice(this);
    437     EnqueueSpliceRecord(this, n, [], m);
    438   }
    439 
    440   return new_length;
    441 }
    442 
    443 // Appends the arguments to the end of the array and returns the new
    444 // length of the array. See ECMA-262, section 15.4.4.7.
    445 function ArrayPush() {
    446   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.push");
    447 
    448   if (%IsObserved(this))
    449     return ObservedArrayPush.apply(this, arguments);
    450 
    451   var array = TO_OBJECT_INLINE(this);
    452   var n = TO_UINT32(array.length);
    453   var m = %_ArgumentsLength();
    454 
    455   for (var i = 0; i < m; i++) {
    456     array[i+n] = %_Arguments(i);
    457   }
    458 
    459   var new_length = n + m;
    460   array.length = new_length;
    461   return new_length;
    462 }
    463 
    464 
    465 // Returns an array containing the array elements of the object followed
    466 // by the array elements of each argument in order. See ECMA-262,
    467 // section 15.4.4.7.
    468 function ArrayConcatJS(arg1) {  // length == 1
    469   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.concat");
    470 
    471   var array = ToObject(this);
    472   var arg_count = %_ArgumentsLength();
    473   var arrays = new InternalArray(1 + arg_count);
    474   arrays[0] = array;
    475   for (var i = 0; i < arg_count; i++) {
    476     arrays[i + 1] = %_Arguments(i);
    477   }
    478 
    479   return %ArrayConcat(arrays);
    480 }
    481 
    482 
    483 // For implementing reverse() on large, sparse arrays.
    484 function SparseReverse(array, len) {
    485   var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
    486   var high_counter = keys.length - 1;
    487   var low_counter = 0;
    488   while (low_counter <= high_counter) {
    489     var i = keys[low_counter];
    490     var j = keys[high_counter];
    491 
    492     var j_complement = len - j - 1;
    493     var low, high;
    494 
    495     if (j_complement <= i) {
    496       high = j;
    497       while (keys[--high_counter] == j) { }
    498       low = j_complement;
    499     }
    500     if (j_complement >= i) {
    501       low = i;
    502       while (keys[++low_counter] == i) { }
    503       high = len - i - 1;
    504     }
    505 
    506     var current_i = array[low];
    507     if (!IS_UNDEFINED(current_i) || low in array) {
    508       var current_j = array[high];
    509       if (!IS_UNDEFINED(current_j) || high in array) {
    510         array[low] = current_j;
    511         array[high] = current_i;
    512       } else {
    513         array[high] = current_i;
    514         delete array[low];
    515       }
    516     } else {
    517       var current_j = array[high];
    518       if (!IS_UNDEFINED(current_j) || high in array) {
    519         array[low] = current_j;
    520         delete array[high];
    521       }
    522     }
    523   }
    524 }
    525 
    526 
    527 function ArrayReverse() {
    528   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reverse");
    529 
    530   var array = TO_OBJECT_INLINE(this);
    531   var len = TO_UINT32(array.length);
    532 
    533   if (UseSparseVariant(array, len, IS_ARRAY(array), len)) {
    534     %NormalizeElements(array);
    535     SparseReverse(array, len);
    536     return array;
    537   }
    538 
    539   var j = len - 1;
    540   for (var i = 0; i < j; i++, j--) {
    541     var current_i = array[i];
    542     if (!IS_UNDEFINED(current_i) || i in array) {
    543       var current_j = array[j];
    544       if (!IS_UNDEFINED(current_j) || j in array) {
    545         array[i] = current_j;
    546         array[j] = current_i;
    547       } else {
    548         array[j] = current_i;
    549         delete array[i];
    550       }
    551     } else {
    552       var current_j = array[j];
    553       if (!IS_UNDEFINED(current_j) || j in array) {
    554         array[i] = current_j;
    555         delete array[j];
    556       }
    557     }
    558   }
    559   return array;
    560 }
    561 
    562 
    563 function ObservedArrayShift(len) {
    564   var first = this[0];
    565 
    566   try {
    567     BeginPerformSplice(this);
    568     SimpleMove(this, 0, 1, len, 0);
    569     this.length = len - 1;
    570   } finally {
    571     EndPerformSplice(this);
    572     EnqueueSpliceRecord(this, 0, [first], 0);
    573   }
    574 
    575   return first;
    576 }
    577 
    578 function ArrayShift() {
    579   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.shift");
    580 
    581   var array = TO_OBJECT_INLINE(this);
    582   var len = TO_UINT32(array.length);
    583 
    584   if (len === 0) {
    585     array.length = 0;
    586     return;
    587   }
    588 
    589   if (ObjectIsSealed(array)) {
    590     throw MakeTypeError("array_functions_change_sealed",
    591                         ["Array.prototype.shift"]);
    592   }
    593 
    594   if (%IsObserved(array))
    595     return ObservedArrayShift.call(array, len);
    596 
    597   var first = array[0];
    598 
    599   if (IS_ARRAY(array)) {
    600     SmartMove(array, 0, 1, len, 0);
    601   } else {
    602     SimpleMove(array, 0, 1, len, 0);
    603   }
    604 
    605   array.length = len - 1;
    606 
    607   return first;
    608 }
    609 
    610 function ObservedArrayUnshift() {
    611   var len = TO_UINT32(this.length);
    612   var num_arguments = %_ArgumentsLength();
    613 
    614   try {
    615     BeginPerformSplice(this);
    616     SimpleMove(this, 0, 0, len, num_arguments);
    617     for (var i = 0; i < num_arguments; i++) {
    618       this[i] = %_Arguments(i);
    619     }
    620     var new_length = len + num_arguments;
    621     this.length = new_length;
    622   } finally {
    623     EndPerformSplice(this);
    624     EnqueueSpliceRecord(this, 0, [], num_arguments);
    625   }
    626 
    627   return new_length;
    628 }
    629 
    630 function ArrayUnshift(arg1) {  // length == 1
    631   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.unshift");
    632 
    633   if (%IsObserved(this))
    634     return ObservedArrayUnshift.apply(this, arguments);
    635 
    636   var array = TO_OBJECT_INLINE(this);
    637   var len = TO_UINT32(array.length);
    638   var num_arguments = %_ArgumentsLength();
    639   var is_sealed = ObjectIsSealed(array);
    640 
    641   if (IS_ARRAY(array) && !is_sealed && len > 0) {
    642     SmartMove(array, 0, 0, len, num_arguments);
    643   } else {
    644     SimpleMove(array, 0, 0, len, num_arguments);
    645   }
    646 
    647   for (var i = 0; i < num_arguments; i++) {
    648     array[i] = %_Arguments(i);
    649   }
    650 
    651   var new_length = len + num_arguments;
    652   array.length = new_length;
    653   return new_length;
    654 }
    655 
    656 
    657 function ArraySlice(start, end) {
    658   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.slice");
    659 
    660   var array = TO_OBJECT_INLINE(this);
    661   var len = TO_UINT32(array.length);
    662   var start_i = TO_INTEGER(start);
    663   var end_i = len;
    664 
    665   if (!IS_UNDEFINED(end)) end_i = TO_INTEGER(end);
    666 
    667   if (start_i < 0) {
    668     start_i += len;
    669     if (start_i < 0) start_i = 0;
    670   } else {
    671     if (start_i > len) start_i = len;
    672   }
    673 
    674   if (end_i < 0) {
    675     end_i += len;
    676     if (end_i < 0) end_i = 0;
    677   } else {
    678     if (end_i > len) end_i = len;
    679   }
    680 
    681   var result = [];
    682 
    683   if (end_i < start_i) return result;
    684 
    685   if (UseSparseVariant(array, len, IS_ARRAY(array), end_i - start_i)) {
    686     %NormalizeElements(array);
    687     %NormalizeElements(result);
    688     SmartSlice(array, start_i, end_i - start_i, len, result);
    689   } else {
    690     SimpleSlice(array, start_i, end_i - start_i, len, result);
    691   }
    692 
    693   result.length = end_i - start_i;
    694 
    695   return result;
    696 }
    697 
    698 
    699 function ComputeSpliceStartIndex(start_i, len) {
    700   if (start_i < 0) {
    701     start_i += len;
    702     return start_i < 0 ? 0 : start_i;
    703   }
    704 
    705   return start_i > len ? len : start_i;
    706 }
    707 
    708 
    709 function ComputeSpliceDeleteCount(delete_count, num_arguments, len, start_i) {
    710   // SpiderMonkey, TraceMonkey and JSC treat the case where no delete count is
    711   // given as a request to delete all the elements from the start.
    712   // And it differs from the case of undefined delete count.
    713   // This does not follow ECMA-262, but we do the same for
    714   // compatibility.
    715   var del_count = 0;
    716   if (num_arguments == 1)
    717     return len - start_i;
    718 
    719   del_count = TO_INTEGER(delete_count);
    720   if (del_count < 0)
    721     return 0;
    722 
    723   if (del_count > len - start_i)
    724     return len - start_i;
    725 
    726   return del_count;
    727 }
    728 
    729 
    730 function ObservedArraySplice(start, delete_count) {
    731   var num_arguments = %_ArgumentsLength();
    732   var len = TO_UINT32(this.length);
    733   var start_i = ComputeSpliceStartIndex(TO_INTEGER(start), len);
    734   var del_count = ComputeSpliceDeleteCount(delete_count, num_arguments, len,
    735                                            start_i);
    736   var deleted_elements = [];
    737   deleted_elements.length = del_count;
    738   var num_elements_to_add = num_arguments > 2 ? num_arguments - 2 : 0;
    739 
    740   try {
    741     BeginPerformSplice(this);
    742 
    743     SimpleSlice(this, start_i, del_count, len, deleted_elements);
    744     SimpleMove(this, start_i, del_count, len, num_elements_to_add);
    745 
    746     // Insert the arguments into the resulting array in
    747     // place of the deleted elements.
    748     var i = start_i;
    749     var arguments_index = 2;
    750     var arguments_length = %_ArgumentsLength();
    751     while (arguments_index < arguments_length) {
    752       this[i++] = %_Arguments(arguments_index++);
    753     }
    754     this.length = len - del_count + num_elements_to_add;
    755 
    756   } finally {
    757     EndPerformSplice(this);
    758     if (deleted_elements.length || num_elements_to_add) {
    759        EnqueueSpliceRecord(this,
    760                            start_i,
    761                            deleted_elements.slice(),
    762                            num_elements_to_add);
    763     }
    764   }
    765 
    766   // Return the deleted elements.
    767   return deleted_elements;
    768 }
    769 
    770 
    771 function ArraySplice(start, delete_count) {
    772   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.splice");
    773 
    774   if (%IsObserved(this))
    775     return ObservedArraySplice.apply(this, arguments);
    776 
    777   var num_arguments = %_ArgumentsLength();
    778   var array = TO_OBJECT_INLINE(this);
    779   var len = TO_UINT32(array.length);
    780   var start_i = ComputeSpliceStartIndex(TO_INTEGER(start), len);
    781   var del_count = ComputeSpliceDeleteCount(delete_count, num_arguments, len,
    782                                            start_i);
    783   var deleted_elements = [];
    784   deleted_elements.length = del_count;
    785   var num_elements_to_add = num_arguments > 2 ? num_arguments - 2 : 0;
    786 
    787   if (del_count != num_elements_to_add && ObjectIsSealed(array)) {
    788     throw MakeTypeError("array_functions_change_sealed",
    789                         ["Array.prototype.splice"]);
    790   } else if (del_count > 0 && ObjectIsFrozen(array)) {
    791     throw MakeTypeError("array_functions_on_frozen",
    792                         ["Array.prototype.splice"]);
    793   }
    794 
    795   var changed_elements = del_count;
    796   if (num_elements_to_add != del_count) {
    797     // If the slice needs to do a actually move elements after the insertion
    798     // point, then include those in the estimate of changed elements.
    799     changed_elements += len - start_i - del_count;
    800   }
    801   if (UseSparseVariant(array, len, IS_ARRAY(array), changed_elements)) {
    802     %NormalizeElements(array);
    803     %NormalizeElements(deleted_elements);
    804     SmartSlice(array, start_i, del_count, len, deleted_elements);
    805     SmartMove(array, start_i, del_count, len, num_elements_to_add);
    806   } else {
    807     SimpleSlice(array, start_i, del_count, len, deleted_elements);
    808     SimpleMove(array, start_i, del_count, len, num_elements_to_add);
    809   }
    810 
    811   // Insert the arguments into the resulting array in
    812   // place of the deleted elements.
    813   var i = start_i;
    814   var arguments_index = 2;
    815   var arguments_length = %_ArgumentsLength();
    816   while (arguments_index < arguments_length) {
    817     array[i++] = %_Arguments(arguments_index++);
    818   }
    819   array.length = len - del_count + num_elements_to_add;
    820 
    821   // Return the deleted elements.
    822   return deleted_elements;
    823 }
    824 
    825 
    826 function ArraySort(comparefn) {
    827   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.sort");
    828 
    829   // In-place QuickSort algorithm.
    830   // For short (length <= 22) arrays, insertion sort is used for efficiency.
    831 
    832   if (!IS_SPEC_FUNCTION(comparefn)) {
    833     comparefn = function (x, y) {
    834       if (x === y) return 0;
    835       if (%_IsSmi(x) && %_IsSmi(y)) {
    836         return %SmiLexicographicCompare(x, y);
    837       }
    838       x = ToString(x);
    839       y = ToString(y);
    840       if (x == y) return 0;
    841       else return x < y ? -1 : 1;
    842     };
    843   }
    844   var receiver = %GetDefaultReceiver(comparefn);
    845 
    846   var InsertionSort = function InsertionSort(a, from, to) {
    847     for (var i = from + 1; i < to; i++) {
    848       var element = a[i];
    849       for (var j = i - 1; j >= from; j--) {
    850         var tmp = a[j];
    851         var order = %_CallFunction(receiver, tmp, element, comparefn);
    852         if (order > 0) {
    853           a[j + 1] = tmp;
    854         } else {
    855           break;
    856         }
    857       }
    858       a[j + 1] = element;
    859     }
    860   };
    861 
    862   var GetThirdIndex = function(a, from, to) {
    863     var t_array = [];
    864     // Use both 'from' and 'to' to determine the pivot candidates.
    865     var increment = 200 + ((to - from) & 15);
    866     for (var i = from + 1, j = 0; i < to - 1; i += increment, j++) {
    867       t_array[j] = [i, a[i]];
    868     }
    869     %_CallFunction(t_array, function(a, b) {
    870       return %_CallFunction(receiver, a[1], b[1], comparefn);
    871     }, ArraySort);
    872     var third_index = t_array[t_array.length >> 1][0];
    873     return third_index;
    874   }
    875 
    876   var QuickSort = function QuickSort(a, from, to) {
    877     var third_index = 0;
    878     while (true) {
    879       // Insertion sort is faster for short arrays.
    880       if (to - from <= 10) {
    881         InsertionSort(a, from, to);
    882         return;
    883       }
    884       if (to - from > 1000) {
    885         third_index = GetThirdIndex(a, from, to);
    886       } else {
    887         third_index = from + ((to - from) >> 1);
    888       }
    889       // Find a pivot as the median of first, last and middle element.
    890       var v0 = a[from];
    891       var v1 = a[to - 1];
    892       var v2 = a[third_index];
    893       var c01 = %_CallFunction(receiver, v0, v1, comparefn);
    894       if (c01 > 0) {
    895         // v1 < v0, so swap them.
    896         var tmp = v0;
    897         v0 = v1;
    898         v1 = tmp;
    899       } // v0 <= v1.
    900       var c02 = %_CallFunction(receiver, v0, v2, comparefn);
    901       if (c02 >= 0) {
    902         // v2 <= v0 <= v1.
    903         var tmp = v0;
    904         v0 = v2;
    905         v2 = v1;
    906         v1 = tmp;
    907       } else {
    908         // v0 <= v1 && v0 < v2
    909         var c12 = %_CallFunction(receiver, v1, v2, comparefn);
    910         if (c12 > 0) {
    911           // v0 <= v2 < v1
    912           var tmp = v1;
    913           v1 = v2;
    914           v2 = tmp;
    915         }
    916       }
    917       // v0 <= v1 <= v2
    918       a[from] = v0;
    919       a[to - 1] = v2;
    920       var pivot = v1;
    921       var low_end = from + 1;   // Upper bound of elements lower than pivot.
    922       var high_start = to - 1;  // Lower bound of elements greater than pivot.
    923       a[third_index] = a[low_end];
    924       a[low_end] = pivot;
    925 
    926       // From low_end to i are elements equal to pivot.
    927       // From i to high_start are elements that haven't been compared yet.
    928       partition: for (var i = low_end + 1; i < high_start; i++) {
    929         var element = a[i];
    930         var order = %_CallFunction(receiver, element, pivot, comparefn);
    931         if (order < 0) {
    932           a[i] = a[low_end];
    933           a[low_end] = element;
    934           low_end++;
    935         } else if (order > 0) {
    936           do {
    937             high_start--;
    938             if (high_start == i) break partition;
    939             var top_elem = a[high_start];
    940             order = %_CallFunction(receiver, top_elem, pivot, comparefn);
    941           } while (order > 0);
    942           a[i] = a[high_start];
    943           a[high_start] = element;
    944           if (order < 0) {
    945             element = a[i];
    946             a[i] = a[low_end];
    947             a[low_end] = element;
    948             low_end++;
    949           }
    950         }
    951       }
    952       if (to - high_start < low_end - from) {
    953         QuickSort(a, high_start, to);
    954         to = low_end;
    955       } else {
    956         QuickSort(a, from, low_end);
    957         from = high_start;
    958       }
    959     }
    960   };
    961 
    962   // Copy elements in the range 0..length from obj's prototype chain
    963   // to obj itself, if obj has holes. Return one more than the maximal index
    964   // of a prototype property.
    965   var CopyFromPrototype = function CopyFromPrototype(obj, length) {
    966     var max = 0;
    967     for (var proto = %GetPrototype(obj); proto; proto = %GetPrototype(proto)) {
    968       var indices = %GetArrayKeys(proto, length);
    969       if (IS_NUMBER(indices)) {
    970         // It's an interval.
    971         var proto_length = indices;
    972         for (var i = 0; i < proto_length; i++) {
    973           if (!HAS_OWN_PROPERTY(obj, i) && HAS_OWN_PROPERTY(proto, i)) {
    974             obj[i] = proto[i];
    975             if (i >= max) { max = i + 1; }
    976           }
    977         }
    978       } else {
    979         for (var i = 0; i < indices.length; i++) {
    980           var index = indices[i];
    981           if (!IS_UNDEFINED(index) && !HAS_OWN_PROPERTY(obj, index)
    982               && HAS_OWN_PROPERTY(proto, index)) {
    983             obj[index] = proto[index];
    984             if (index >= max) { max = index + 1; }
    985           }
    986         }
    987       }
    988     }
    989     return max;
    990   };
    991 
    992   // Set a value of "undefined" on all indices in the range from..to
    993   // where a prototype of obj has an element. I.e., shadow all prototype
    994   // elements in that range.
    995   var ShadowPrototypeElements = function(obj, from, to) {
    996     for (var proto = %GetPrototype(obj); proto; proto = %GetPrototype(proto)) {
    997       var indices = %GetArrayKeys(proto, to);
    998       if (IS_NUMBER(indices)) {
    999         // It's an interval.
   1000         var proto_length = indices;
   1001         for (var i = from; i < proto_length; i++) {
   1002           if (HAS_OWN_PROPERTY(proto, i)) {
   1003             obj[i] = UNDEFINED;
   1004           }
   1005         }
   1006       } else {
   1007         for (var i = 0; i < indices.length; i++) {
   1008           var index = indices[i];
   1009           if (!IS_UNDEFINED(index) && from <= index &&
   1010               HAS_OWN_PROPERTY(proto, index)) {
   1011             obj[index] = UNDEFINED;
   1012           }
   1013         }
   1014       }
   1015     }
   1016   };
   1017 
   1018   var SafeRemoveArrayHoles = function SafeRemoveArrayHoles(obj) {
   1019     // Copy defined elements from the end to fill in all holes and undefineds
   1020     // in the beginning of the array.  Write undefineds and holes at the end
   1021     // after loop is finished.
   1022     var first_undefined = 0;
   1023     var last_defined = length - 1;
   1024     var num_holes = 0;
   1025     while (first_undefined < last_defined) {
   1026       // Find first undefined element.
   1027       while (first_undefined < last_defined &&
   1028              !IS_UNDEFINED(obj[first_undefined])) {
   1029         first_undefined++;
   1030       }
   1031       // Maintain the invariant num_holes = the number of holes in the original
   1032       // array with indices <= first_undefined or > last_defined.
   1033       if (!HAS_OWN_PROPERTY(obj, first_undefined)) {
   1034         num_holes++;
   1035       }
   1036 
   1037       // Find last defined element.
   1038       while (first_undefined < last_defined &&
   1039              IS_UNDEFINED(obj[last_defined])) {
   1040         if (!HAS_OWN_PROPERTY(obj, last_defined)) {
   1041           num_holes++;
   1042         }
   1043         last_defined--;
   1044       }
   1045       if (first_undefined < last_defined) {
   1046         // Fill in hole or undefined.
   1047         obj[first_undefined] = obj[last_defined];
   1048         obj[last_defined] = UNDEFINED;
   1049       }
   1050     }
   1051     // If there were any undefineds in the entire array, first_undefined
   1052     // points to one past the last defined element.  Make this true if
   1053     // there were no undefineds, as well, so that first_undefined == number
   1054     // of defined elements.
   1055     if (!IS_UNDEFINED(obj[first_undefined])) first_undefined++;
   1056     // Fill in the undefineds and the holes.  There may be a hole where
   1057     // an undefined should be and vice versa.
   1058     var i;
   1059     for (i = first_undefined; i < length - num_holes; i++) {
   1060       obj[i] = UNDEFINED;
   1061     }
   1062     for (i = length - num_holes; i < length; i++) {
   1063       // For compatability with Webkit, do not expose elements in the prototype.
   1064       if (i in %GetPrototype(obj)) {
   1065         obj[i] = UNDEFINED;
   1066       } else {
   1067         delete obj[i];
   1068       }
   1069     }
   1070 
   1071     // Return the number of defined elements.
   1072     return first_undefined;
   1073   };
   1074 
   1075   var length = TO_UINT32(this.length);
   1076   if (length < 2) return this;
   1077 
   1078   var is_array = IS_ARRAY(this);
   1079   var max_prototype_element;
   1080   if (!is_array) {
   1081     // For compatibility with JSC, we also sort elements inherited from
   1082     // the prototype chain on non-Array objects.
   1083     // We do this by copying them to this object and sorting only
   1084     // own elements. This is not very efficient, but sorting with
   1085     // inherited elements happens very, very rarely, if at all.
   1086     // The specification allows "implementation dependent" behavior
   1087     // if an element on the prototype chain has an element that
   1088     // might interact with sorting.
   1089     max_prototype_element = CopyFromPrototype(this, length);
   1090   }
   1091 
   1092   // %RemoveArrayHoles returns -1 if fast removal is not supported.
   1093   var num_non_undefined = %RemoveArrayHoles(this, length);
   1094 
   1095   if (num_non_undefined == -1) {
   1096     // The array is observed, or there were indexed accessors in the array.
   1097     // Move array holes and undefineds to the end using a Javascript function
   1098     // that is safe in the presence of accessors and is observable.
   1099     num_non_undefined = SafeRemoveArrayHoles(this);
   1100   }
   1101 
   1102   QuickSort(this, 0, num_non_undefined);
   1103 
   1104   if (!is_array && (num_non_undefined + 1 < max_prototype_element)) {
   1105     // For compatibility with JSC, we shadow any elements in the prototype
   1106     // chain that has become exposed by sort moving a hole to its position.
   1107     ShadowPrototypeElements(this, num_non_undefined, max_prototype_element);
   1108   }
   1109 
   1110   return this;
   1111 }
   1112 
   1113 
   1114 // The following functions cannot be made efficient on sparse arrays while
   1115 // preserving the semantics, since the calls to the receiver function can add
   1116 // or delete elements from the array.
   1117 function ArrayFilter(f, receiver) {
   1118   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.filter");
   1119 
   1120   // Pull out the length so that modifications to the length in the
   1121   // loop will not affect the looping and side effects are visible.
   1122   var array = ToObject(this);
   1123   var length = ToUint32(array.length);
   1124 
   1125   if (!IS_SPEC_FUNCTION(f)) {
   1126     throw MakeTypeError('called_non_callable', [ f ]);
   1127   }
   1128   if (IS_NULL_OR_UNDEFINED(receiver)) {
   1129     receiver = %GetDefaultReceiver(f) || receiver;
   1130   } else if (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(f)) {
   1131     receiver = ToObject(receiver);
   1132   }
   1133 
   1134   var result = new $Array();
   1135   var accumulator = new InternalArray();
   1136   var accumulator_length = 0;
   1137   var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
   1138   for (var i = 0; i < length; i++) {
   1139     if (i in array) {
   1140       var element = array[i];
   1141       // Prepare break slots for debugger step in.
   1142       if (stepping) %DebugPrepareStepInIfStepping(f);
   1143       if (%_CallFunction(receiver, element, i, array, f)) {
   1144         accumulator[accumulator_length++] = element;
   1145       }
   1146     }
   1147   }
   1148   %MoveArrayContents(accumulator, result);
   1149   return result;
   1150 }
   1151 
   1152 
   1153 function ArrayForEach(f, receiver) {
   1154   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.forEach");
   1155 
   1156   // Pull out the length so that modifications to the length in the
   1157   // loop will not affect the looping and side effects are visible.
   1158   var array = ToObject(this);
   1159   var length = TO_UINT32(array.length);
   1160 
   1161   if (!IS_SPEC_FUNCTION(f)) {
   1162     throw MakeTypeError('called_non_callable', [ f ]);
   1163   }
   1164   if (IS_NULL_OR_UNDEFINED(receiver)) {
   1165     receiver = %GetDefaultReceiver(f) || receiver;
   1166   } else if (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(f)) {
   1167     receiver = ToObject(receiver);
   1168   }
   1169 
   1170   var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
   1171   for (var i = 0; i < length; i++) {
   1172     if (i in array) {
   1173       var element = array[i];
   1174       // Prepare break slots for debugger step in.
   1175       if (stepping) %DebugPrepareStepInIfStepping(f);
   1176       %_CallFunction(receiver, element, i, array, f);
   1177     }
   1178   }
   1179 }
   1180 
   1181 
   1182 // Executes the function once for each element present in the
   1183 // array until it finds one where callback returns true.
   1184 function ArraySome(f, receiver) {
   1185   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.some");
   1186 
   1187   // Pull out the length so that modifications to the length in the
   1188   // loop will not affect the looping and side effects are visible.
   1189   var array = ToObject(this);
   1190   var length = TO_UINT32(array.length);
   1191 
   1192   if (!IS_SPEC_FUNCTION(f)) {
   1193     throw MakeTypeError('called_non_callable', [ f ]);
   1194   }
   1195   if (IS_NULL_OR_UNDEFINED(receiver)) {
   1196     receiver = %GetDefaultReceiver(f) || receiver;
   1197   } else if (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(f)) {
   1198     receiver = ToObject(receiver);
   1199   }
   1200 
   1201   var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
   1202   for (var i = 0; i < length; i++) {
   1203     if (i in array) {
   1204       var element = array[i];
   1205       // Prepare break slots for debugger step in.
   1206       if (stepping) %DebugPrepareStepInIfStepping(f);
   1207       if (%_CallFunction(receiver, element, i, array, f)) return true;
   1208     }
   1209   }
   1210   return false;
   1211 }
   1212 
   1213 
   1214 function ArrayEvery(f, receiver) {
   1215   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.every");
   1216 
   1217   // Pull out the length so that modifications to the length in the
   1218   // loop will not affect the looping and side effects are visible.
   1219   var array = ToObject(this);
   1220   var length = TO_UINT32(array.length);
   1221 
   1222   if (!IS_SPEC_FUNCTION(f)) {
   1223     throw MakeTypeError('called_non_callable', [ f ]);
   1224   }
   1225   if (IS_NULL_OR_UNDEFINED(receiver)) {
   1226     receiver = %GetDefaultReceiver(f) || receiver;
   1227   } else if (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(f)) {
   1228     receiver = ToObject(receiver);
   1229   }
   1230 
   1231   var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
   1232   for (var i = 0; i < length; i++) {
   1233     if (i in array) {
   1234       var element = array[i];
   1235       // Prepare break slots for debugger step in.
   1236       if (stepping) %DebugPrepareStepInIfStepping(f);
   1237       if (!%_CallFunction(receiver, element, i, array, f)) return false;
   1238     }
   1239   }
   1240   return true;
   1241 }
   1242 
   1243 function ArrayMap(f, receiver) {
   1244   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map");
   1245 
   1246   // Pull out the length so that modifications to the length in the
   1247   // loop will not affect the looping and side effects are visible.
   1248   var array = ToObject(this);
   1249   var length = TO_UINT32(array.length);
   1250 
   1251   if (!IS_SPEC_FUNCTION(f)) {
   1252     throw MakeTypeError('called_non_callable', [ f ]);
   1253   }
   1254   if (IS_NULL_OR_UNDEFINED(receiver)) {
   1255     receiver = %GetDefaultReceiver(f) || receiver;
   1256   } else if (!IS_SPEC_OBJECT(receiver) && %IsSloppyModeFunction(f)) {
   1257     receiver = ToObject(receiver);
   1258   }
   1259 
   1260   var result = new $Array();
   1261   var accumulator = new InternalArray(length);
   1262   var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
   1263   for (var i = 0; i < length; i++) {
   1264     if (i in array) {
   1265       var element = array[i];
   1266       // Prepare break slots for debugger step in.
   1267       if (stepping) %DebugPrepareStepInIfStepping(f);
   1268       accumulator[i] = %_CallFunction(receiver, element, i, array, f);
   1269     }
   1270   }
   1271   %MoveArrayContents(accumulator, result);
   1272   return result;
   1273 }
   1274 
   1275 
   1276 function ArrayIndexOf(element, index) {
   1277   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.indexOf");
   1278 
   1279   var length = TO_UINT32(this.length);
   1280   if (length == 0) return -1;
   1281   if (IS_UNDEFINED(index)) {
   1282     index = 0;
   1283   } else {
   1284     index = TO_INTEGER(index);
   1285     // If index is negative, index from the end of the array.
   1286     if (index < 0) {
   1287       index = length + index;
   1288       // If index is still negative, search the entire array.
   1289       if (index < 0) index = 0;
   1290     }
   1291   }
   1292   var min = index;
   1293   var max = length;
   1294   if (UseSparseVariant(this, length, IS_ARRAY(this), max - min)) {
   1295     %NormalizeElements(this);
   1296     var indices = %GetArrayKeys(this, length);
   1297     if (IS_NUMBER(indices)) {
   1298       // It's an interval.
   1299       max = indices;  // Capped by length already.
   1300       // Fall through to loop below.
   1301     } else {
   1302       if (indices.length == 0) return -1;
   1303       // Get all the keys in sorted order.
   1304       var sortedKeys = GetSortedArrayKeys(this, indices);
   1305       var n = sortedKeys.length;
   1306       var i = 0;
   1307       while (i < n && sortedKeys[i] < index) i++;
   1308       while (i < n) {
   1309         var key = sortedKeys[i];
   1310         if (!IS_UNDEFINED(key) && this[key] === element) return key;
   1311         i++;
   1312       }
   1313       return -1;
   1314     }
   1315   }
   1316   // Lookup through the array.
   1317   if (!IS_UNDEFINED(element)) {
   1318     for (var i = min; i < max; i++) {
   1319       if (this[i] === element) return i;
   1320     }
   1321     return -1;
   1322   }
   1323   // Lookup through the array.
   1324   for (var i = min; i < max; i++) {
   1325     if (IS_UNDEFINED(this[i]) && i in this) {
   1326       return i;
   1327     }
   1328   }
   1329   return -1;
   1330 }
   1331 
   1332 
   1333 function ArrayLastIndexOf(element, index) {
   1334   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.lastIndexOf");
   1335 
   1336   var length = TO_UINT32(this.length);
   1337   if (length == 0) return -1;
   1338   if (%_ArgumentsLength() < 2) {
   1339     index = length - 1;
   1340   } else {
   1341     index = TO_INTEGER(index);
   1342     // If index is negative, index from end of the array.
   1343     if (index < 0) index += length;
   1344     // If index is still negative, do not search the array.
   1345     if (index < 0) return -1;
   1346     else if (index >= length) index = length - 1;
   1347   }
   1348   var min = 0;
   1349   var max = index;
   1350   if (UseSparseVariant(this, length, IS_ARRAY(this), index)) {
   1351     %NormalizeElements(this);
   1352     var indices = %GetArrayKeys(this, index + 1);
   1353     if (IS_NUMBER(indices)) {
   1354       // It's an interval.
   1355       max = indices;  // Capped by index already.
   1356       // Fall through to loop below.
   1357     } else {
   1358       if (indices.length == 0) return -1;
   1359       // Get all the keys in sorted order.
   1360       var sortedKeys = GetSortedArrayKeys(this, indices);
   1361       var i = sortedKeys.length - 1;
   1362       while (i >= 0) {
   1363         var key = sortedKeys[i];
   1364         if (!IS_UNDEFINED(key) && this[key] === element) return key;
   1365         i--;
   1366       }
   1367       return -1;
   1368     }
   1369   }
   1370   // Lookup through the array.
   1371   if (!IS_UNDEFINED(element)) {
   1372     for (var i = max; i >= min; i--) {
   1373       if (this[i] === element) return i;
   1374     }
   1375     return -1;
   1376   }
   1377   for (var i = max; i >= min; i--) {
   1378     if (IS_UNDEFINED(this[i]) && i in this) {
   1379       return i;
   1380     }
   1381   }
   1382   return -1;
   1383 }
   1384 
   1385 
   1386 function ArrayReduce(callback, current) {
   1387   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduce");
   1388 
   1389   // Pull out the length so that modifications to the length in the
   1390   // loop will not affect the looping and side effects are visible.
   1391   var array = ToObject(this);
   1392   var length = ToUint32(array.length);
   1393 
   1394   if (!IS_SPEC_FUNCTION(callback)) {
   1395     throw MakeTypeError('called_non_callable', [callback]);
   1396   }
   1397 
   1398   var i = 0;
   1399   find_initial: if (%_ArgumentsLength() < 2) {
   1400     for (; i < length; i++) {
   1401       current = array[i];
   1402       if (!IS_UNDEFINED(current) || i in array) {
   1403         i++;
   1404         break find_initial;
   1405       }
   1406     }
   1407     throw MakeTypeError('reduce_no_initial', []);
   1408   }
   1409 
   1410   var receiver = %GetDefaultReceiver(callback);
   1411   var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(callback);
   1412   for (; i < length; i++) {
   1413     if (i in array) {
   1414       var element = array[i];
   1415       // Prepare break slots for debugger step in.
   1416       if (stepping) %DebugPrepareStepInIfStepping(callback);
   1417       current = %_CallFunction(receiver, current, element, i, array, callback);
   1418     }
   1419   }
   1420   return current;
   1421 }
   1422 
   1423 function ArrayReduceRight(callback, current) {
   1424   CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduceRight");
   1425 
   1426   // Pull out the length so that side effects are visible before the
   1427   // callback function is checked.
   1428   var array = ToObject(this);
   1429   var length = ToUint32(array.length);
   1430 
   1431   if (!IS_SPEC_FUNCTION(callback)) {
   1432     throw MakeTypeError('called_non_callable', [callback]);
   1433   }
   1434 
   1435   var i = length - 1;
   1436   find_initial: if (%_ArgumentsLength() < 2) {
   1437     for (; i >= 0; i--) {
   1438       current = array[i];
   1439       if (!IS_UNDEFINED(current) || i in array) {
   1440         i--;
   1441         break find_initial;
   1442       }
   1443     }
   1444     throw MakeTypeError('reduce_no_initial', []);
   1445   }
   1446 
   1447   var receiver = %GetDefaultReceiver(callback);
   1448   var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(callback);
   1449   for (; i >= 0; i--) {
   1450     if (i in array) {
   1451       var element = array[i];
   1452       // Prepare break slots for debugger step in.
   1453       if (stepping) %DebugPrepareStepInIfStepping(callback);
   1454       current = %_CallFunction(receiver, current, element, i, array, callback);
   1455     }
   1456   }
   1457   return current;
   1458 }
   1459 
   1460 // ES5, 15.4.3.2
   1461 function ArrayIsArray(obj) {
   1462   return IS_ARRAY(obj);
   1463 }
   1464 
   1465 
   1466 // -------------------------------------------------------------------
   1467 
   1468 function SetUpArray() {
   1469   %CheckIsBootstrapping();
   1470 
   1471   // Set up non-enumerable constructor property on the Array.prototype
   1472   // object.
   1473   %AddNamedProperty($Array.prototype, "constructor", $Array, DONT_ENUM);
   1474 
   1475   // Set up unscopable properties on the Array.prototype object.
   1476   var unscopables = {
   1477     __proto__: null,
   1478     copyWithin: true,
   1479     entries: true,
   1480     fill: true,
   1481     find: true,
   1482     findIndex: true,
   1483     keys: true,
   1484   };
   1485   %AddNamedProperty($Array.prototype, symbolUnscopables, unscopables,
   1486       DONT_ENUM | READ_ONLY);
   1487 
   1488   // Set up non-enumerable functions on the Array object.
   1489   InstallFunctions($Array, DONT_ENUM, $Array(
   1490     "isArray", ArrayIsArray
   1491   ));
   1492 
   1493   var specialFunctions = %SpecialArrayFunctions();
   1494 
   1495   var getFunction = function(name, jsBuiltin, len) {
   1496     var f = jsBuiltin;
   1497     if (specialFunctions.hasOwnProperty(name)) {
   1498       f = specialFunctions[name];
   1499     }
   1500     if (!IS_UNDEFINED(len)) {
   1501       %FunctionSetLength(f, len);
   1502     }
   1503     return f;
   1504   };
   1505 
   1506   // Set up non-enumerable functions of the Array.prototype object and
   1507   // set their names.
   1508   // Manipulate the length of some of the functions to meet
   1509   // expectations set by ECMA-262 or Mozilla.
   1510   InstallFunctions($Array.prototype, DONT_ENUM, $Array(
   1511     "toString", getFunction("toString", ArrayToString),
   1512     "toLocaleString", getFunction("toLocaleString", ArrayToLocaleString),
   1513     "join", getFunction("join", ArrayJoin),
   1514     "pop", getFunction("pop", ArrayPop),
   1515     "push", getFunction("push", ArrayPush, 1),
   1516     "concat", getFunction("concat", ArrayConcatJS, 1),
   1517     "reverse", getFunction("reverse", ArrayReverse),
   1518     "shift", getFunction("shift", ArrayShift),
   1519     "unshift", getFunction("unshift", ArrayUnshift, 1),
   1520     "slice", getFunction("slice", ArraySlice, 2),
   1521     "splice", getFunction("splice", ArraySplice, 2),
   1522     "sort", getFunction("sort", ArraySort),
   1523     "filter", getFunction("filter", ArrayFilter, 1),
   1524     "forEach", getFunction("forEach", ArrayForEach, 1),
   1525     "some", getFunction("some", ArraySome, 1),
   1526     "every", getFunction("every", ArrayEvery, 1),
   1527     "map", getFunction("map", ArrayMap, 1),
   1528     "indexOf", getFunction("indexOf", ArrayIndexOf, 1),
   1529     "lastIndexOf", getFunction("lastIndexOf", ArrayLastIndexOf, 1),
   1530     "reduce", getFunction("reduce", ArrayReduce, 1),
   1531     "reduceRight", getFunction("reduceRight", ArrayReduceRight, 1)
   1532   ));
   1533 
   1534   %FinishArrayPrototypeSetup($Array.prototype);
   1535 
   1536   // The internal Array prototype doesn't need to be fancy, since it's never
   1537   // exposed to user code.
   1538   // Adding only the functions that are actually used.
   1539   SetUpLockedPrototype(InternalArray, $Array(), $Array(
   1540     "concat", getFunction("concat", ArrayConcatJS),
   1541     "indexOf", getFunction("indexOf", ArrayIndexOf),
   1542     "join", getFunction("join", ArrayJoin),
   1543     "pop", getFunction("pop", ArrayPop),
   1544     "push", getFunction("push", ArrayPush),
   1545     "splice", getFunction("splice", ArraySplice)
   1546   ));
   1547 
   1548   SetUpLockedPrototype(InternalPackedArray, $Array(), $Array(
   1549     "join", getFunction("join", ArrayJoin),
   1550     "pop", getFunction("pop", ArrayPop),
   1551     "push", getFunction("push", ArrayPush)
   1552   ));
   1553 }
   1554 
   1555 SetUpArray();
   1556