1// Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2// 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Python Software Foundation;
3// All Rights Reserved
4
5// This file implements a stable, adapative merge sort variant called TimSort.
6//
7// It was first implemented in python and this Torque implementation
8// is based on the current version:
9//
10// https://github.com/python/cpython/blob/master/Objects/listobject.c
11//
12// Detailed analysis and a description of the algorithm can be found at:
13//
14// https://github.com/python/cpython/blob/master/Objects/listsort.txt
15
16module array {
17  // All accessors bail to the GenericElementsAccessor if assumptions checked
18  // by "CanUseSameAccessor<>" are violated:
19  //  Generic <- FastPackedSmi
20  //          <- FastSmiOrObject
21  //          <- FastDouble
22  //          <- Dictionary
23  //
24  // The only exception is TempArrayElements, since it does not describe the
25  // "elements" of the receiver, but instead is used as an "adaptor" so
26  // GallopLeft/GallopRight can be reused with the temporary array.
27  const kGenericElementsAccessorId: Smi = 0;
28  const kFastElementsAccessorId: Smi = 1;
29
30  // This is a special type, used to access the temporary array which is always
31  // PACKED_ELEMENTS. As a result, we do not need a sanity check for it,
32  // otherwise we might wrongly bail to the slow path.
33  type TempArrayElements;
34
35  // The following index constants describe the layout of the sortState.
36  // The sortState is currently implemented as a FixedArray of
37  // size kSortStateSize.
38
39  // The receiver of the Array.p.sort call.
40  const kReceiverIdx: constexpr int31 = 0;
41
42  // The initial map and length of the receiver. After calling into JS, these
43  // are reloaded and checked. If they changed we bail to the baseline
44  // GenericElementsAccessor.
45  const kInitialReceiverMapIdx: constexpr int31 = 1;
46  const kInitialReceiverLengthIdx: constexpr int31 = 2;
47
48  // If the user provided a comparison function, it is stored here.
49  const kUserCmpFnIdx: constexpr int31 = 3;
50
51  // Function pointer to the comparison function. This can either be a builtin
52  // that calls the user-provided comparison function or "SortDefault", which
53  // uses ToString and a lexicographical compare.
54  const kSortComparePtrIdx: constexpr int31 = 4;
55
56  // The following three function pointer represent a Accessor/Path.
57  // These are used to Load/Store elements and to check whether to bail to the
58  // baseline GenericElementsAccessor.
59  const kLoadFnIdx: constexpr int31 = 5;
60  const kStoreFnIdx: constexpr int31 = 6;
61  const kCanUseSameAccessorFnIdx: constexpr int31 = 7;
62
63  // If this field has the value kFailure, we need to bail to the baseline
64  // GenericElementsAccessor.
65  const kBailoutStatusIdx: constexpr int31 = 8;
66
67  // This controls when we get *into* galloping mode. It's initialized to
68  // kMinGallop. mergeLow and mergeHigh tend to nudge it higher for random data,
69  // and lower for highly structured data.
70  const kMinGallopIdx: constexpr int31 = 9;
71
72  // A stack of sortState[kPendingRunsSizeIdx] pending runs yet to be merged.
73  // Run #i starts at sortState[kPendingRunsIdx][2 * i] and extends for
74  // sortState[kPendingRunsIdx][2 * i + 1] elements:
75  //
76  //   [..., base (i-1), length (i-1), base i, length i]
77  //
78  // It's always true (so long as the indices are in bounds) that
79  //
80  //   base of run #i + length of run #i == base of run #i + 1
81  //
82  const kPendingRunsSizeIdx: constexpr int31 = 10;
83  const kPendingRunsIdx: constexpr int31 = 11;
84
85  // The current size of the temporary array.
86  const kTempArraySizeIdx: constexpr int31 = 12;
87
88  // Pointer to the temporary array.
89  const kTempArrayIdx: constexpr int31 = 13;
90
91  // Contains a Smi constant describing which accessors to use. This is used
92  // for reloading the right elements and for a sanity check.
93  const kAccessorIdx: constexpr int31 = 14;
94
95  const kSortStateSize: intptr = 15;
96
97  const kFailure: Smi = -1;
98  const kSuccess: Smi = 0;
99
100  // The maximum number of entries in a SortState's pending-runs stack.
101  // This is enough to sort arrays of size up to about
102  //   32 * phi ** kMaxMergePending
103  // where phi ~= 1.618. 85 is ridiculously large enough, good for an array with
104  // 2 ** 64 elements.
105  const kMaxMergePending: constexpr int31 = 85;
106
107  // When we get into galloping mode, we stay there until both runs win less
108  // often then kMinGallop consecutive times. See listsort.txt for more info.
109  const kMinGallopWins: constexpr int31 = 7;
110
111  // Default size of the temporary array. The temporary array is allocated when
112  // it is first requested, but it has always at least this size.
113  const kSortStateTempSize: Smi = 32;
114
115  type LoadFn = builtin(Context, FixedArray, HeapObject, Smi) => Object;
116  type StoreFn = builtin(Context, FixedArray, HeapObject, Smi, Object) => Smi;
117  type CanUseSameAccessorFn = builtin(Context, JSReceiver, Object, Number) =>
118      Boolean;
119  type CompareBuiltinFn = builtin(Context, Object, Object, Object) => Number;
120
121  // The following builtins implement Load/Store for all the Accessors.
122  // The most generic baseline version uses Get-/SetProperty. We do not need
123  // to worry about the prototype chain, because the pre-processing step has
124  // copied values from the prototype chain to the receiver if they were visible
125  // through a hole.
126
127  builtin Load<ElementsAccessor : type>(
128      context: Context, sortState: FixedArray, elements: HeapObject,
129      index: Smi): Object {
130    return GetProperty(context, elements, index);
131  }
132
133  Load<FastPackedSmiElements>(
134      context: Context, sortState: FixedArray, elements: HeapObject,
135      index: Smi): Object {
136    const elems: FixedArray = unsafe_cast<FixedArray>(elements);
137    return elems[index];
138  }
139
140  Load<FastSmiOrObjectElements>(
141      context: Context, sortState: FixedArray, elements: HeapObject,
142      index: Smi): Object {
143    const elems: FixedArray = unsafe_cast<FixedArray>(elements);
144    const result: Object = elems[index];
145    if (IsTheHole(result)) {
146      // The pre-processing step removed all holes by compacting all elements
147      // at the start of the array. Finding a hole means the cmp function or
148      // ToString changes the array.
149      return Failure(sortState);
150    }
151    return result;
152  }
153
154  Load<FastDoubleElements>(
155      context: Context, sortState: FixedArray, elements: HeapObject,
156      index: Smi): Object {
157    try {
158      const elems: FixedDoubleArray = unsafe_cast<FixedDoubleArray>(elements);
159      const value: float64 =
160          LoadDoubleWithHoleCheck(elems, index) otherwise Bailout;
161      return AllocateHeapNumberWithValue(value);
162    }
163    label Bailout {
164      // The pre-processing step removed all holes by compacting all elements
165      // at the start of the array. Finding a hole means the cmp function or
166      // ToString changes the array.
167      return Failure(sortState);
168    }
169  }
170
171  Load<DictionaryElements>(
172      context: Context, sortState: FixedArray, elements: HeapObject,
173      index: Smi): Object {
174    try {
175      const dictionary: NumberDictionary =
176          unsafe_cast<NumberDictionary>(elements);
177      const intptr_index: intptr = convert<intptr>(index);
178      const value: Object =
179          BasicLoadNumberDictionaryElement(dictionary, intptr_index)
180      otherwise Bailout, Bailout;
181      return value;
182    }
183    label Bailout {
184      return Failure(sortState);
185    }
186  }
187
188  Load<TempArrayElements>(
189      context: Context, sortState: FixedArray, elements: HeapObject,
190      index: Smi): Object {
191    assert(IsFixedArray(elements));
192    const elems: FixedArray = unsafe_cast<FixedArray>(elements);
193    return elems[index];
194  }
195
196  builtin Store<ElementsAccessor : type>(
197      context: Context, sortState: FixedArray, elements: HeapObject, index: Smi,
198      value: Object): Smi {
199    SetProperty(context, elements, index, value);
200    return kSuccess;
201  }
202
203  Store<FastPackedSmiElements>(
204      context: Context, sortState: FixedArray, elements: HeapObject, index: Smi,
205      value: Object): Smi {
206    const elems: FixedArray = unsafe_cast<FixedArray>(elements);
207    StoreFixedArrayElementSmi(elems, index, value, SKIP_WRITE_BARRIER);
208    return kSuccess;
209  }
210
211  Store<FastSmiOrObjectElements>(
212      context: Context, sortState: FixedArray, elements: HeapObject, index: Smi,
213      value: Object): Smi {
214    const elems: FixedArray = unsafe_cast<FixedArray>(elements);
215    elems[index] = value;
216    return kSuccess;
217  }
218
219  Store<FastDoubleElements>(
220      context: Context, sortState: FixedArray, elements: HeapObject, index: Smi,
221      value: Object): Smi {
222    const elems: FixedDoubleArray = unsafe_cast<FixedDoubleArray>(elements);
223    const heap_val: HeapNumber = unsafe_cast<HeapNumber>(value);
224    // Make sure we do not store signalling NaNs into double arrays.
225    const val: float64 = Float64SilenceNaN(convert<float64>(heap_val));
226    StoreFixedDoubleArrayElementWithSmiIndex(elems, index, val);
227    return kSuccess;
228  }
229
230  Store<DictionaryElements>(
231      context: Context, sortState: FixedArray, elements: HeapObject, index: Smi,
232      value: Object): Smi {
233    const dictionary: NumberDictionary =
234        unsafe_cast<NumberDictionary>(elements);
235    const intptr_index: intptr = convert<intptr>(index);
236    try {
237      BasicStoreNumberDictionaryElement(dictionary, intptr_index, value)
238      otherwise Fail, Fail, ReadOnly;
239      return kSuccess;
240    }
241    label ReadOnly {
242      // We cannot write to read-only data properties. Throw the same TypeError
243      // as SetProperty would.
244      const receiver: JSReceiver = GetReceiver(sortState);
245      ThrowTypeError(
246          context, kStrictReadOnlyProperty, index, Typeof(receiver), receiver);
247    }
248    label Fail {
249      return Failure(sortState);
250    }
251  }
252
253  Store<TempArrayElements>(
254      context: Context, sortState: FixedArray, elements: HeapObject, index: Smi,
255      value: Object): Smi {
256    const elems: FixedArray = unsafe_cast<FixedArray>(elements);
257    elems[index] = value;
258    return kSuccess;
259  }
260
261  extern macro UnsafeCastObjectToCompareBuiltinFn(Object): CompareBuiltinFn;
262  unsafe_cast<CompareBuiltinFn>(o: Object): CompareBuiltinFn {
263    return UnsafeCastObjectToCompareBuiltinFn(o);
264  }
265
266  extern macro UnsafeCastObjectToLoadFn(Object): LoadFn;
267  unsafe_cast<LoadFn>(o: Object): LoadFn {
268    return UnsafeCastObjectToLoadFn(o);
269  }
270
271  extern macro UnsafeCastObjectToStoreFn(Object): StoreFn;
272  unsafe_cast<StoreFn>(o: Object): StoreFn {
273    return UnsafeCastObjectToStoreFn(o);
274  }
275
276  extern macro UnsafeCastObjectToCanUseSameAccessorFn(Object):
277      CanUseSameAccessorFn;
278  unsafe_cast<CanUseSameAccessorFn>(o: Object): CanUseSameAccessorFn {
279    return UnsafeCastObjectToCanUseSameAccessorFn(o);
280  }
281
282  builtin SortCompareDefault(
283      context: Context, comparefn: Object, x: Object, y: Object): Number {
284    assert(comparefn == Undefined);
285
286    if (TaggedIsSmi(x) && TaggedIsSmi(y)) {
287      // TODO(szuend): Replace with a fast CallCFunction call.
288      return SmiLexicographicCompare(context, x, y);
289    }
290
291    // 5. Let xString be ? ToString(x).
292    const xString: String = ToString_Inline(context, x);
293
294    // 6. Let yString be ? ToString(y).
295    const yString: String = ToString_Inline(context, y);
296
297    // 7. Let xSmaller be the result of performing
298    //    Abstract Relational Comparison xString < yString.
299    // 8. If xSmaller is true, return -1.
300    if (StringLessThan(context, xString, yString) == True) return -1;
301
302    // 9. Let ySmaller be the result of performing
303    //    Abstract Relational Comparison yString < xString.
304    // 10. If ySmaller is true, return 1.
305    if (StringLessThan(context, yString, xString) == True) return 1;
306
307    // 11. Return +0.
308    return 0;
309  }
310
311  builtin SortCompareUserFn(
312      context: Context, comparefn: Object, x: Object, y: Object): Number {
313    assert(comparefn != Undefined);
314    const cmpfn: Callable = unsafe_cast<Callable>(comparefn);
315
316    // a. Let v be ? ToNumber(? Call(comparefn, undefined, x, y)).
317    const v: Number =
318        ToNumber_Inline(context, Call(context, cmpfn, Undefined, x, y));
319
320    // b. If v is NaN, return +0.
321    if (NumberIsNaN(v)) return 0;
322
323    // c. return v.
324    return v;
325  }
326
327  builtin CanUseSameAccessor<ElementsAccessor : type>(
328      context: Context, receiver: JSReceiver, initialReceiverMap: Object,
329      initialReceiverLength: Number): Boolean {
330    assert(IsJSArray(receiver));
331
332    let a: JSArray = unsafe_cast<JSArray>(receiver);
333    if (a.map != initialReceiverMap) return False;
334
335    assert(TaggedIsSmi(initialReceiverLength));
336    let originalLength: Smi = unsafe_cast<Smi>(initialReceiverLength);
337    if (a.length_fast != originalLength) return False;
338
339    return True;
340  }
341
342  CanUseSameAccessor<GenericElementsAccessor>(
343      context: Context, receiver: JSReceiver, initialReceiverMap: Object,
344      initialReceiverLength: Number): Boolean {
345    // Do nothing. We are already on the slow path.
346    return True;
347  }
348
349  CanUseSameAccessor<DictionaryElements>(
350      context: Context, receiver: JSReceiver, initialReceiverMap: Object,
351      initialReceiverLength: Number): Boolean {
352    let obj: JSReceiver = unsafe_cast<JSReceiver>(receiver);
353    return SelectBooleanConstant(obj.map == initialReceiverMap);
354  }
355
356  macro CallCompareFn(
357      context: Context, sortState: FixedArray, x: Object, y: Object): Number
358  labels Bailout {
359    const userCmpFn: Object = sortState[kUserCmpFnIdx];
360    const sortCompare: CompareBuiltinFn =
361        unsafe_cast<CompareBuiltinFn>(sortState[kSortComparePtrIdx]);
362
363    const result: Number = sortCompare(context, userCmpFn, x, y);
364
365    const receiver: JSReceiver = GetReceiver(sortState);
366    const initialReceiverMap: Object = sortState[kInitialReceiverMapIdx];
367    const initialReceiverLength: Number =
368        unsafe_cast<Number>(sortState[kInitialReceiverLengthIdx]);
369    const CanUseSameAccessor: CanUseSameAccessorFn =
370        GetCanUseSameAccessorFn(sortState);
371
372    if (!CanUseSameAccessor(
373            context, receiver, initialReceiverMap, initialReceiverLength)) {
374      goto Bailout;
375    }
376    return result;
377  }
378
379  // Reloading elements after returning from JS is needed since left-trimming
380  // might have occurred. This means we cannot leave any pointer to the elements
381  // backing store on the stack (since it would point to the filler object).
382  // TODO(v8:7995): Remove reloading once left-trimming is removed.
383  macro ReloadElements(sortState: FixedArray): HeapObject {
384    const receiver: JSReceiver = GetReceiver(sortState);
385    if (sortState[kAccessorIdx] == kGenericElementsAccessorId) return receiver;
386
387    const object: JSObject = unsafe_cast<JSObject>(receiver);
388    return object.elements;
389  }
390
391  macro GetLoadFn(sortState: FixedArray): LoadFn {
392    return unsafe_cast<LoadFn>(sortState[kLoadFnIdx]);
393  }
394
395  macro GetStoreFn(sortState: FixedArray): StoreFn {
396    return unsafe_cast<StoreFn>(sortState[kStoreFnIdx]);
397  }
398
399  macro GetCanUseSameAccessorFn(sortState: FixedArray): CanUseSameAccessorFn {
400    return unsafe_cast<CanUseSameAccessorFn>(
401        sortState[kCanUseSameAccessorFnIdx]);
402  }
403
404  macro GetReceiver(sortState: FixedArray): JSReceiver {
405    return unsafe_cast<JSReceiver>(sortState[kReceiverIdx]);
406  }
407
408  // Returns the temporary array without changing its size.
409  macro GetTempArray(sortState: FixedArray): FixedArray {
410    return unsafe_cast<FixedArray>(sortState[kTempArrayIdx]);
411  }
412
413  // Re-loading the stack-size is done in a few places. The small macro allows
414  // for easier invariant checks at all use sites.
415  macro GetPendingRunsSize(sortState: FixedArray): Smi {
416    assert(TaggedIsSmi(sortState[kPendingRunsSizeIdx]));
417    const stack_size: Smi = unsafe_cast<Smi>(sortState[kPendingRunsSizeIdx]);
418
419    assert(stack_size >= 0);
420    return stack_size;
421  }
422
423  macro SetPendingRunsSize(sortState: FixedArray, value: Smi) {
424    sortState[kPendingRunsSizeIdx] = value;
425  }
426
427  macro GetPendingRunBase(pendingRuns: FixedArray, run: Smi): Smi {
428    return unsafe_cast<Smi>(pendingRuns[run << 1]);
429  }
430
431  macro SetPendingRunBase(pendingRuns: FixedArray, run: Smi, value: Smi) {
432    pendingRuns[run << 1] = value;
433  }
434
435  macro GetPendingRunLength(pendingRuns: FixedArray, run: Smi): Smi {
436    return unsafe_cast<Smi>(pendingRuns[(run << 1) + 1]);
437  }
438
439  macro SetPendingRunLength(pendingRuns: FixedArray, run: Smi, value: Smi) {
440    pendingRuns[(run << 1) + 1] = value;
441  }
442
443  macro PushRun(sortState: FixedArray, base: Smi, length: Smi) {
444    assert(GetPendingRunsSize(sortState) < kMaxMergePending);
445
446    const stack_size: Smi = GetPendingRunsSize(sortState);
447    const pending_runs: FixedArray =
448        unsafe_cast<FixedArray>(sortState[kPendingRunsIdx]);
449
450    SetPendingRunBase(pending_runs, stack_size, base);
451    SetPendingRunLength(pending_runs, stack_size, length);
452
453    SetPendingRunsSize(sortState, stack_size + 1);
454  }
455
456  // Returns the temporary array and makes sure that it is big enough.
457  // TODO(szuend): Implement a better re-size strategy.
458  macro GetTempArray(sortState: FixedArray, requestedSize: Smi): FixedArray {
459    const min_size: Smi = SmiMax(kSortStateTempSize, requestedSize);
460
461    const current_size: Smi = unsafe_cast<Smi>(sortState[kTempArraySizeIdx]);
462    if (current_size >= min_size) {
463      return GetTempArray(sortState);
464    }
465
466    const temp_array: FixedArray =
467        AllocateZeroedFixedArray(convert<intptr>(min_size));
468    FillFixedArrayWithSmiZero(temp_array, min_size);
469
470    sortState[kTempArraySizeIdx] = min_size;
471    sortState[kTempArrayIdx] = temp_array;
472    return temp_array;
473  }
474
475  // This macro jumps to the Bailout label iff kBailoutStatus is kFailure.
476  macro EnsureSuccess(sortState: FixedArray) labels Bailout {
477    const status: Smi = unsafe_cast<Smi>(sortState[kBailoutStatusIdx]);
478    if (status == kFailure) goto Bailout;
479  }
480
481  // Sets kBailoutStatus to kFailure and returns kFailure.
482  macro Failure(sortState: FixedArray): Smi {
483    sortState[kBailoutStatusIdx] = kFailure;
484    return kFailure;
485  }
486
487  // The following Call* macros wrap builtin calls, making call sites more
488  // readable since we can use labels and do not have to check kBailoutStatus
489  // or the return value.
490
491  macro CallLoad(
492      context: Context, sortState: FixedArray, Load: LoadFn,
493      elements: HeapObject, index: Smi): Object labels Bailout {
494    const result: Object = Load(context, sortState, elements, index);
495    EnsureSuccess(sortState) otherwise Bailout;
496    return result;
497  }
498
499  macro CallStore(
500      context: Context, sortState: FixedArray, Store: StoreFn,
501      elements: HeapObject, index: Smi, value: Object) labels Bailout {
502    Store(context, sortState, elements, index, value);
503    EnsureSuccess(sortState) otherwise Bailout;
504  }
505
506  macro CallCopyFromTempArray(
507      context: Context, sortState: FixedArray, dstElements: HeapObject,
508      dstPos: Smi, tempArray: FixedArray, srcPos: Smi, length: Smi)
509  labels Bailout {
510    CopyFromTempArray(
511        context, sortState, dstElements, dstPos, tempArray, srcPos, length);
512    EnsureSuccess(sortState) otherwise Bailout;
513  }
514
515  macro CallCopyWithinSortArray(
516      context: Context, sortState: FixedArray, elements: HeapObject,
517      srcPos: Smi, dstPos: Smi, length: Smi)
518  labels Bailout {
519    CopyWithinSortArray(context, sortState, elements, srcPos, dstPos, length);
520    EnsureSuccess(sortState) otherwise Bailout;
521  }
522
523  macro CallGallopRight(
524      context: Context, sortState: FixedArray, Load: LoadFn, key: Object,
525      base: Smi, length: Smi, hint: Smi, useTempArray: Boolean): Smi
526  labels Bailout {
527    const result: Smi = GallopRight(
528        context, sortState, Load, key, base, length, hint, useTempArray);
529    EnsureSuccess(sortState) otherwise Bailout;
530    return result;
531  }
532
533  macro CallGallopLeft(
534      context: Context, sortState: FixedArray, Load: LoadFn, key: Object,
535      base: Smi, length: Smi, hint: Smi, useTempArray: Boolean): Smi
536  labels Bailout {
537    const result: Smi = GallopLeft(
538        context, sortState, Load, key, base, length, hint, useTempArray);
539    EnsureSuccess(sortState) otherwise Bailout;
540    return result;
541  }
542
543  macro CallMergeAt(context: Context, sortState: FixedArray, i: Smi)
544  labels Bailout {
545    MergeAt(context, sortState, i);
546    EnsureSuccess(sortState) otherwise Bailout;
547  }
548
549  // Used for OOB asserts in Copy* builtins.
550  macro GetReceiverLengthProperty(
551      context: Context, sortState: FixedArray): Smi {
552    const receiver: JSReceiver = GetReceiver(sortState);
553
554    if (IsJSArray(receiver)) return unsafe_cast<JSArray>(receiver).length_fast;
555
556    const len: Number =
557        ToLength_Inline(context, GetProperty(context, receiver, 'length'));
558    return unsafe_cast<Smi>(len);
559  }
560
561  macro CopyToTempArray(
562      context: Context, sortState: FixedArray, Load: LoadFn,
563      srcElements: HeapObject, srcPos: Smi, tempArray: FixedArray, dstPos: Smi,
564      length: Smi)
565  labels Bailout {
566    assert(srcPos >= 0);
567    assert(dstPos >= 0);
568    assert(srcPos <= GetReceiverLengthProperty(context, sortState) - length);
569    assert(dstPos <= tempArray.length - length);
570
571    let src_idx: Smi = srcPos;
572    let dst_idx: Smi = dstPos;
573    let to: Smi = srcPos + length;
574
575    while (src_idx < to) {
576      let element: Object =
577          CallLoad(context, sortState, Load, srcElements, src_idx++)
578      otherwise Bailout;
579      tempArray[dst_idx++] = element;
580    }
581  }
582
583  builtin CopyFromTempArray(
584      context: Context, sortState: FixedArray, dstElements: HeapObject,
585      dstPos: Smi, tempArray: FixedArray, srcPos: Smi, length: Smi): Smi {
586    assert(srcPos >= 0);
587    assert(dstPos >= 0);
588    assert(srcPos <= tempArray.length - length);
589    assert(dstPos <= GetReceiverLengthProperty(context, sortState) - length);
590
591    let Store: StoreFn = GetStoreFn(sortState);
592
593    let src_idx: Smi = srcPos;
594    let dst_idx: Smi = dstPos;
595    let to: Smi = srcPos + length;
596    try {
597      while (src_idx < to) {
598        CallStore(
599            context, sortState, Store, dstElements, dst_idx++,
600            tempArray[src_idx++])
601        otherwise Bailout;
602      }
603      return kSuccess;
604    }
605    label Bailout {
606      return Failure(sortState);
607    }
608  }
609
610  builtin CopyWithinSortArray(
611      context: Context, sortState: FixedArray, elements: HeapObject,
612      srcPos: Smi, dstPos: Smi, length: Smi): Smi {
613    assert(srcPos >= 0);
614    assert(dstPos >= 0);
615    assert(srcPos <= GetReceiverLengthProperty(context, sortState) - length);
616    assert(dstPos <= GetReceiverLengthProperty(context, sortState) - length);
617
618    try {
619      let Load: LoadFn = GetLoadFn(sortState);
620      let Store: StoreFn = GetStoreFn(sortState);
621
622      if (srcPos < dstPos) {
623        let src_idx: Smi = srcPos + length - 1;
624        let dst_idx: Smi = dstPos + length - 1;
625        while (src_idx >= srcPos) {
626          CopyElement(
627              context, sortState, Load, Store, elements, src_idx--, dst_idx--)
628          otherwise Bailout;
629        }
630      } else {
631        let src_idx: Smi = srcPos;
632        let dst_idx: Smi = dstPos;
633        let to: Smi = srcPos + length;
634        while (src_idx < to) {
635          CopyElement(
636              context, sortState, Load, Store, elements, src_idx++, dst_idx++)
637          otherwise Bailout;
638        }
639      }
640      return kSuccess;
641    }
642    label Bailout {
643      return Failure(sortState);
644    }
645  }
646
647  // BinaryInsertionSort is the best method for sorting small arrays: it does
648  // few compares, but can do data movement quadratic in the number of elements.
649  // This is an advantage since comparisons are more expensive due to
650  // calling into JS.
651  //
652  //  [low, high) is a contiguous range of a array, and is sorted via
653  // binary insertion. This sort is stable.
654  //
655  // On entry, must have low <= start <= high, and that [low, start) is already
656  // sorted. Pass start == low if you do not know!.
657  builtin BinaryInsertionSort(
658      context: Context, sortState: FixedArray, low: Smi, startArg: Smi,
659      high: Smi): Smi {
660    assert(low <= startArg && startArg <= high);
661
662    try {
663      let elements: HeapObject = ReloadElements(sortState);
664
665      const Load: LoadFn = GetLoadFn(sortState);
666      const Store: StoreFn = GetStoreFn(sortState);
667
668      let start: Smi = low == startArg ? (startArg + 1) : startArg;
669
670      for (; start < high; ++start) {
671        // Set left to where a[start] belongs.
672        let left: Smi = low;
673        let right: Smi = start;
674
675        const pivot: Object =
676            CallLoad(context, sortState, Load, elements, right)
677        otherwise Bailout;
678
679        // Invariants:
680        //   pivot >= all in [low, left).
681        //   pivot  < all in [right, start).
682        assert(left < right);
683
684        // Find pivot insertion point.
685        while (left < right) {
686          const mid: Smi = left + ((right - left) >>> 1);
687          const mid_element: Object =
688              CallLoad(context, sortState, Load, elements, mid)
689          otherwise Bailout;
690          const order: Number =
691              CallCompareFn(context, sortState, pivot, mid_element)
692          otherwise Bailout;
693          elements = ReloadElements(sortState);
694
695          if (order < 0) {
696            right = mid;
697          } else {
698            left = mid + 1;
699          }
700        }
701        assert(left == right);
702
703        // The invariants still hold, so:
704        //   pivot >= all in [low, left) and
705        //   pivot  < all in [left, start),
706        //
707        // so pivot belongs at left. Note that if there are elements equal to
708        // pivot, left points to the first slot after them -- that's why this
709        // sort is stable.
710        // Slide over to make room.
711        for (let p: Smi = start; p > left; --p) {
712          CopyElement(context, sortState, Load, Store, elements, p - 1, p)
713          otherwise Bailout;
714        }
715        CallStore(context, sortState, Store, elements, left, pivot)
716        otherwise Bailout;
717      }
718      return kSuccess;
719    }
720    label Bailout {
721      return Failure(sortState);
722    }
723  }
724
725  // Return the length of the run beginning at low, in the range [low, high),
726  // low < high is required on entry.
727  // "A run" is the longest ascending sequence, with
728  //
729  //   a[low] <= a[low + 1] <= a[low + 2] <= ...
730  //
731  // or the longest descending sequence, with
732  //
733  //   a[low] > a[low + 1] > a[low + 2] > ...
734  //
735  // For its intended use in stable mergesort, the strictness of the definition
736  // of "descending" is needed so that the range can safely be reversed
737  // without violating stability (strict ">" ensures there are no equal
738  // elements to get out of order).
739  //
740  // In addition, if the run is "descending", it is reversed, so the returned
741  // length is always an ascending sequence.
742  macro CountAndMakeRun(
743      context: Context, sortState: FixedArray, lowArg: Smi, high: Smi): Smi
744  labels Bailout {
745    assert(lowArg < high);
746
747    let elements: HeapObject = ReloadElements(sortState);
748    const Load: LoadFn = GetLoadFn(sortState);
749    const Store: StoreFn = GetStoreFn(sortState);
750
751    let low: Smi = lowArg + 1;
752    if (low == high) return 1;
753
754    let run_length: Smi = 2;
755
756    const element_low: Object =
757        CallLoad(context, sortState, Load, elements, low) otherwise Bailout;
758    const element_low_pred: Object =
759        CallLoad(context, sortState, Load, elements, low - 1) otherwise Bailout;
760    let order: Number =
761        CallCompareFn(context, sortState, element_low, element_low_pred)
762    otherwise Bailout;
763    elements = ReloadElements(sortState);
764
765    // TODO(szuend): Replace with "order < 0" once Torque supports it.
766    //               Currently the operator<(Number, Number) has return type
767    //               'never' and uses two labels to branch.
768    const is_descending: bool = order < 0 ? true : false;
769
770    let previous_element: Object = element_low;
771    for (let idx: Smi = low + 1; idx < high; ++idx) {
772      const current_element: Object =
773          CallLoad(context, sortState, Load, elements, idx) otherwise Bailout;
774      order =
775          CallCompareFn(context, sortState, current_element, previous_element)
776      otherwise Bailout;
777      elements = ReloadElements(sortState);
778
779      if (is_descending) {
780        if (order >= 0) break;
781      } else {
782        if (order < 0) break;
783      }
784
785      previous_element = current_element;
786      ++run_length;
787    }
788
789    if (is_descending) {
790      ReverseRange(
791          context, sortState, Load, Store, elements, lowArg,
792          lowArg + run_length)
793      otherwise Bailout;
794    }
795
796    return run_length;
797  }
798
799  macro ReverseRange(
800      context: Context, sortState: FixedArray, Load: LoadFn, Store: StoreFn,
801      elements: HeapObject, from: Smi, to: Smi)
802  labels Bailout {
803    let low: Smi = from;
804    let high: Smi = to - 1;
805
806    while (low < high) {
807      const element_low: Object =
808          CallLoad(context, sortState, Load, elements, low) otherwise Bailout;
809      const element_high: Object =
810          CallLoad(context, sortState, Load, elements, high) otherwise Bailout;
811      CallStore(context, sortState, Store, elements, low++, element_high)
812      otherwise Bailout;
813      CallStore(context, sortState, Store, elements, high--, element_low)
814      otherwise Bailout;
815    }
816  }
817
818  // Merges the two runs at stack indices i and i + 1.
819  // Returns kFailure if we need to bailout, kSuccess otherwise.
820  builtin MergeAt(context: Context, sortState: FixedArray, i: Smi): Smi {
821    const stack_size: Smi = GetPendingRunsSize(sortState);
822
823    // We are only allowed to either merge the two top-most runs, or leave
824    // the top most run alone and merge the two next runs.
825    assert(stack_size >= 2);
826    assert(i >= 0);
827    assert(i == stack_size - 2 || i == stack_size - 3);
828
829    let elements: HeapObject = ReloadElements(sortState);
830    const Load: LoadFn = GetLoadFn(sortState);
831
832    const pending_runs: FixedArray =
833        unsafe_cast<FixedArray>(sortState[kPendingRunsIdx]);
834    let base_a: Smi = GetPendingRunBase(pending_runs, i);
835    let length_a: Smi = GetPendingRunLength(pending_runs, i);
836    let base_b: Smi = GetPendingRunBase(pending_runs, i + 1);
837    let length_b: Smi = GetPendingRunLength(pending_runs, i + 1);
838    assert(length_a > 0 && length_b > 0);
839    assert(base_a + length_a == base_b);
840
841    // Record the length of the combined runs; if i is the 3rd-last run now,
842    // also slide over the last run (which isn't involved in this merge).
843    // The current run i + 1 goes away in any case.
844    SetPendingRunLength(pending_runs, i, length_a + length_b);
845    if (i == stack_size - 3) {
846      const base: Smi = GetPendingRunBase(pending_runs, i + 2);
847      const length: Smi = GetPendingRunLength(pending_runs, i + 2);
848      SetPendingRunBase(pending_runs, i + 1, base);
849      SetPendingRunLength(pending_runs, i + 1, length);
850    }
851    SetPendingRunsSize(sortState, stack_size - 1);
852
853    try {
854      // Where does b start in a? Elements in a before that can be ignored,
855      // because they are already in place.
856      const key_right: Object =
857          CallLoad(context, sortState, Load, elements, base_b)
858      otherwise Bailout;
859      const k: Smi = CallGallopRight(
860          context, sortState, Load, key_right, base_a, length_a, 0, False)
861      otherwise Bailout;
862      elements = ReloadElements(sortState);
863      assert(k >= 0);
864
865      base_a = base_a + k;
866      length_a = length_a - k;
867      if (length_a == 0) return kSuccess;
868      assert(length_a > 0);
869
870      // Where does a end in b? Elements in b after that can be ignored,
871      // because they are already in place.
872      let key_left: Object =
873          CallLoad(context, sortState, Load, elements, base_a + length_a - 1)
874      otherwise Bailout;
875      length_b = CallGallopLeft(
876          context, sortState, Load, key_left, base_b, length_b, length_b - 1,
877          False) otherwise Bailout;
878      elements = ReloadElements(sortState);
879      assert(length_b >= 0);
880      if (length_b == 0) return kSuccess;
881
882      // Merge what remains of the runs, using a temp array with
883      // min(length_a, length_b) elements.
884      if (length_a <= length_b) {
885        MergeLow(context, sortState, base_a, length_a, base_b, length_b)
886        otherwise Bailout;
887      } else {
888        MergeHigh(context, sortState, base_a, length_a, base_b, length_b)
889        otherwise Bailout;
890      }
891      return kSuccess;
892    }
893    label Bailout {
894      return Failure(sortState);
895    }
896  }
897
898  macro LoadElementsOrTempArray(
899      useTempArray: Boolean, sortState: FixedArray): HeapObject {
900    return useTempArray == True ? GetTempArray(sortState) :
901                                  ReloadElements(sortState);
902  }
903
904  // Locates the proper position of key in a sorted array; if the array contains
905  // an element equal to key, return the position immediately to the left of
906  // the leftmost equal element. (GallopRight does the same except returns the
907  // position to the right of the rightmost equal element (if any)).
908  //
909  // The array is sorted with "length" elements, starting at "base".
910  // "length" must be > 0.
911  //
912  // "hint" is an index at which to begin the search, 0 <= hint < n. The closer
913  // hint is to the final result, the faster this runs.
914  //
915  // The return value is the int offset in 0..length such that
916  //
917  // array[base + offset] < key <= array[base + offset + 1]
918  //
919  // pretending that array[base - 1] is minus infinity and array[base + len]
920  // is plus infinity. In other words, key belongs at index base + k.
921  builtin GallopLeft(
922      context: Context, sortState: FixedArray, Load: LoadFn, key: Object,
923      base: Smi, length: Smi, hint: Smi, useTempArray: Boolean): Smi {
924    assert(length > 0 && base >= 0);
925    assert(0 <= hint && hint < length);
926
927    let last_ofs: Smi = 0;
928    let offset: Smi = 1;
929
930    try {
931      const base_hint_element: Object = CallLoad(
932          context, sortState, Load,
933          LoadElementsOrTempArray(useTempArray, sortState), base + hint)
934      otherwise Bailout;
935      let order: Number =
936          CallCompareFn(context, sortState, base_hint_element, key)
937      otherwise Bailout;
938
939      if (order < 0) {
940        // a[base + hint] < key: gallop right, until
941        // a[base + hint + last_ofs] < key <= a[base + hint + offset].
942
943        // a[base + length - 1] is highest.
944        let max_ofs: Smi = length - hint;
945        while (offset < max_ofs) {
946          const offset_element: Object = CallLoad(
947              context, sortState, Load,
948              LoadElementsOrTempArray(useTempArray, sortState),
949              base + hint + offset)
950          otherwise Bailout;
951          order = CallCompareFn(context, sortState, offset_element, key)
952          otherwise Bailout;
953
954          // a[base + hint + offset] >= key? Break.
955          if (order >= 0) break;
956
957          last_ofs = offset;
958          offset = (offset << 1) + 1;
959
960          // Integer overflow.
961          if (offset <= 0) offset = max_ofs;
962        }
963
964        if (offset > max_ofs) offset = max_ofs;
965
966        // Translate back to positive offsets relative to base.
967        last_ofs = last_ofs + hint;
968        offset = offset + hint;
969      } else {
970        // key <= a[base + hint]: gallop left, until
971        // a[base + hint - offset] < key <= a[base + hint - last_ofs].
972        assert(order >= 0);
973
974        // a[base + hint] is lowest.
975        let max_ofs: Smi = hint + 1;
976        while (offset < max_ofs) {
977          const offset_element: Object = CallLoad(
978              context, sortState, Load,
979              LoadElementsOrTempArray(useTempArray, sortState),
980              base + hint - offset)
981          otherwise Bailout;
982          order = CallCompareFn(context, sortState, offset_element, key)
983          otherwise Bailout;
984
985          if (order < 0) break;
986
987          last_ofs = offset;
988          offset = (offset << 1) + 1;
989
990          // Integer overflow.
991          if (offset <= 0) offset = max_ofs;
992        }
993
994        if (offset > max_ofs) offset = max_ofs;
995
996        // Translate back to positive offsets relative to base.
997        const tmp: Smi = last_ofs;
998        last_ofs = hint - offset;
999        offset = hint - tmp;
1000      }
1001
1002      assert(-1 <= last_ofs && last_ofs < offset && offset <= length);
1003
1004      // Now a[base+last_ofs] < key <= a[base+offset], so key belongs somewhere
1005      // to the right of last_ofs but no farther right than offset. Do a binary
1006      // search, with invariant:
1007      //   a[base + last_ofs - 1] < key <= a[base + offset].
1008      last_ofs++;
1009      while (last_ofs < offset) {
1010        const m: Smi = last_ofs + ((offset - last_ofs) >>> 1);
1011
1012        const base_m_element: Object = CallLoad(
1013            context, sortState, Load,
1014            LoadElementsOrTempArray(useTempArray, sortState), base + m)
1015        otherwise Bailout;
1016        order = CallCompareFn(context, sortState, base_m_element, key)
1017        otherwise Bailout;
1018
1019        if (order < 0) {
1020          last_ofs = m + 1;  // a[base + m] < key.
1021        } else {
1022          offset = m;  // key <= a[base + m].
1023        }
1024      }
1025      // so a[base + offset - 1] < key <= a[base + offset].
1026      assert(last_ofs == offset);
1027      assert(0 <= offset && offset <= length);
1028      return offset;
1029    }
1030    label Bailout {
1031      return Failure(sortState);
1032    }
1033  }
1034
1035  // Exactly like GallopLeft, except that if key already exists in
1036  // [base, base + length), finds the position immediately to the right of the
1037  // rightmost equal value.
1038  //
1039  // The return value is the int offset in 0..length such that
1040  //
1041  // array[base + offset - 1] <= key < array[base + offset]
1042  //
1043  // or kFailure on error.
1044  builtin GallopRight(
1045      context: Context, sortState: FixedArray, Load: LoadFn, key: Object,
1046      base: Smi, length: Smi, hint: Smi, useTempArray: Boolean): Smi {
1047    assert(length > 0 && base >= 0);
1048    assert(0 <= hint && hint < length);
1049
1050    let last_ofs: Smi = 0;
1051    let offset: Smi = 1;
1052
1053    try {
1054      const base_hint_element: Object = CallLoad(
1055          context, sortState, Load,
1056          LoadElementsOrTempArray(useTempArray, sortState), base + hint)
1057      otherwise Bailout;
1058      let order: Number =
1059          CallCompareFn(context, sortState, key, base_hint_element)
1060      otherwise Bailout;
1061
1062      if (order < 0) {
1063        // key < a[base + hint]: gallop left, until
1064        // a[base + hint - offset] <= key < a[base + hint - last_ofs].
1065
1066        // a[base + hint] is lowest.
1067        let max_ofs: Smi = hint + 1;
1068        while (offset < max_ofs) {
1069          const offset_element: Object = CallLoad(
1070              context, sortState, Load,
1071              LoadElementsOrTempArray(useTempArray, sortState),
1072              base + hint - offset)
1073          otherwise Bailout;
1074          order = CallCompareFn(context, sortState, key, offset_element)
1075          otherwise Bailout;
1076
1077          if (order >= 0) break;
1078
1079          last_ofs = offset;
1080          offset = (offset << 1) + 1;
1081
1082          // Integer overflow.
1083          if (offset <= 0) offset = max_ofs;
1084        }
1085
1086        if (offset > max_ofs) offset = max_ofs;
1087
1088        // Translate back to positive offsets relative to base.
1089        const tmp: Smi = last_ofs;
1090        last_ofs = hint - offset;
1091        offset = hint - tmp;
1092      } else {
1093        // a[base + hint] <= key: gallop right, until
1094        // a[base + hint + last_ofs] <= key < a[base + hint + offset].
1095
1096        // a[base + length - 1] is highest.
1097        let max_ofs: Smi = length - hint;
1098        while (offset < max_ofs) {
1099          const offset_element: Object = CallLoad(
1100              context, sortState, Load,
1101              LoadElementsOrTempArray(useTempArray, sortState),
1102              base + hint + offset)
1103          otherwise Bailout;
1104          order = CallCompareFn(context, sortState, key, offset_element)
1105          otherwise Bailout;
1106
1107          // a[base + hint + ofs] <= key.
1108          if (order < 0) break;
1109
1110          last_ofs = offset;
1111          offset = (offset << 1) + 1;
1112
1113          // Integer overflow.
1114          if (offset <= 0) offset = max_ofs;
1115        }
1116
1117        if (offset > max_ofs) offset = max_ofs;
1118
1119        // Translate back to positive offests relative to base.
1120        last_ofs = last_ofs + hint;
1121        offset = offset + hint;
1122      }
1123      assert(-1 <= last_ofs && last_ofs < offset && offset <= length);
1124
1125      // Now a[base + last_ofs] <= key < a[base + ofs], so key belongs
1126      // somewhere to the right of last_ofs but no farther right than ofs.
1127      // Do a binary search, with invariant
1128      // a[base + last_ofs - 1] < key <= a[base + ofs].
1129      last_ofs++;
1130      while (last_ofs < offset) {
1131        const m: Smi = last_ofs + ((offset - last_ofs) >>> 1);
1132
1133        const base_m_element: Object = CallLoad(
1134            context, sortState, Load,
1135            LoadElementsOrTempArray(useTempArray, sortState), base + m)
1136        otherwise Bailout;
1137        order = CallCompareFn(context, sortState, key, base_m_element)
1138        otherwise Bailout;
1139
1140        if (order < 0) {
1141          offset = m;  // key < a[base + m].
1142        } else {
1143          last_ofs = m + 1;  // a[base + m] <= key.
1144        }
1145      }
1146      // so a[base + offset - 1] <= key < a[base + offset].
1147      assert(last_ofs == offset);
1148      assert(0 <= offset && offset <= length);
1149      return offset;
1150    }
1151    label Bailout {
1152      return Failure(sortState);
1153    }
1154  }
1155
1156  // Copies a single element inside the array/object (NOT the temp_array).
1157  macro CopyElement(
1158      context: Context, sortState: FixedArray, Load: LoadFn, Store: StoreFn,
1159      elements: HeapObject, from: Smi, to: Smi)
1160  labels Bailout {
1161    const element: Object = CallLoad(context, sortState, Load, elements, from)
1162    otherwise Bailout;
1163    CallStore(context, sortState, Store, elements, to, element)
1164    otherwise Bailout;
1165  }
1166
1167  // Merge the length_a elements starting at base_a with the length_b elements
1168  // starting at base_b in a stable way, in-place. length_a and length_b must
1169  // be > 0, and base_a + length_a == base_b. Must also have that
1170  // array[base_b] < array[base_a],
1171  // that array[base_a + length_a - 1] belongs at the end of the merge,
1172  // and should have length_a <= length_b.
1173  macro MergeLow(
1174      context: Context, sortState: FixedArray, baseA: Smi, lengthA: Smi,
1175      baseB: Smi, lengthB: Smi)
1176  labels Bailout {
1177    assert(0 < lengthA && 0 < lengthB);
1178    assert(0 <= baseA && 0 < baseB);
1179    assert(baseA + lengthA == baseB);
1180
1181    let length_a: Smi = lengthA;
1182    let length_b: Smi = lengthB;
1183
1184    let elements: HeapObject = ReloadElements(sortState);
1185    const LoadF: LoadFn = GetLoadFn(sortState);
1186    const Store: StoreFn = GetStoreFn(sortState);
1187
1188    const temp_array: FixedArray = GetTempArray(sortState, length_a);
1189    CopyToTempArray(
1190        context, sortState, LoadF, elements, baseA, temp_array, 0, length_a)
1191    otherwise Bailout;
1192
1193    let dest: Smi = baseA;
1194    let cursor_temp: Smi = 0;
1195    let cursor_b: Smi = baseB;
1196
1197    CopyElement(context, sortState, LoadF, Store, elements, cursor_b++, dest++)
1198    otherwise Bailout;
1199
1200    try {
1201      if (--length_b == 0) goto Succeed;
1202      if (length_a == 1) goto CopyB;
1203
1204      let min_gallop: Smi = unsafe_cast<Smi>(sortState[kMinGallopIdx]);
1205      // TODO(szuend): Replace with something that does not have a runtime
1206      //               overhead as soon as its available in Torque.
1207      while (Int32TrueConstant()) {
1208        let nof_wins_a: Smi = 0;  // # of times A won in a row.
1209        let nof_wins_b: Smi = 0;  // # of times B won in a row.
1210
1211        // Do the straightforward thing until (if ever) one run appears to
1212        // win consistently.
1213        // TODO(szuend): Replace with something that does not have a runtime
1214        //               overhead as soon as its available in Torque.
1215        while (Int32TrueConstant()) {
1216          assert(length_a > 1 && length_b > 0);
1217
1218          let element_b: Object =
1219              CallLoad(context, sortState, LoadF, elements, cursor_b)
1220          otherwise Bailout;
1221          let order: Number = CallCompareFn(
1222              context, sortState, element_b, temp_array[cursor_temp])
1223          otherwise Bailout;
1224          elements = ReloadElements(sortState);
1225
1226          if (order < 0) {
1227            CopyElement(
1228                context, sortState, LoadF, Store, elements, cursor_b, dest)
1229            otherwise Bailout;
1230
1231            ++cursor_b;
1232            ++dest;
1233            ++nof_wins_b;
1234            --length_b;
1235            nof_wins_a = 0;
1236
1237            if (length_b == 0) goto Succeed;
1238            if (nof_wins_b >= min_gallop) break;
1239          } else {
1240            CallStore(
1241                context, sortState, Store, elements, dest,
1242                temp_array[cursor_temp])
1243            otherwise Bailout;
1244
1245            ++cursor_temp;
1246            ++dest;
1247            ++nof_wins_a;
1248            --length_a;
1249            nof_wins_b = 0;
1250
1251            if (length_a == 1) goto CopyB;
1252            if (nof_wins_a >= min_gallop) break;
1253          }
1254        }
1255
1256        // One run is winning so consistently that galloping may be a huge win.
1257        // So try that, and continue galloping until (if ever) neither run
1258        // appears to be winning consistently anymore.
1259        ++min_gallop;
1260        let first_iteration: bool = true;
1261        while (nof_wins_a >= kMinGallopWins || nof_wins_b >= kMinGallopWins ||
1262               first_iteration) {
1263          first_iteration = false;
1264          assert(length_a > 1 && length_b > 0);
1265
1266          min_gallop = SmiMax(1, min_gallop - 1);
1267          sortState[kMinGallopIdx] = min_gallop;
1268
1269          let key_right: Object =
1270              CallLoad(context, sortState, LoadF, elements, cursor_b)
1271          otherwise Bailout;
1272          nof_wins_a = CallGallopRight(
1273              context, sortState, Load<TempArrayElements>, key_right,
1274              cursor_temp, length_a, 0, True) otherwise Bailout;
1275          elements = ReloadElements(sortState);
1276          assert(nof_wins_a >= 0);
1277
1278          if (nof_wins_a > 0) {
1279            CallCopyFromTempArray(
1280                context, sortState, elements, dest, temp_array, cursor_temp,
1281                nof_wins_a) otherwise Bailout;
1282            dest = dest + nof_wins_a;
1283            cursor_temp = cursor_temp + nof_wins_a;
1284            length_a = length_a - nof_wins_a;
1285
1286            if (length_a == 1) goto CopyB;
1287
1288            // length_a == 0 is impossible now if the comparison function is
1289            // consistent, but we can't assume that it is.
1290            if (length_a == 0) goto Succeed;
1291          }
1292          CopyElement(
1293              context, sortState, LoadF, Store, elements, cursor_b++, dest++)
1294          otherwise Bailout;
1295          if (--length_b == 0) goto Succeed;
1296
1297          nof_wins_b = CallGallopLeft(
1298              context, sortState, LoadF, temp_array[cursor_temp], cursor_b,
1299              length_b, 0, False)
1300          otherwise Bailout;
1301          elements = ReloadElements(sortState);
1302          assert(nof_wins_b >= 0);
1303          if (nof_wins_b > 0) {
1304            CallCopyWithinSortArray(
1305                context, sortState, elements, cursor_b, dest, nof_wins_b)
1306            otherwise Bailout;
1307
1308            dest = dest + nof_wins_b;
1309            cursor_b = cursor_b + nof_wins_b;
1310            length_b = length_b - nof_wins_b;
1311
1312            if (length_b == 0) goto Succeed;
1313          }
1314          CallStore(
1315              context, sortState, Store, elements, dest++,
1316              temp_array[cursor_temp++])
1317          otherwise Bailout;
1318          if (--length_a == 1) goto CopyB;
1319        }
1320        ++min_gallop;  // Penalize it for leaving galloping mode
1321        sortState[kMinGallopIdx] = min_gallop;
1322      }
1323    }
1324    label Succeed {
1325      if (length_a > 0) {
1326        CallCopyFromTempArray(
1327            context, sortState, elements, dest, temp_array, cursor_temp,
1328            length_a) otherwise Bailout;
1329      }
1330    }
1331    label CopyB {
1332      assert(length_a == 1 && length_b > 0);
1333      // The last element of run A belongs at the end of the merge.
1334      CallCopyWithinSortArray(
1335          context, sortState, elements, cursor_b, dest, length_b)
1336      otherwise Bailout;
1337      CallStore(
1338          context, sortState, Store, elements, dest + length_b,
1339          temp_array[cursor_temp])
1340      otherwise Bailout;
1341    }
1342  }
1343
1344  // Merge the length_a elements starting at base_a with the length_b elements
1345  // starting at base_b in a stable way, in-place. length_a and length_b must
1346  // be > 0. Must also have that array[base_a + length_a - 1] belongs at the
1347  // end of the merge and should have length_a >= length_b.
1348  macro MergeHigh(
1349      context: Context, sortState: FixedArray, baseA: Smi, lengthA: Smi,
1350      baseB: Smi, lengthB: Smi)
1351  labels Bailout {
1352    assert(0 < lengthA && 0 < lengthB);
1353    assert(0 <= baseA && 0 < baseB);
1354    assert(baseA + lengthA == baseB);
1355
1356    let length_a: Smi = lengthA;
1357    let length_b: Smi = lengthB;
1358
1359    let elements: HeapObject = ReloadElements(sortState);
1360    const LoadF: LoadFn = GetLoadFn(sortState);
1361    const Store: StoreFn = GetStoreFn(sortState);
1362
1363    const temp_array: FixedArray = GetTempArray(sortState, length_b);
1364    CopyToTempArray(
1365        context, sortState, LoadF, elements, baseB, temp_array, 0, length_b)
1366    otherwise Bailout;
1367
1368    // MergeHigh merges the two runs backwards.
1369    let dest: Smi = baseB + length_b - 1;
1370    let cursor_temp: Smi = length_b - 1;
1371    let cursor_a: Smi = baseA + length_a - 1;
1372
1373    CopyElement(context, sortState, LoadF, Store, elements, cursor_a--, dest--)
1374    otherwise Bailout;
1375
1376    try {
1377      if (--length_a == 0) goto Succeed;
1378      if (length_b == 1) goto CopyA;
1379
1380      let min_gallop: Smi = unsafe_cast<Smi>(sortState[kMinGallopIdx]);
1381      // TODO(szuend): Replace with something that does not have a runtime
1382      //               overhead as soon as its available in Torque.
1383      while (Int32TrueConstant()) {
1384        let nof_wins_a: Smi = 0;  // # of times A won in a row.
1385        let nof_wins_b: Smi = 0;  // # of times B won in a row.
1386
1387        // Do the straightforward thing until (if ever) one run appears to
1388        // win consistently.
1389        // TODO(szuend): Replace with something that does not have a runtime
1390        //               overhead as soon as its available in Torque.
1391        while (Int32TrueConstant()) {
1392          assert(length_a > 0 && length_b > 1);
1393
1394          let element_a: Object =
1395              CallLoad(context, sortState, LoadF, elements, cursor_a)
1396          otherwise Bailout;
1397          let order: Number = CallCompareFn(
1398              context, sortState, temp_array[cursor_temp], element_a)
1399          otherwise Bailout;
1400          elements = ReloadElements(sortState);
1401
1402          if (order < 0) {
1403            CopyElement(
1404                context, sortState, LoadF, Store, elements, cursor_a, dest)
1405            otherwise Bailout;
1406
1407            --cursor_a;
1408            --dest;
1409            ++nof_wins_a;
1410            --length_a;
1411            nof_wins_b = 0;
1412
1413            if (length_a == 0) goto Succeed;
1414            if (nof_wins_a >= min_gallop) break;
1415          } else {
1416            CallStore(
1417                context, sortState, Store, elements, dest,
1418                temp_array[cursor_temp])
1419            otherwise Bailout;
1420
1421            --cursor_temp;
1422            --dest;
1423            ++nof_wins_b;
1424            --length_b;
1425            nof_wins_a = 0;
1426
1427            if (length_b == 1) goto CopyA;
1428            if (nof_wins_b >= min_gallop) break;
1429          }
1430        }
1431
1432        // One run is winning so consistently that galloping may be a huge win.
1433        // So try that, and continue galloping until (if ever) neither run
1434        // appears to be winning consistently anymore.
1435        ++min_gallop;
1436        let first_iteration: bool = true;
1437        while (nof_wins_a >= kMinGallopWins || nof_wins_b >= kMinGallopWins ||
1438               first_iteration) {
1439          first_iteration = false;
1440
1441          assert(length_a > 0 && length_b > 1);
1442
1443          min_gallop = SmiMax(1, min_gallop - 1);
1444          sortState[kMinGallopIdx] = min_gallop;
1445
1446          let k: Smi = CallGallopRight(
1447              context, sortState, LoadF, temp_array[cursor_temp], baseA,
1448              length_a, length_a - 1, False)
1449          otherwise Bailout;
1450          elements = ReloadElements(sortState);
1451          assert(k >= 0);
1452          nof_wins_a = length_a - k;
1453
1454          if (nof_wins_a > 0) {
1455            dest = dest - nof_wins_a;
1456            cursor_a = cursor_a - nof_wins_a;
1457            CallCopyWithinSortArray(
1458                context, sortState, elements, cursor_a + 1, dest + 1,
1459                nof_wins_a)
1460            otherwise Bailout;
1461
1462            length_a = length_a - nof_wins_a;
1463            if (length_a == 0) goto Succeed;
1464          }
1465          CallStore(
1466              context, sortState, Store, elements, dest--,
1467              temp_array[cursor_temp--])
1468          otherwise Bailout;
1469          if (--length_b == 1) goto CopyA;
1470
1471          let key: Object =
1472              CallLoad(context, sortState, LoadF, elements, cursor_a)
1473          otherwise Bailout;
1474          k = CallGallopLeft(
1475              context, sortState, Load<TempArrayElements>, key, 0, length_b,
1476              length_b - 1, True) otherwise Bailout;
1477          elements = ReloadElements(sortState);
1478          assert(k >= 0);
1479          nof_wins_b = length_b - k;
1480
1481          if (nof_wins_b > 0) {
1482            dest = dest - nof_wins_b;
1483            cursor_temp = cursor_temp - nof_wins_b;
1484            CallCopyFromTempArray(
1485                context, sortState, elements, dest + 1, temp_array,
1486                cursor_temp + 1, nof_wins_b) otherwise Bailout;
1487
1488            length_b = length_b - nof_wins_b;
1489            if (length_b == 1) goto CopyA;
1490
1491            // length_b == 0 is impossible now if the comparison function is
1492            // consistent, but we can't assume that it is.
1493            if (length_b == 0) goto Succeed;
1494          }
1495          CopyElement(
1496              context, sortState, LoadF, Store, elements, cursor_a--, dest--)
1497          otherwise Bailout;
1498          if (--length_a == 0) goto Succeed;
1499        }
1500        ++min_gallop;
1501        sortState[kMinGallopIdx] = min_gallop;
1502      }
1503    }
1504    label Succeed {
1505      if (length_b > 0) {
1506        assert(length_a == 0);
1507        CallCopyFromTempArray(
1508            context, sortState, elements, dest - (length_b - 1), temp_array, 0,
1509            length_b) otherwise Bailout;
1510      }
1511    }
1512    label CopyA {
1513      assert(length_b == 1 && length_a > 0);
1514
1515      // The first element of run B belongs at the front of the merge.
1516      dest = dest - length_a;
1517      cursor_a = cursor_a - length_a;
1518      CallCopyWithinSortArray(
1519          context, sortState, elements, cursor_a + 1, dest + 1, length_a)
1520      otherwise Bailout;
1521      CallStore(
1522          context, sortState, Store, elements, dest, temp_array[cursor_temp])
1523      otherwise Bailout;
1524    }
1525  }
1526
1527  // Compute a good value for the minimum run length; natural runs shorter than
1528  // this are boosted artificially via binary insertion sort.
1529  //
1530  // If n < 64, return n (it's too small to bother with fancy stuff).
1531  // Else if n is an exact power of 2, return 32.
1532  // Else return an int k, 32 <= k <= 64, such that n/k is close to, but
1533  // strictly less than, an exact power of 2.
1534  //
1535  // See listsort.txt for more info.
1536  macro ComputeMinRunLength(nArg: Smi): Smi {
1537    let n: Smi = nArg;
1538    let r: Smi = 0;  // Becomes 1 if any 1 bits are shifted off.
1539
1540    assert(n >= 0);
1541    while (n >= 64) {
1542      r = r | (n & 1);
1543      n = n >>> 1;
1544    }
1545
1546    const min_run_length: Smi = n + r;
1547    assert(nArg < 64 || (32 <= min_run_length && min_run_length <= 64));
1548    return min_run_length;
1549  }
1550
1551  // Returns true iff run_length(n - 2) > run_length(n - 1) + run_length(n).
1552  macro RunInvariantEstablished(pendingRuns: FixedArray, n: Smi): bool {
1553    if (n < 2) return true;
1554
1555    const run_length_n: Smi = GetPendingRunLength(pendingRuns, n);
1556    const run_length_nm: Smi = GetPendingRunLength(pendingRuns, n - 1);
1557    const run_length_nmm: Smi = GetPendingRunLength(pendingRuns, n - 2);
1558
1559    return run_length_nmm > run_length_nm + run_length_n;
1560  }
1561
1562  // Examines the stack of runs waiting to be merged, merging adjacent runs
1563  // until the stack invariants are re-established:
1564  //
1565  //   1. run_length(i - 3) > run_length(i - 2) + run_length(i - 1)
1566  //   2. run_length(i - 2) > run_length(i - 1)
1567  //
1568  // TODO(szuend): Remove unnecessary loads. This macro was refactored to
1569  //               improve readability, introducing unnecessary loads in the
1570  //               process. Determine if all these extra loads are ok.
1571  macro MergeCollapse(context: Context, sortState: FixedArray)
1572  labels Bailout {
1573    const pending_runs: FixedArray =
1574        unsafe_cast<FixedArray>(sortState[kPendingRunsIdx]);
1575
1576    // Reload the stack size because MergeAt might change it.
1577    while (GetPendingRunsSize(sortState) > 1) {
1578      let n: Smi = GetPendingRunsSize(sortState) - 2;
1579
1580      if (!RunInvariantEstablished(pending_runs, n + 1) ||
1581          !RunInvariantEstablished(pending_runs, n)) {
1582        if (GetPendingRunLength(pending_runs, n - 1) <
1583            GetPendingRunLength(pending_runs, n + 1)) {
1584          --n;
1585        }
1586
1587        CallMergeAt(context, sortState, n) otherwise Bailout;
1588      } else if (
1589          GetPendingRunLength(pending_runs, n) <=
1590          GetPendingRunLength(pending_runs, n + 1)) {
1591        CallMergeAt(context, sortState, n) otherwise Bailout;
1592      } else {
1593        break;
1594      }
1595    }
1596  }
1597
1598  // Regardless of invariants, merge all runs on the stack until only one
1599  // remains. This is used at the end of the mergesort.
1600  macro MergeForceCollapse(context: Context, sortState: FixedArray)
1601  labels Bailout {
1602    let pending_runs: FixedArray =
1603        unsafe_cast<FixedArray>(sortState[kPendingRunsIdx]);
1604
1605    // Reload the stack size becuase MergeAt might change it.
1606    while (GetPendingRunsSize(sortState) > 1) {
1607      let n: Smi = GetPendingRunsSize(sortState) - 2;
1608
1609      if (n > 0 &&
1610          GetPendingRunLength(pending_runs, n - 1) <
1611              GetPendingRunLength(pending_runs, n + 1)) {
1612        --n;
1613      }
1614      CallMergeAt(context, sortState, n) otherwise Bailout;
1615    }
1616  }
1617
1618  macro InitializeSortState(sortState: FixedArray) {
1619    sortState[kMinGallopIdx] = SmiConstant(kMinGallopWins);
1620    sortState[kTempArraySizeIdx] = SmiConstant(0);
1621
1622    SetPendingRunsSize(sortState, 0);
1623    let pending_runs: FixedArray =
1624        AllocateZeroedFixedArray(convert<intptr>(kMaxMergePending));
1625    FillFixedArrayWithSmiZero(pending_runs, kMaxMergePending);
1626    sortState[kPendingRunsIdx] = pending_runs;
1627  }
1628
1629  macro InitializeSortStateAccessor<Accessor : type>(sortState: FixedArray) {
1630    sortState[kAccessorIdx] = kFastElementsAccessorId;
1631    sortState[kLoadFnIdx] = Load<Accessor>;
1632    sortState[kStoreFnIdx] = Store<Accessor>;
1633    sortState[kCanUseSameAccessorFnIdx] = CanUseSameAccessor<Accessor>;
1634  }
1635
1636  InitializeSortStateAccessor<GenericElementsAccessor>(sortState: FixedArray) {
1637    sortState[kAccessorIdx] = kGenericElementsAccessorId;
1638    sortState[kLoadFnIdx] = Load<GenericElementsAccessor>;
1639    sortState[kStoreFnIdx] = Store<GenericElementsAccessor>;
1640    sortState[kCanUseSameAccessorFnIdx] =
1641        CanUseSameAccessor<GenericElementsAccessor>;
1642  }
1643
1644  macro ArrayTimSortImpl(context: Context, sortState: FixedArray, length: Smi)
1645  labels Bailout {
1646    InitializeSortState(sortState);
1647
1648    if (length < 2) return;
1649    let remaining: Smi = length;
1650
1651    // March over the array once, left to right, finding natural runs,
1652    // and extending short natural runs to minrun elements.
1653    let low: Smi = 0;
1654    const min_run_length: Smi = ComputeMinRunLength(remaining);
1655    while (remaining != 0) {
1656      let current_run_length: Smi =
1657          CountAndMakeRun(context, sortState, low, low + remaining)
1658      otherwise Bailout;
1659
1660      // If the run is short, extend it to min(min_run_length, remaining).
1661      if (current_run_length < min_run_length) {
1662        const forced_run_length: Smi = SmiMin(min_run_length, remaining);
1663        BinaryInsertionSort(
1664            context, sortState, low, low + current_run_length,
1665            low + forced_run_length);
1666        EnsureSuccess(sortState) otherwise Bailout;
1667        current_run_length = forced_run_length;
1668      }
1669
1670      // Push run onto pending-runs stack, and maybe merge.
1671      PushRun(sortState, low, current_run_length);
1672
1673      MergeCollapse(context, sortState) otherwise Bailout;
1674
1675      // Advance to find next run.
1676      low = low + current_run_length;
1677      remaining = remaining - current_run_length;
1678    }
1679
1680    MergeForceCollapse(context, sortState) otherwise Bailout;
1681    assert(GetPendingRunsSize(sortState) == 1);
1682    assert(
1683        GetPendingRunLength(
1684            unsafe_cast<FixedArray>(sortState[kPendingRunsIdx]), 0) == length);
1685  }
1686
1687  builtin ArrayTimSort(
1688      context: Context, sortState: FixedArray, length: Smi): Object {
1689    try {
1690      ArrayTimSortImpl(context, sortState, length)
1691      otherwise Slow;
1692    }
1693    label Slow {
1694      if (sortState[kAccessorIdx] == kGenericElementsAccessorId) {
1695        // We were already on the slow path. This must not happen.
1696        unreachable;
1697      }
1698      sortState[kBailoutStatusIdx] = kSuccess;
1699
1700      InitializeSortStateAccessor<GenericElementsAccessor>(sortState);
1701      ArrayTimSort(context, sortState, length);
1702    }
1703    return kSuccess;
1704  }
1705
1706  // For compatibility with JSC, we also sort elements inherited from
1707  // the prototype chain on non-Array objects.
1708  // We do this by copying them to this object and sorting only
1709  // own elements. This is not very efficient, but sorting with
1710  // inherited elements happens very, very rarely, if at all.
1711  // The specification allows "implementation dependent" behavior
1712  // if an element on the prototype chain has an element that
1713  // might interact with sorting.
1714  //
1715  // We also move all non-undefined elements to the front of the
1716  // array and move the undefineds after that. Holes are removed.
1717  // This happens for Array as well as non-Array objects.
1718  extern runtime PrepareElementsForSort(Context, Object, Number): Smi;
1719  extern macro FillFixedArrayWithSmiZero(FixedArray, Smi);
1720
1721  // https://tc39.github.io/ecma262/#sec-array.prototype.sort
1722  javascript builtin ArrayPrototypeSort(
1723      context: Context, receiver: Object, ...arguments): Object {
1724    // 1. If comparefn is not undefined and IsCallable(comparefn) is false,
1725    //    throw a TypeError exception.
1726    const comparefnObj: Object = arguments[0];
1727    if (comparefnObj != Undefined && !TaggedIsCallable(comparefnObj)) {
1728      ThrowTypeError(context, kBadSortComparisonFunction, comparefnObj);
1729    }
1730
1731    // 2. Let obj be ? ToObject(this value).
1732    const obj: JSReceiver = ToObject(context, receiver);
1733
1734    const sort_state: FixedArray = AllocateZeroedFixedArray(kSortStateSize);
1735    FillFixedArrayWithSmiZero(sort_state, SmiTag(kSortStateSize));
1736
1737    sort_state[kReceiverIdx] = obj;
1738    sort_state[kUserCmpFnIdx] = comparefnObj;
1739    sort_state[kSortComparePtrIdx] =
1740        comparefnObj != Undefined ? SortCompareUserFn : SortCompareDefault;
1741    sort_state[kBailoutStatusIdx] = kSuccess;
1742
1743    // 3. Let len be ? ToLength(? Get(obj, "length")).
1744    const len: Number =
1745        ToLength_Inline(context, GetProperty(context, obj, 'length'));
1746    if (len < 2) return receiver;
1747
1748    // TODO(szuend): Investigate performance tradeoff of skipping this step
1749    //               for PACKED_* and handling Undefineds during sorting.
1750    const nofNonUndefined: Smi = PrepareElementsForSort(context, obj, len);
1751    assert(nofNonUndefined <= len);
1752
1753    let map: Map = obj.map;
1754    sort_state[kInitialReceiverMapIdx] = map;
1755    sort_state[kInitialReceiverLengthIdx] = len;
1756
1757    try {
1758      const a: JSArray = cast<JSArray>(obj) otherwise slow;
1759      const elementsKind: ElementsKind = map.elements_kind;
1760      if (!IsFastElementsKind(elementsKind)) goto slow;
1761
1762      if (IsDoubleElementsKind(elementsKind)) {
1763        InitializeSortStateAccessor<FastDoubleElements>(sort_state);
1764      } else if (elementsKind == PACKED_SMI_ELEMENTS) {
1765        InitializeSortStateAccessor<FastPackedSmiElements>(sort_state);
1766      } else {
1767        InitializeSortStateAccessor<FastSmiOrObjectElements>(sort_state);
1768      }
1769      ArrayTimSort(context, sort_state, nofNonUndefined);
1770    }
1771    label slow {
1772      if (map.elements_kind == DICTIONARY_ELEMENTS && IsExtensibleMap(map) &&
1773          !IsCustomElementsReceiverInstanceType(map.instance_type)) {
1774        InitializeSortStateAccessor<DictionaryElements>(sort_state);
1775      } else {
1776        InitializeSortStateAccessor<GenericElementsAccessor>(sort_state);
1777      }
1778      ArrayTimSort(context, sort_state, nofNonUndefined);
1779    }
1780
1781    return receiver;
1782  }
1783}
1784
1785