array.js revision e0cee9b3ed82e2391fd85d118aeaa4ea361c687d
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.
36var 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.
41function 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 ''.
71function 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
92function UseSparseVariant(object, length, is_array) {
93   return is_array &&
94       length > 1000 &&
95       (!%_IsSmi(length) ||
96        %EstimateNumberOfElements(object) < (length >> 2));
97}
98
99
100function 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
173function 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
181function 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.
199function 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.
242function 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.
305function 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
318function 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
361function 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
369function 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
377function 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.
393function 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.
409function 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
420function 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.
433function 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
476function 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
507function 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
528function 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
547function 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
584function 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
655function 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.
916function 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
937function 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.
955function 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
972function 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
988function 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
1008function 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
1065function 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
1118function 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
1147function 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
1174function ArrayIsArray(obj) {
1175  return IS_ARRAY(obj);
1176}
1177
1178
1179// -------------------------------------------------------------------
1180function 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
1249SetupArray();
1250