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