Home | History | Annotate | Download | only in src
      1 // Copyright 2010 the V8 project authors. All rights reserved.
      2 // Redistribution and use in source and binary forms, with or without
      3 // modification, are permitted provided that the following conditions are
      4 // met:
      5 //
      6 //     * Redistributions of source code must retain the above copyright
      7 //       notice, this list of conditions and the following disclaimer.
      8 //     * Redistributions in binary form must reproduce the above
      9 //       copyright notice, this list of conditions and the following
     10 //       disclaimer in the documentation and/or other materials provided
     11 //       with the distribution.
     12 //     * Neither the name of Google Inc. nor the names of its
     13 //       contributors may be used to endorse or promote products derived
     14 //       from this software without specific prior written permission.
     15 //
     16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27 
     28 // This file relies on the fact that the following declarations have been made
     29 // in runtime.js:
     30 // const $Array = global.Array;
     31 
     32 // -------------------------------------------------------------------
     33 
     34 // Global list of arrays visited during toString, toLocaleString and
     35 // join invocations.
     36 var visited_arrays = new InternalArray();
     37 
     38 
     39 // Gets a sorted array of array keys.  Useful for operations on sparse
     40 // arrays.  Dupes have not been removed.
     41 function GetSortedArrayKeys(array, intervals) {
     42   var length = intervals.length;
     43   var keys = [];
     44   for (var k = 0; k < length; k++) {
     45     var key = intervals[k];
     46     if (key < 0) {
     47       var j = -1 - key;
     48       var limit = j + intervals[++k];
     49       for (; j < limit; j++) {
     50         var e = array[j];
     51         if (!IS_UNDEFINED(e) || j in array) {
     52           keys.push(j);
     53         }
     54       }
     55     } else {
     56       // The case where key is undefined also ends here.
     57       if (!IS_UNDEFINED(key)) {
     58         var e = array[key];
     59         if (!IS_UNDEFINED(e) || key in array) {
     60           keys.push(key);
     61         }
     62       }
     63     }
     64   }
     65   keys.sort(function(a, b) { return a - b; });
     66   return keys;
     67 }
     68 
     69 
     70 // Optimized for sparse arrays if separator is ''.
     71 function SparseJoin(array, len, convert) {
     72   var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
     73   var last_key = -1;
     74   var keys_length = keys.length;
     75 
     76   var elements = new InternalArray(keys_length);
     77   var elements_length = 0;
     78 
     79   for (var i = 0; i < keys_length; i++) {
     80     var key = keys[i];
     81     if (key != last_key) {
     82       var e = array[key];
     83       if (!IS_STRING(e)) e = convert(e);
     84       elements[elements_length++] = e;
     85       last_key = key;
     86     }
     87   }
     88   return %StringBuilderConcat(elements, elements_length, '');
     89 }
     90 
     91 
     92 function UseSparseVariant(object, length, is_array) {
     93    return is_array &&
     94        length > 1000 &&
     95        (!%_IsSmi(length) ||
     96         %EstimateNumberOfElements(object) < (length >> 2));
     97 }
     98 
     99 
    100 function Join(array, length, separator, convert) {
    101   if (length == 0) return '';
    102 
    103   var is_array = IS_ARRAY(array);
    104 
    105   if (is_array) {
    106     // If the array is cyclic, return the empty string for already
    107     // visited arrays.
    108     if (!%PushIfAbsent(visited_arrays, array)) return '';
    109   }
    110 
    111   // Attempt to convert the elements.
    112   try {
    113     if (UseSparseVariant(array, length, is_array) && (separator.length == 0)) {
    114       return SparseJoin(array, length, convert);
    115     }
    116 
    117     // Fast case for one-element arrays.
    118     if (length == 1) {
    119       var e = array[0];
    120       if (IS_STRING(e)) return e;
    121       return convert(e);
    122     }
    123 
    124     // Construct an array for the elements.
    125     var elements = new InternalArray(length);
    126 
    127     // We pull the empty separator check outside the loop for speed!
    128     if (separator.length == 0) {
    129       var elements_length = 0;
    130       for (var i = 0; i < length; i++) {
    131         var e = array[i];
    132         if (!IS_UNDEFINED(e)) {
    133           if (!IS_STRING(e)) e = convert(e);
    134           elements[elements_length++] = e;
    135         }
    136       }
    137       elements.length = elements_length;
    138       var result = %_FastAsciiArrayJoin(elements, '');
    139       if (!IS_UNDEFINED(result)) return result;
    140       return %StringBuilderConcat(elements, elements_length, '');
    141     }
    142     // Non-empty separator case.
    143     // If the first element is a number then use the heuristic that the
    144     // remaining elements are also likely to be numbers.
    145     if (!IS_NUMBER(array[0])) {
    146       for (var i = 0; i < length; i++) {
    147         var e = array[i];
    148         if (!IS_STRING(e)) e = convert(e);
    149         elements[i] = e;
    150       }
    151     } else {
    152       for (var i = 0; i < length; i++) {
    153         var e = array[i];
    154         if (IS_NUMBER(e)) elements[i] = %_NumberToString(e);
    155         else {
    156           if (!IS_STRING(e)) e = convert(e);
    157           elements[i] = e;
    158         }
    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     // Make sure to remove the last element of the visited array no
    167     // matter what happens.
    168     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   if (IS_NUMBER(x)) return %_NumberToString(x);
    176   if (IS_BOOLEAN(x)) return x ? 'true' : 'false';
    177   return (IS_NULL_OR_UNDEFINED(x)) ? '' : %ToString(%DefaultString(x));
    178 }
    179 
    180 
    181 function ConvertToLocaleString(e) {
    182   if (e == null) {
    183     return '';
    184   } else {
    185     // e_obj's toLocaleString might be overwritten, check if it is a function.
    186     // Call ToString if toLocaleString is not a function.
    187     // See issue 877615.
    188     var e_obj = ToObject(e);
    189     if (IS_FUNCTION(e_obj.toLocaleString))
    190       return ToString(e_obj.toLocaleString());
    191     else
    192       return ToString(e);
    193   }
    194 }
    195 
    196 
    197 // This function implements the optimized splice implementation that can use
    198 // special array operations to handle sparse arrays in a sensible fashion.
    199 function SmartSlice(array, start_i, del_count, len, deleted_elements) {
    200   // Move deleted elements to a new array (the return value from splice).
    201   // Intervals array can contain keys and intervals.  See comment in Concat.
    202   var intervals = %GetArrayKeys(array, start_i + del_count);
    203   var length = intervals.length;
    204   for (var k = 0; k < length; k++) {
    205     var key = intervals[k];
    206     if (key < 0) {
    207       var j = -1 - key;
    208       var interval_limit = j + intervals[++k];
    209       if (j < start_i) {
    210         j = start_i;
    211       }
    212       for (; j < interval_limit; j++) {
    213         // ECMA-262 15.4.4.12 line 10.  The spec could also be
    214         // interpreted such that %HasLocalProperty would be the
    215         // appropriate test.  We follow KJS in consulting the
    216         // prototype.
    217         var current = array[j];
    218         if (!IS_UNDEFINED(current) || j in array) {
    219           deleted_elements[j - start_i] = current;
    220         }
    221       }
    222     } else {
    223       if (!IS_UNDEFINED(key)) {
    224         if (key >= start_i) {
    225           // ECMA-262 15.4.4.12 line 10.  The spec could also be
    226           // interpreted such that %HasLocalProperty would be the
    227           // appropriate test.  We follow KJS in consulting the
    228           // prototype.
    229           var current = array[key];
    230           if (!IS_UNDEFINED(current) || key in array) {
    231             deleted_elements[key - start_i] = current;
    232           }
    233         }
    234       }
    235     }
    236   }
    237 }
    238 
    239 
    240 // This function implements the optimized splice implementation that can use
    241 // special array operations to handle sparse arrays in a sensible fashion.
    242 function SmartMove(array, start_i, del_count, len, num_additional_args) {
    243   // Move data to new array.
    244   var new_array = new InternalArray(len - del_count + num_additional_args);
    245   var intervals = %GetArrayKeys(array, len);
    246   var length = intervals.length;
    247   for (var k = 0; k < length; k++) {
    248     var key = intervals[k];
    249     if (key < 0) {
    250       var j = -1 - key;
    251       var interval_limit = j + intervals[++k];
    252       while (j < start_i && j < interval_limit) {
    253         // The spec could also be interpreted such that
    254         // %HasLocalProperty would be the appropriate test.  We follow
    255         // KJS in consulting the prototype.
    256         var current = array[j];
    257         if (!IS_UNDEFINED(current) || j in array) {
    258           new_array[j] = current;
    259         }
    260         j++;
    261       }
    262       j = start_i + del_count;
    263       while (j < interval_limit) {
    264         // ECMA-262 15.4.4.12 lines 24 and 41.  The spec could also be
    265         // interpreted such that %HasLocalProperty would be the
    266         // appropriate test.  We follow KJS in consulting the
    267         // prototype.
    268         var current = array[j];
    269         if (!IS_UNDEFINED(current) || j in array) {
    270           new_array[j - del_count + num_additional_args] = current;
    271         }
    272         j++;
    273       }
    274     } else {
    275       if (!IS_UNDEFINED(key)) {
    276         if (key < start_i) {
    277           // The spec could also be interpreted such that
    278           // %HasLocalProperty would be the appropriate test.  We follow
    279           // KJS in consulting the prototype.
    280           var current = array[key];
    281           if (!IS_UNDEFINED(current) || key in array) {
    282             new_array[key] = current;
    283           }
    284         } else if (key >= start_i + del_count) {
    285           // ECMA-262 15.4.4.12 lines 24 and 41.  The spec could also
    286           // be interpreted such that %HasLocalProperty would be the
    287           // appropriate test.  We follow KJS in consulting the
    288           // prototype.
    289           var current = array[key];
    290           if (!IS_UNDEFINED(current) || key in array) {
    291             new_array[key - del_count + num_additional_args] = current;
    292           }
    293         }
    294       }
    295     }
    296   }
    297   // Move contents of new_array into this array
    298   %MoveArrayContents(new_array, array);
    299 }
    300 
    301 
    302 // This is part of the old simple-minded splice.  We are using it either
    303 // because the receiver is not an array (so we have no choice) or because we
    304 // know we are not deleting or moving a lot of elements.
    305 function SimpleSlice(array, start_i, del_count, len, deleted_elements) {
    306   for (var i = 0; i < del_count; i++) {
    307     var index = start_i + i;
    308     // The spec could also be interpreted such that %HasLocalProperty
    309     // would be the appropriate test.  We follow KJS in consulting the
    310     // prototype.
    311     var current = array[index];
    312     if (!IS_UNDEFINED(current) || index in array)
    313       deleted_elements[i] = current;
    314   }
    315 }
    316 
    317 
    318 function SimpleMove(array, start_i, del_count, len, num_additional_args) {
    319   if (num_additional_args !== del_count) {
    320     // Move the existing elements after the elements to be deleted
    321     // to the right position in the resulting array.
    322     if (num_additional_args > del_count) {
    323       for (var i = len - del_count; i > start_i; i--) {
    324         var from_index = i + del_count - 1;
    325         var to_index = i + num_additional_args - 1;
    326         // The spec could also be interpreted such that
    327         // %HasLocalProperty would be the appropriate test.  We follow
    328         // KJS in consulting the prototype.
    329         var current = array[from_index];
    330         if (!IS_UNDEFINED(current) || from_index in array) {
    331           array[to_index] = current;
    332         } else {
    333           delete array[to_index];
    334         }
    335       }
    336     } else {
    337       for (var i = start_i; i < len - del_count; i++) {
    338         var from_index = i + del_count;
    339         var to_index = i + num_additional_args;
    340         // The spec could also be interpreted such that
    341         // %HasLocalProperty would be the appropriate test.  We follow
    342         // KJS in consulting the prototype.
    343         var current = array[from_index];
    344         if (!IS_UNDEFINED(current) || from_index in array) {
    345           array[to_index] = current;
    346         } else {
    347           delete array[to_index];
    348         }
    349       }
    350       for (var i = len; i > len - del_count + num_additional_args; i--) {
    351         delete array[i - 1];
    352       }
    353     }
    354   }
    355 }
    356 
    357 
    358 // -------------------------------------------------------------------
    359 
    360 
    361 function ArrayToString() {
    362   if (!IS_ARRAY(this)) {
    363     throw new $TypeError('Array.prototype.toString is not generic');
    364   }
    365   return Join(this, this.length, ',', ConvertToString);
    366 }
    367 
    368 
    369 function ArrayToLocaleString() {
    370   if (!IS_ARRAY(this)) {
    371     throw new $TypeError('Array.prototype.toString is not generic');
    372   }
    373   return Join(this, this.length, ',', ConvertToLocaleString);
    374 }
    375 
    376 
    377 function ArrayJoin(separator) {
    378   if (IS_UNDEFINED(separator)) {
    379     separator = ',';
    380   } else if (!IS_STRING(separator)) {
    381     separator = NonStringToString(separator);
    382   }
    383 
    384   var result = %_FastAsciiArrayJoin(this, separator);
    385   if (!IS_UNDEFINED(result)) return result;
    386 
    387   return Join(this, TO_UINT32(this.length), separator, ConvertToString);
    388 }
    389 
    390 
    391 // Removes the last element from the array and returns it. See
    392 // ECMA-262, section 15.4.4.6.
    393 function ArrayPop() {
    394   var n = TO_UINT32(this.length);
    395   if (n == 0) {
    396     this.length = n;
    397     return;
    398   }
    399   n--;
    400   var value = this[n];
    401   this.length = n;
    402   delete this[n];
    403   return value;
    404 }
    405 
    406 
    407 // Appends the arguments to the end of the array and returns the new
    408 // length of the array. See ECMA-262, section 15.4.4.7.
    409 function ArrayPush() {
    410   var n = TO_UINT32(this.length);
    411   var m = %_ArgumentsLength();
    412   for (var i = 0; i < m; i++) {
    413     this[i+n] = %_Arguments(i);
    414   }
    415   this.length = n + m;
    416   return this.length;
    417 }
    418 
    419 
    420 function ArrayConcat(arg1) {  // length == 1
    421   var arg_count = %_ArgumentsLength();
    422   var arrays = new InternalArray(1 + arg_count);
    423   arrays[0] = this;
    424   for (var i = 0; i < arg_count; i++) {
    425     arrays[i + 1] = %_Arguments(i);
    426   }
    427 
    428   return %ArrayConcat(arrays);
    429 }
    430 
    431 
    432 // For implementing reverse() on large, sparse arrays.
    433 function SparseReverse(array, len) {
    434   var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
    435   var high_counter = keys.length - 1;
    436   var low_counter = 0;
    437   while (low_counter <= high_counter) {
    438     var i = keys[low_counter];
    439     var j = keys[high_counter];
    440 
    441     var j_complement = len - j - 1;
    442     var low, high;
    443 
    444     if (j_complement <= i) {
    445       high = j;
    446       while (keys[--high_counter] == j);
    447       low = j_complement;
    448     }
    449     if (j_complement >= i) {
    450       low = i;
    451       while (keys[++low_counter] == i);
    452       high = len - i - 1;
    453     }
    454 
    455     var current_i = array[low];
    456     if (!IS_UNDEFINED(current_i) || low in array) {
    457       var current_j = array[high];
    458       if (!IS_UNDEFINED(current_j) || high in array) {
    459         array[low] = current_j;
    460         array[high] = current_i;
    461       } else {
    462         array[high] = current_i;
    463         delete array[low];
    464       }
    465     } else {
    466       var current_j = array[high];
    467       if (!IS_UNDEFINED(current_j) || high in array) {
    468         array[low] = current_j;
    469         delete array[high];
    470       }
    471     }
    472   }
    473 }
    474 
    475 
    476 function ArrayReverse() {
    477   var j = TO_UINT32(this.length) - 1;
    478 
    479   if (UseSparseVariant(this, j, IS_ARRAY(this))) {
    480     SparseReverse(this, j+1);
    481     return this;
    482   }
    483 
    484   for (var i = 0; i < j; i++, j--) {
    485     var current_i = this[i];
    486     if (!IS_UNDEFINED(current_i) || i in this) {
    487       var current_j = this[j];
    488       if (!IS_UNDEFINED(current_j) || j in this) {
    489         this[i] = current_j;
    490         this[j] = current_i;
    491       } else {
    492         this[j] = current_i;
    493         delete this[i];
    494       }
    495     } else {
    496       var current_j = this[j];
    497       if (!IS_UNDEFINED(current_j) || j in this) {
    498         this[i] = current_j;
    499         delete this[j];
    500       }
    501     }
    502   }
    503   return this;
    504 }
    505 
    506 
    507 function ArrayShift() {
    508   var len = TO_UINT32(this.length);
    509 
    510   if (len === 0) {
    511     this.length = 0;
    512     return;
    513   }
    514 
    515   var first = this[0];
    516 
    517   if (IS_ARRAY(this))
    518     SmartMove(this, 0, 1, len, 0);
    519   else
    520     SimpleMove(this, 0, 1, len, 0);
    521 
    522   this.length = len - 1;
    523 
    524   return first;
    525 }
    526 
    527 
    528 function ArrayUnshift(arg1) {  // length == 1
    529   var len = TO_UINT32(this.length);
    530   var num_arguments = %_ArgumentsLength();
    531 
    532   if (IS_ARRAY(this))
    533     SmartMove(this, 0, 0, len, num_arguments);
    534   else
    535     SimpleMove(this, 0, 0, len, num_arguments);
    536 
    537   for (var i = 0; i < num_arguments; i++) {
    538     this[i] = %_Arguments(i);
    539   }
    540 
    541   this.length = len + num_arguments;
    542 
    543   return len + num_arguments;
    544 }
    545 
    546 
    547 function ArraySlice(start, end) {
    548   var len = TO_UINT32(this.length);
    549   var start_i = TO_INTEGER(start);
    550   var end_i = len;
    551 
    552   if (end !== void 0) end_i = TO_INTEGER(end);
    553 
    554   if (start_i < 0) {
    555     start_i += len;
    556     if (start_i < 0) start_i = 0;
    557   } else {
    558     if (start_i > len) start_i = len;
    559   }
    560 
    561   if (end_i < 0) {
    562     end_i += len;
    563     if (end_i < 0) end_i = 0;
    564   } else {
    565     if (end_i > len) end_i = len;
    566   }
    567 
    568   var result = [];
    569 
    570   if (end_i < start_i) return result;
    571 
    572   if (IS_ARRAY(this)) {
    573     SmartSlice(this, start_i, end_i - start_i, len, result);
    574   } else {
    575     SimpleSlice(this, start_i, end_i - start_i, len, result);
    576   }
    577 
    578   result.length = end_i - start_i;
    579 
    580   return result;
    581 }
    582 
    583 
    584 function ArraySplice(start, delete_count) {
    585   var num_arguments = %_ArgumentsLength();
    586 
    587   var len = TO_UINT32(this.length);
    588   var start_i = TO_INTEGER(start);
    589 
    590   if (start_i < 0) {
    591     start_i += len;
    592     if (start_i < 0) start_i = 0;
    593   } else {
    594     if (start_i > len) start_i = len;
    595   }
    596 
    597   // SpiderMonkey, TraceMonkey and JSC treat the case where no delete count is
    598   // given as a request to delete all the elements from the start.
    599   // And it differs from the case of undefined delete count.
    600   // This does not follow ECMA-262, but we do the same for
    601   // compatibility.
    602   var del_count = 0;
    603   if (num_arguments == 1) {
    604     del_count = len - start_i;
    605   } else {
    606     del_count = TO_INTEGER(delete_count);
    607     if (del_count < 0) del_count = 0;
    608     if (del_count > len - start_i) del_count = len - start_i;
    609   }
    610 
    611   var deleted_elements = [];
    612   deleted_elements.length = del_count;
    613 
    614   // Number of elements to add.
    615   var num_additional_args = 0;
    616   if (num_arguments > 2) {
    617     num_additional_args = num_arguments - 2;
    618   }
    619 
    620   var use_simple_splice = true;
    621 
    622   if (IS_ARRAY(this) && num_additional_args !== del_count) {
    623     // If we are only deleting/moving a few things near the end of the
    624     // array then the simple version is going to be faster, because it
    625     // doesn't touch most of the array.
    626     var estimated_non_hole_elements = %EstimateNumberOfElements(this);
    627     if (len > 20 && (estimated_non_hole_elements >> 2) < (len - start_i)) {
    628       use_simple_splice = false;
    629     }
    630   }
    631 
    632   if (use_simple_splice) {
    633     SimpleSlice(this, start_i, del_count, len, deleted_elements);
    634     SimpleMove(this, start_i, del_count, len, num_additional_args);
    635   } else {
    636     SmartSlice(this, start_i, del_count, len, deleted_elements);
    637     SmartMove(this, start_i, del_count, len, num_additional_args);
    638   }
    639 
    640   // Insert the arguments into the resulting array in
    641   // place of the deleted elements.
    642   var i = start_i;
    643   var arguments_index = 2;
    644   var arguments_length = %_ArgumentsLength();
    645   while (arguments_index < arguments_length) {
    646     this[i++] = %_Arguments(arguments_index++);
    647   }
    648   this.length = len - del_count + num_additional_args;
    649 
    650   // Return the deleted elements.
    651   return deleted_elements;
    652 }
    653 
    654 
    655 function ArraySort(comparefn) {
    656   // In-place QuickSort algorithm.
    657   // For short (length <= 22) arrays, insertion sort is used for efficiency.
    658 
    659   if (!IS_FUNCTION(comparefn)) {
    660     comparefn = function (x, y) {
    661       if (x === y) return 0;
    662       if (%_IsSmi(x) && %_IsSmi(y)) {
    663         return %SmiLexicographicCompare(x, y);
    664       }
    665       x = ToString(x);
    666       y = ToString(y);
    667       if (x == y) return 0;
    668       else return x < y ? -1 : 1;
    669     };
    670   }
    671   var global_receiver = %GetGlobalReceiver();
    672 
    673   function InsertionSort(a, from, to) {
    674     for (var i = from + 1; i < to; i++) {
    675       var element = a[i];
    676       for (var j = i - 1; j >= from; j--) {
    677         var tmp = a[j];
    678         var order = %_CallFunction(global_receiver, tmp, element, comparefn);
    679         if (order > 0) {
    680           a[j + 1] = tmp;
    681         } else {
    682           break;
    683         }
    684       }
    685       a[j + 1] = element;
    686     }
    687   }
    688 
    689   function QuickSort(a, from, to) {
    690     // Insertion sort is faster for short arrays.
    691     if (to - from <= 10) {
    692       InsertionSort(a, from, to);
    693       return;
    694     }
    695     // Find a pivot as the median of first, last and middle element.
    696     var v0 = a[from];
    697     var v1 = a[to - 1];
    698     var middle_index = from + ((to - from) >> 1);
    699     var v2 = a[middle_index];
    700     var c01 = %_CallFunction(global_receiver, v0, v1, comparefn);
    701     if (c01 > 0) {
    702       // v1 < v0, so swap them.
    703       var tmp = v0;
    704       v0 = v1;
    705       v1 = tmp;
    706     } // v0 <= v1.
    707     var c02 = %_CallFunction(global_receiver, v0, v2, comparefn);
    708     if (c02 >= 0) {
    709       // v2 <= v0 <= v1.
    710       var tmp = v0;
    711       v0 = v2;
    712       v2 = v1;
    713       v1 = tmp;
    714     } else {
    715       // v0 <= v1 && v0 < v2
    716       var c12 = %_CallFunction(global_receiver, v1, v2, comparefn);
    717       if (c12 > 0) {
    718         // v0 <= v2 < v1
    719         var tmp = v1;
    720         v1 = v2;
    721         v2 = tmp;
    722       }
    723     }
    724     // v0 <= v1 <= v2
    725     a[from] = v0;
    726     a[to - 1] = v2;
    727     var pivot = v1;
    728     var low_end = from + 1;   // Upper bound of elements lower than pivot.
    729     var high_start = to - 1;  // Lower bound of elements greater than pivot.
    730     a[middle_index] = a[low_end];
    731     a[low_end] = pivot;
    732 
    733     // From low_end to i are elements equal to pivot.
    734     // From i to high_start are elements that haven't been compared yet.
    735     partition: for (var i = low_end + 1; i < high_start; i++) {
    736       var element = a[i];
    737       var order = %_CallFunction(global_receiver, element, pivot, comparefn);
    738       if (order < 0) {
    739         %_SwapElements(a, i, low_end);
    740         low_end++;
    741       } else if (order > 0) {
    742         do {
    743           high_start--;
    744           if (high_start == i) break partition;
    745           var top_elem = a[high_start];
    746           order = %_CallFunction(global_receiver, top_elem, pivot, comparefn);
    747         } while (order > 0);
    748         %_SwapElements(a, i, high_start);
    749         if (order < 0) {
    750           %_SwapElements(a, i, low_end);
    751           low_end++;
    752         }
    753       }
    754     }
    755     QuickSort(a, from, low_end);
    756     QuickSort(a, high_start, to);
    757   }
    758 
    759   // Copy elements in the range 0..length from obj's prototype chain
    760   // to obj itself, if obj has holes. Return one more than the maximal index
    761   // of a prototype property.
    762   function CopyFromPrototype(obj, length) {
    763     var max = 0;
    764     for (var proto = obj.__proto__; proto; proto = proto.__proto__) {
    765       var indices = %GetArrayKeys(proto, length);
    766       if (indices.length > 0) {
    767         if (indices[0] == -1) {
    768           // It's an interval.
    769           var proto_length = indices[1];
    770           for (var i = 0; i < proto_length; i++) {
    771             if (!obj.hasOwnProperty(i) && proto.hasOwnProperty(i)) {
    772               obj[i] = proto[i];
    773               if (i >= max) { max = i + 1; }
    774             }
    775           }
    776         } else {
    777           for (var i = 0; i < indices.length; i++) {
    778             var index = indices[i];
    779             if (!IS_UNDEFINED(index) &&
    780                 !obj.hasOwnProperty(index) && proto.hasOwnProperty(index)) {
    781               obj[index] = proto[index];
    782               if (index >= max) { max = index + 1; }
    783             }
    784           }
    785         }
    786       }
    787     }
    788     return max;
    789   }
    790 
    791   // Set a value of "undefined" on all indices in the range from..to
    792   // where a prototype of obj has an element. I.e., shadow all prototype
    793   // elements in that range.
    794   function ShadowPrototypeElements(obj, from, to) {
    795     for (var proto = obj.__proto__; proto; proto = proto.__proto__) {
    796       var indices = %GetArrayKeys(proto, to);
    797       if (indices.length > 0) {
    798         if (indices[0] == -1) {
    799           // It's an interval.
    800           var proto_length = indices[1];
    801           for (var i = from; i < proto_length; i++) {
    802             if (proto.hasOwnProperty(i)) {
    803               obj[i] = void 0;
    804             }
    805           }
    806         } else {
    807           for (var i = 0; i < indices.length; i++) {
    808             var index = indices[i];
    809             if (!IS_UNDEFINED(index) && from <= index &&
    810                 proto.hasOwnProperty(index)) {
    811               obj[index] = void 0;
    812             }
    813           }
    814         }
    815       }
    816     }
    817   }
    818 
    819   function SafeRemoveArrayHoles(obj) {
    820     // Copy defined elements from the end to fill in all holes and undefineds
    821     // in the beginning of the array.  Write undefineds and holes at the end
    822     // after loop is finished.
    823     var first_undefined = 0;
    824     var last_defined = length - 1;
    825     var num_holes = 0;
    826     while (first_undefined < last_defined) {
    827       // Find first undefined element.
    828       while (first_undefined < last_defined &&
    829              !IS_UNDEFINED(obj[first_undefined])) {
    830         first_undefined++;
    831       }
    832       // Maintain the invariant num_holes = the number of holes in the original
    833       // array with indices <= first_undefined or > last_defined.
    834       if (!obj.hasOwnProperty(first_undefined)) {
    835         num_holes++;
    836       }
    837 
    838       // Find last defined element.
    839       while (first_undefined < last_defined &&
    840              IS_UNDEFINED(obj[last_defined])) {
    841         if (!obj.hasOwnProperty(last_defined)) {
    842           num_holes++;
    843         }
    844         last_defined--;
    845       }
    846       if (first_undefined < last_defined) {
    847         // Fill in hole or undefined.
    848         obj[first_undefined] = obj[last_defined];
    849         obj[last_defined] = void 0;
    850       }
    851     }
    852     // If there were any undefineds in the entire array, first_undefined
    853     // points to one past the last defined element.  Make this true if
    854     // there were no undefineds, as well, so that first_undefined == number
    855     // of defined elements.
    856     if (!IS_UNDEFINED(obj[first_undefined])) first_undefined++;
    857     // Fill in the undefineds and the holes.  There may be a hole where
    858     // an undefined should be and vice versa.
    859     var i;
    860     for (i = first_undefined; i < length - num_holes; i++) {
    861       obj[i] = void 0;
    862     }
    863     for (i = length - num_holes; i < length; i++) {
    864       // For compatability with Webkit, do not expose elements in the prototype.
    865       if (i in obj.__proto__) {
    866         obj[i] = void 0;
    867       } else {
    868         delete obj[i];
    869       }
    870     }
    871 
    872     // Return the number of defined elements.
    873     return first_undefined;
    874   }
    875 
    876   var length = TO_UINT32(this.length);
    877   if (length < 2) return this;
    878 
    879   var is_array = IS_ARRAY(this);
    880   var max_prototype_element;
    881   if (!is_array) {
    882     // For compatibility with JSC, we also sort elements inherited from
    883     // the prototype chain on non-Array objects.
    884     // We do this by copying them to this object and sorting only
    885     // local elements. This is not very efficient, but sorting with
    886     // inherited elements happens very, very rarely, if at all.
    887     // The specification allows "implementation dependent" behavior
    888     // if an element on the prototype chain has an element that
    889     // might interact with sorting.
    890     max_prototype_element = CopyFromPrototype(this, length);
    891   }
    892 
    893   var num_non_undefined = %RemoveArrayHoles(this, length);
    894   if (num_non_undefined == -1) {
    895     // There were indexed accessors in the array.  Move array holes and
    896     // undefineds to the end using a Javascript function that is safe
    897     // in the presence of accessors.
    898     num_non_undefined = SafeRemoveArrayHoles(this);
    899   }
    900 
    901   QuickSort(this, 0, num_non_undefined);
    902 
    903   if (!is_array && (num_non_undefined + 1 < max_prototype_element)) {
    904     // For compatibility with JSC, we shadow any elements in the prototype
    905     // chain that has become exposed by sort moving a hole to its position.
    906     ShadowPrototypeElements(this, num_non_undefined, max_prototype_element);
    907   }
    908 
    909   return this;
    910 }
    911 
    912 
    913 // The following functions cannot be made efficient on sparse arrays while
    914 // preserving the semantics, since the calls to the receiver function can add
    915 // or delete elements from the array.
    916 function ArrayFilter(f, receiver) {
    917   if (!IS_FUNCTION(f)) {
    918     throw MakeTypeError('called_non_callable', [ f ]);
    919   }
    920   // Pull out the length so that modifications to the length in the
    921   // loop will not affect the looping.
    922   var length = this.length;
    923   var result = [];
    924   var result_length = 0;
    925   for (var i = 0; i < length; i++) {
    926     var current = this[i];
    927     if (!IS_UNDEFINED(current) || i in this) {
    928       if (f.call(receiver, current, i, this)) {
    929         result[result_length++] = current;
    930       }
    931     }
    932   }
    933   return result;
    934 }
    935 
    936 
    937 function ArrayForEach(f, receiver) {
    938   if (!IS_FUNCTION(f)) {
    939     throw MakeTypeError('called_non_callable', [ f ]);
    940   }
    941   // Pull out the length so that modifications to the length in the
    942   // loop will not affect the looping.
    943   var length =  TO_UINT32(this.length);
    944   for (var i = 0; i < length; i++) {
    945     var current = this[i];
    946     if (!IS_UNDEFINED(current) || i in this) {
    947       f.call(receiver, current, i, this);
    948     }
    949   }
    950 }
    951 
    952 
    953 // Executes the function once for each element present in the
    954 // array until it finds one where callback returns true.
    955 function ArraySome(f, receiver) {
    956   if (!IS_FUNCTION(f)) {
    957     throw MakeTypeError('called_non_callable', [ f ]);
    958   }
    959   // Pull out the length so that modifications to the length in the
    960   // loop will not affect the looping.
    961   var length = TO_UINT32(this.length);
    962   for (var i = 0; i < length; i++) {
    963     var current = this[i];
    964     if (!IS_UNDEFINED(current) || i in this) {
    965       if (f.call(receiver, current, i, this)) return true;
    966     }
    967   }
    968   return false;
    969 }
    970 
    971 
    972 function ArrayEvery(f, receiver) {
    973   if (!IS_FUNCTION(f)) {
    974     throw MakeTypeError('called_non_callable', [ f ]);
    975   }
    976   // Pull out the length so that modifications to the length in the
    977   // loop will not affect the looping.
    978   var length = TO_UINT32(this.length);
    979   for (var i = 0; i < length; i++) {
    980     var current = this[i];
    981     if (!IS_UNDEFINED(current) || i in this) {
    982       if (!f.call(receiver, current, i, this)) return false;
    983     }
    984   }
    985   return true;
    986 }
    987 
    988 function ArrayMap(f, receiver) {
    989   if (!IS_FUNCTION(f)) {
    990     throw MakeTypeError('called_non_callable', [ f ]);
    991   }
    992   // Pull out the length so that modifications to the length in the
    993   // loop will not affect the looping.
    994   var length = TO_UINT32(this.length);
    995   var result = new $Array();
    996   var accumulator = new InternalArray(length);
    997   for (var i = 0; i < length; i++) {
    998     var current = this[i];
    999     if (!IS_UNDEFINED(current) || i in this) {
   1000       accumulator[i] = f.call(receiver, current, i, this);
   1001     }
   1002   }
   1003   %MoveArrayContents(accumulator, result);
   1004   return result;
   1005 }
   1006 
   1007 
   1008 function ArrayIndexOf(element, index) {
   1009   var length = TO_UINT32(this.length);
   1010   if (length == 0) return -1;
   1011   if (IS_UNDEFINED(index)) {
   1012     index = 0;
   1013   } else {
   1014     index = TO_INTEGER(index);
   1015     // If index is negative, index from the end of the array.
   1016     if (index < 0) {
   1017       index = length + index;
   1018       // If index is still negative, search the entire array.
   1019       if (index < 0) index = 0;
   1020     }
   1021   }
   1022   var min = index;
   1023   var max = length;
   1024   if (UseSparseVariant(this, length, IS_ARRAY(this))) {
   1025     var intervals = %GetArrayKeys(this, length);
   1026     if (intervals.length == 2 && intervals[0] < 0) {
   1027       // A single interval.
   1028       var intervalMin = -(intervals[0] + 1);
   1029       var intervalMax = intervalMin + intervals[1];
   1030       if (min < intervalMin) min = intervalMin;
   1031       max = intervalMax;  // Capped by length already.
   1032       // Fall through to loop below.
   1033     } else {
   1034       if (intervals.length == 0) return -1;
   1035       // Get all the keys in sorted order.
   1036       var sortedKeys = GetSortedArrayKeys(this, intervals);
   1037       var n = sortedKeys.length;
   1038       var i = 0;
   1039       while (i < n && sortedKeys[i] < index) i++;
   1040       while (i < n) {
   1041         var key = sortedKeys[i];
   1042         if (!IS_UNDEFINED(key) && this[key] === element) return key;
   1043         i++;
   1044       }
   1045       return -1;
   1046     }
   1047   }
   1048   // Lookup through the array.
   1049   if (!IS_UNDEFINED(element)) {
   1050     for (var i = min; i < max; i++) {
   1051       if (this[i] === element) return i;
   1052     }
   1053     return -1;
   1054   }
   1055   // Lookup through the array.
   1056   for (var i = min; i < max; i++) {
   1057     if (IS_UNDEFINED(this[i]) && i in this) {
   1058       return i;
   1059     }
   1060   }
   1061   return -1;
   1062 }
   1063 
   1064 
   1065 function ArrayLastIndexOf(element, index) {
   1066   var length = TO_UINT32(this.length);
   1067   if (length == 0) return -1;
   1068   if (%_ArgumentsLength() < 2) {
   1069     index = length - 1;
   1070   } else {
   1071     index = TO_INTEGER(index);
   1072     // If index is negative, index from end of the array.
   1073     if (index < 0) index += length;
   1074     // If index is still negative, do not search the array.
   1075     if (index < 0) return -1;
   1076     else if (index >= length) index = length - 1;
   1077   }
   1078   var min = 0;
   1079   var max = index;
   1080   if (UseSparseVariant(this, length, IS_ARRAY(this))) {
   1081     var intervals = %GetArrayKeys(this, index + 1);
   1082     if (intervals.length == 2 && intervals[0] < 0) {
   1083       // A single interval.
   1084       var intervalMin = -(intervals[0] + 1);
   1085       var intervalMax = intervalMin + intervals[1];
   1086       if (min < intervalMin) min = intervalMin;
   1087       max = intervalMax;  // Capped by index already.
   1088       // Fall through to loop below.
   1089     } else {
   1090       if (intervals.length == 0) return -1;
   1091       // Get all the keys in sorted order.
   1092       var sortedKeys = GetSortedArrayKeys(this, intervals);
   1093       var i = sortedKeys.length - 1;
   1094       while (i >= 0) {
   1095         var key = sortedKeys[i];
   1096         if (!IS_UNDEFINED(key) && this[key] === element) return key;
   1097         i--;
   1098       }
   1099       return -1;
   1100     }
   1101   }
   1102   // Lookup through the array.
   1103   if (!IS_UNDEFINED(element)) {
   1104     for (var i = max; i >= min; i--) {
   1105       if (this[i] === element) return i;
   1106     }
   1107     return -1;
   1108   }
   1109   for (var i = max; i >= min; i--) {
   1110     if (IS_UNDEFINED(this[i]) && i in this) {
   1111       return i;
   1112     }
   1113   }
   1114   return -1;
   1115 }
   1116 
   1117 
   1118 function ArrayReduce(callback, current) {
   1119   if (!IS_FUNCTION(callback)) {
   1120     throw MakeTypeError('called_non_callable', [callback]);
   1121   }
   1122   // Pull out the length so that modifications to the length in the
   1123   // loop will not affect the looping.
   1124   var length = this.length;
   1125   var i = 0;
   1126 
   1127   find_initial: if (%_ArgumentsLength() < 2) {
   1128     for (; i < length; i++) {
   1129       current = this[i];
   1130       if (!IS_UNDEFINED(current) || i in this) {
   1131         i++;
   1132         break find_initial;
   1133       }
   1134     }
   1135     throw MakeTypeError('reduce_no_initial', []);
   1136   }
   1137 
   1138   for (; i < length; i++) {
   1139     var element = this[i];
   1140     if (!IS_UNDEFINED(element) || i in this) {
   1141       current = callback.call(null, current, element, i, this);
   1142     }
   1143   }
   1144   return current;
   1145 }
   1146 
   1147 function ArrayReduceRight(callback, current) {
   1148   if (!IS_FUNCTION(callback)) {
   1149     throw MakeTypeError('called_non_callable', [callback]);
   1150   }
   1151   var i = this.length - 1;
   1152 
   1153   find_initial: if (%_ArgumentsLength() < 2) {
   1154     for (; i >= 0; i--) {
   1155       current = this[i];
   1156       if (!IS_UNDEFINED(current) || i in this) {
   1157         i--;
   1158         break find_initial;
   1159       }
   1160     }
   1161     throw MakeTypeError('reduce_no_initial', []);
   1162   }
   1163 
   1164   for (; i >= 0; i--) {
   1165     var element = this[i];
   1166     if (!IS_UNDEFINED(element) || i in this) {
   1167       current = callback.call(null, current, element, i, this);
   1168     }
   1169   }
   1170   return current;
   1171 }
   1172 
   1173 // ES5, 15.4.3.2
   1174 function ArrayIsArray(obj) {
   1175   return IS_ARRAY(obj);
   1176 }
   1177 
   1178 
   1179 // -------------------------------------------------------------------
   1180 function SetupArray() {
   1181   // Setup non-enumerable constructor property on the Array.prototype
   1182   // object.
   1183   %SetProperty($Array.prototype, "constructor", $Array, DONT_ENUM);
   1184 
   1185   // Setup non-enumerable functions on the Array object.
   1186   InstallFunctions($Array, DONT_ENUM, $Array(
   1187     "isArray", ArrayIsArray
   1188   ));
   1189 
   1190   var specialFunctions = %SpecialArrayFunctions({});
   1191 
   1192   function getFunction(name, jsBuiltin, len) {
   1193     var f = jsBuiltin;
   1194     if (specialFunctions.hasOwnProperty(name)) {
   1195       f = specialFunctions[name];
   1196     }
   1197     if (!IS_UNDEFINED(len)) {
   1198       %FunctionSetLength(f, len);
   1199     }
   1200     return f;
   1201   }
   1202 
   1203   // Setup non-enumerable functions of the Array.prototype object and
   1204   // set their names.
   1205   // Manipulate the length of some of the functions to meet
   1206   // expectations set by ECMA-262 or Mozilla.
   1207   InstallFunctionsOnHiddenPrototype($Array.prototype, DONT_ENUM, $Array(
   1208     "toString", getFunction("toString", ArrayToString),
   1209     "toLocaleString", getFunction("toLocaleString", ArrayToLocaleString),
   1210     "join", getFunction("join", ArrayJoin),
   1211     "pop", getFunction("pop", ArrayPop),
   1212     "push", getFunction("push", ArrayPush, 1),
   1213     "concat", getFunction("concat", ArrayConcat, 1),
   1214     "reverse", getFunction("reverse", ArrayReverse),
   1215     "shift", getFunction("shift", ArrayShift),
   1216     "unshift", getFunction("unshift", ArrayUnshift, 1),
   1217     "slice", getFunction("slice", ArraySlice, 2),
   1218     "splice", getFunction("splice", ArraySplice, 2),
   1219     "sort", getFunction("sort", ArraySort),
   1220     "filter", getFunction("filter", ArrayFilter, 1),
   1221     "forEach", getFunction("forEach", ArrayForEach, 1),
   1222     "some", getFunction("some", ArraySome, 1),
   1223     "every", getFunction("every", ArrayEvery, 1),
   1224     "map", getFunction("map", ArrayMap, 1),
   1225     "indexOf", getFunction("indexOf", ArrayIndexOf, 1),
   1226     "lastIndexOf", getFunction("lastIndexOf", ArrayLastIndexOf, 1),
   1227     "reduce", getFunction("reduce", ArrayReduce, 1),
   1228     "reduceRight", getFunction("reduceRight", ArrayReduceRight, 1)
   1229   ));
   1230 
   1231   %FinishArrayPrototypeSetup($Array.prototype);
   1232 
   1233   // The internal Array prototype doesn't need to be fancy, since it's never
   1234   // exposed to user code, so no hidden prototypes or DONT_ENUM attributes
   1235   // are necessary.
   1236   // The null __proto__ ensures that we never inherit any user created
   1237   // getters or setters from, e.g., Object.prototype.
   1238   InternalArray.prototype.__proto__ = null;
   1239   // Adding only the functions that are actually used, and a toString.
   1240   InternalArray.prototype.join = getFunction("join", ArrayJoin);
   1241   InternalArray.prototype.pop = getFunction("pop", ArrayPop);
   1242   InternalArray.prototype.push = getFunction("push", ArrayPush);
   1243   InternalArray.prototype.toString = function() {
   1244     return "Internal Array, length " + this.length;
   1245   };
   1246 }
   1247 
   1248 
   1249 SetupArray();
   1250