• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
16namespace array {
17class SortState extends HeapObject {
18  macro Compare(implicit context: Context)(x: JSAny, y: JSAny): Number {
19    const sortCompare: CompareBuiltinFn = this.sortComparePtr;
20    return sortCompare(context, this.userCmpFn, x, y);
21  }
22
23  macro CheckAccessor(implicit context: Context)(): void labels Bailout {
24    if (!IsFastJSArray(this.receiver, context)) goto Bailout;
25
26    const canUseSameAccessorFn: CanUseSameAccessorFn =
27        this.canUseSameAccessorFn;
28
29    if (!canUseSameAccessorFn(
30            context, this.receiver, this.initialReceiverMap,
31            this.initialReceiverLength)) {
32      goto Bailout;
33    }
34  }
35
36  macro ResetToGenericAccessor(): void {
37    this.loadFn = Load<GenericElementsAccessor>;
38    this.storeFn = Store<GenericElementsAccessor>;
39    this.deleteFn = Delete<GenericElementsAccessor>;
40  }
41
42  // The receiver of the Array.p.sort call.
43  receiver: JSReceiver;
44
45  // The initial map and length of the receiver. After calling into JS, these
46  // are reloaded and checked. If they changed we bail to the baseline
47  // GenericElementsAccessor.
48  initialReceiverMap: Map;
49  initialReceiverLength: Number;
50
51  // If the user provided a comparison function, it is stored here.
52  userCmpFn: Undefined|Callable;
53
54  // Function pointer to the comparison function. This can either be a builtin
55  // that calls the user-provided comparison function or "SortDefault", which
56  // uses ToString and a lexicographical compare.
57  sortComparePtr: CompareBuiltinFn;
58
59  // The following four function pointer represent a Accessor/Path.
60  // These are used to Load/Store/Delete elements and to check whether
61  // to bail to the baseline GenericElementsAccessor.
62  loadFn: LoadFn;
63  storeFn: StoreFn;
64  deleteFn: DeleteFn;
65  canUseSameAccessorFn: CanUseSameAccessorFn;
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
69  // data, and lower for highly structured data.
70  minGallop: Smi;
71
72  // A stack of sortState.pendingRunsSize pending runs yet to be merged.
73  // Run #i starts at sortState.pendingRuns[2 * i] and extends for
74  // sortState.pendingRuns[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  pendingRunsSize: Smi;
83  pendingRuns: FixedArray;
84
85  // This is a copy of the original array/object that needs sorting.
86  // workArray is never exposed to user-code, and as such cannot change
87  // shape and won't be left-trimmed.
88  workArray: FixedArray;
89
90  // Pointer to the temporary array.
91  tempArray: FixedArray;
92
93  // The initialReceiverLength converted and clamped to Smi.
94  sortLength: Smi;
95
96  // The number of undefined that need to be inserted after sorting
97  // when the elements are copied back from the workArray to the receiver.
98  numberOfUndefined: Smi;
99}
100
101type FastSmiElements extends ElementsKind;
102type FastObjectElements extends ElementsKind;
103
104// With the pre-processing step in Torque, the exact number of elements
105// to sort is unknown at the time the sort state is created.
106// The 'length' property is an upper bound (as per spec),
107// while the actual size of the backing store is a good guess.
108// After the pre-processing step, the workarray won't change in length.
109macro CalculateWorkArrayLength(
110    receiver: JSReceiver, initialReceiverLength: Number): intptr {
111  // TODO(szuend): Implement full range sorting, not only up to MaxSmi.
112  //               https://crbug.com/v8/7970.
113  let clampedReceiverLength: uintptr;
114  try {
115    clampedReceiverLength =
116        ChangeSafeIntegerNumberToUintPtr(initialReceiverLength)
117        otherwise UIntPtrOverflow;
118    if (clampedReceiverLength > kSmiMaxValue) {
119      clampedReceiverLength = kSmiMaxValue;
120    }
121  } label UIntPtrOverflow {
122    clampedReceiverLength = kSmiMaxValue;
123  }
124
125  let workArrayLength: intptr = Convert<intptr>(clampedReceiverLength);
126  try {
127    const object = Cast<JSObject>(receiver) otherwise NoJsObject;
128    const elementsLength = Convert<intptr>(object.elements.length);
129
130    // In some cases, elements are only on prototypes, but not on the receiver
131    // itself. Do nothing then, as {workArrayLength} got initialized with the
132    // {length} property.
133    if (elementsLength != 0) {
134      workArrayLength = IntPtrMin(workArrayLength, elementsLength);
135    }
136  } label NoJsObject {}
137
138  return workArrayLength;
139}
140
141transitioning macro NewSortState(implicit context: Context)(
142    receiver: JSReceiver, comparefn: Undefined|Callable,
143    initialReceiverLength: Number): SortState {
144  const sortComparePtr =
145      comparefn != Undefined ? SortCompareUserFn : SortCompareDefault;
146  const map = receiver.map;
147  let loadFn: LoadFn;
148  let storeFn: StoreFn;
149  let deleteFn: DeleteFn;
150  let canUseSameAccessorFn: CanUseSameAccessorFn;
151
152  try {
153    const a: FastJSArray = Cast<FastJSArray>(receiver) otherwise Slow;
154
155    // Copy copy-on-write (COW) arrays.
156    array::EnsureWriteableFastElements(a);
157
158    const elementsKind: ElementsKind = map.elements_kind;
159    if (IsDoubleElementsKind(elementsKind)) {
160      loadFn = Load<FastDoubleElements>;
161      storeFn = Store<FastDoubleElements>;
162      deleteFn = Delete<FastDoubleElements>;
163      canUseSameAccessorFn = CanUseSameAccessor<FastDoubleElements>;
164    } else if (IsFastSmiElementsKind(elementsKind)) {
165      loadFn = Load<FastSmiElements>;
166      storeFn = Store<FastSmiElements>;
167      deleteFn = Delete<FastSmiElements>;
168      canUseSameAccessorFn = CanUseSameAccessor<FastSmiElements>;
169    } else {
170      loadFn = Load<FastObjectElements>;
171      storeFn = Store<FastObjectElements>;
172      deleteFn = Delete<FastObjectElements>;
173      canUseSameAccessorFn = CanUseSameAccessor<FastObjectElements>;
174    }
175  } label Slow {
176    loadFn = Load<GenericElementsAccessor>;
177    storeFn = Store<GenericElementsAccessor>;
178    deleteFn = Delete<GenericElementsAccessor>;
179    canUseSameAccessorFn = CanUseSameAccessor<GenericElementsAccessor>;
180  }
181
182  const workArrayLength =
183      CalculateWorkArrayLength(receiver, initialReceiverLength);
184
185  return new SortState{
186    receiver,
187    initialReceiverMap: map,
188    initialReceiverLength,
189    userCmpFn: comparefn,
190    sortComparePtr,
191    loadFn,
192    storeFn,
193    deleteFn,
194    canUseSameAccessorFn,
195    minGallop: kMinGallopWins,
196    pendingRunsSize: 0,
197    pendingRuns: AllocateZeroedFixedArray(Convert<intptr>(kMaxMergePending)),
198    workArray: AllocateZeroedFixedArray(workArrayLength),
199    tempArray: kEmptyFixedArray,
200    sortLength: 0,
201    numberOfUndefined: 0
202  };
203}
204
205const kSuccess: Smi = 0;
206
207// The maximum number of entries in a SortState's pending-runs stack.
208// This is enough to sort arrays of size up to about
209//   32 * phi ** kMaxMergePending
210// where phi ~= 1.618. 85 is ridiculously large enough, good for an array with
211// 2 ** 64 elements.
212const kMaxMergePending: constexpr int31 = 85;
213
214// When we get into galloping mode, we stay there until both runs win less
215// often then kMinGallop consecutive times. See listsort.txt for more info.
216const kMinGallopWins: constexpr int31 = 7;
217
218// Default size of the temporary array. The temporary array is allocated when
219// it is first requested, but it has always at least this size.
220const kSortStateTempSize: Smi = 32;
221
222type LoadFn = builtin(Context, SortState, Smi) => (JSAny|TheHole);
223type StoreFn = builtin(Context, SortState, Smi, JSAny) => Smi;
224type DeleteFn = builtin(Context, SortState, Smi) => Smi;
225type CanUseSameAccessorFn = builtin(Context, JSReceiver, Map, Number) =>
226    Boolean;
227type CompareBuiltinFn = builtin(Context, JSAny, JSAny, JSAny) => Number;
228
229// The following builtins implement Load/Store for all the Accessors.
230// The most generic baseline version uses Get-/SetProperty. We do not need
231// to worry about the prototype chain, because the pre-processing step has
232// copied values from the prototype chain to the receiver if they were visible
233// through a hole.
234
235transitioning builtin Load<ElementsAccessor : type extends ElementsKind>(
236    context: Context, sortState: SortState, index: Smi): JSAny|TheHole {
237  const receiver = sortState.receiver;
238  if (!HasProperty_Inline(receiver, index)) return TheHole;
239  return GetProperty(receiver, index);
240}
241
242Load<FastSmiElements>(
243    context: Context, sortState: SortState, index: Smi): JSAny|TheHole {
244  const object = UnsafeCast<JSObject>(sortState.receiver);
245  const elements = UnsafeCast<FixedArray>(object.elements);
246  return UnsafeCast<(JSAny | TheHole)>(elements.objects[index]);
247}
248
249Load<FastObjectElements>(
250    context: Context, sortState: SortState, index: Smi): JSAny|TheHole {
251  const object = UnsafeCast<JSObject>(sortState.receiver);
252  const elements = UnsafeCast<FixedArray>(object.elements);
253  return UnsafeCast<(JSAny | TheHole)>(elements.objects[index]);
254}
255
256Load<FastDoubleElements>(
257    context: Context, sortState: SortState, index: Smi): JSAny|TheHole {
258  try {
259    const object = UnsafeCast<JSObject>(sortState.receiver);
260    const elements = UnsafeCast<FixedDoubleArray>(object.elements);
261    const value = elements.floats[index].Value() otherwise IfHole;
262    return AllocateHeapNumberWithValue(value);
263  } label IfHole {
264    return TheHole;
265  }
266}
267
268transitioning builtin Store<ElementsAccessor : type extends ElementsKind>(
269    context: Context, sortState: SortState, index: Smi, value: JSAny): Smi {
270  SetProperty(sortState.receiver, index, value);
271  return kSuccess;
272}
273
274Store<FastSmiElements>(
275    context: Context, sortState: SortState, index: Smi, value: JSAny): Smi {
276  const object = UnsafeCast<JSObject>(sortState.receiver);
277  const elements = UnsafeCast<FixedArray>(object.elements);
278  const value = UnsafeCast<Smi>(value);
279  StoreFixedArrayElement(elements, index, value);
280  return kSuccess;
281}
282
283Store<FastObjectElements>(
284    context: Context, sortState: SortState, index: Smi, value: JSAny): Smi {
285  const object = UnsafeCast<JSObject>(sortState.receiver);
286  const elements = UnsafeCast<FixedArray>(object.elements);
287  elements.objects[index] = value;
288  return kSuccess;
289}
290
291Store<FastDoubleElements>(
292    context: Context, sortState: SortState, index: Smi, value: JSAny): Smi {
293  const object = UnsafeCast<JSObject>(sortState.receiver);
294  const elements = UnsafeCast<FixedDoubleArray>(object.elements);
295  const heapVal = UnsafeCast<HeapNumber>(value);
296  const val = Convert<float64>(heapVal);
297  StoreFixedDoubleArrayElement(elements, index, val);
298  return kSuccess;
299}
300
301transitioning builtin Delete<ElementsAccessor : type extends ElementsKind>(
302    context: Context, sortState: SortState, index: Smi): Smi {
303  const receiver = sortState.receiver;
304  DeleteProperty(receiver, index, LanguageMode::kStrict);
305  return kSuccess;
306}
307
308Delete<FastSmiElements>(
309    context: Context, sortState: SortState, index: Smi): Smi {
310  dcheck(IsHoleyFastElementsKind(sortState.receiver.map.elements_kind));
311
312  const object = UnsafeCast<JSObject>(sortState.receiver);
313  const elements = UnsafeCast<FixedArray>(object.elements);
314  elements.objects[index] = TheHole;
315  return kSuccess;
316}
317
318Delete<FastObjectElements>(
319    context: Context, sortState: SortState, index: Smi): Smi {
320  dcheck(IsHoleyFastElementsKind(sortState.receiver.map.elements_kind));
321
322  const object = UnsafeCast<JSObject>(sortState.receiver);
323  const elements = UnsafeCast<FixedArray>(object.elements);
324  elements.objects[index] = TheHole;
325  return kSuccess;
326}
327
328Delete<FastDoubleElements>(
329    context: Context, sortState: SortState, index: Smi): Smi {
330  dcheck(IsHoleyFastElementsKind(sortState.receiver.map.elements_kind));
331
332  const object = UnsafeCast<JSObject>(sortState.receiver);
333  const elements = UnsafeCast<FixedDoubleArray>(object.elements);
334  elements.floats[index] = kDoubleHole;
335  return kSuccess;
336}
337
338transitioning builtin SortCompareDefault(
339    context: Context, comparefn: JSAny, x: JSAny, y: JSAny): Number {
340  dcheck(comparefn == Undefined);
341
342  if (TaggedIsSmi(x) && TaggedIsSmi(y)) {
343    return SmiLexicographicCompare(UnsafeCast<Smi>(x), UnsafeCast<Smi>(y));
344  }
345
346  // 5. Let xString be ? ToString(x).
347  const xString = ToString_Inline(x);
348
349  // 6. Let yString be ? ToString(y).
350  const yString = ToString_Inline(y);
351
352  // 7. Let xSmaller be the result of performing
353  //    Abstract Relational Comparison xString < yString.
354  // 8. If xSmaller is true, return -1.
355  if (StringLessThan(context, xString, yString) == True) return -1;
356
357  // 9. Let ySmaller be the result of performing
358  //    Abstract Relational Comparison yString < xString.
359  // 10. If ySmaller is true, return 1.
360  if (StringLessThan(context, yString, xString) == True) return 1;
361
362  // 11. Return +0.
363  return 0;
364}
365
366transitioning builtin SortCompareUserFn(
367    context: Context, comparefn: JSAny, x: JSAny, y: JSAny): Number {
368  dcheck(comparefn != Undefined);
369  const cmpfn = UnsafeCast<Callable>(comparefn);
370
371  // a. Let v be ? ToNumber(? Call(comparefn, undefined, x, y)).
372  const v = ToNumber_Inline(Call(context, cmpfn, Undefined, x, y));
373
374  // b. If v is NaN, return +0.
375  if (NumberIsNaN(v)) return 0;
376
377  // c. return v.
378  return v;
379}
380
381builtin CanUseSameAccessor<ElementsAccessor : type extends ElementsKind>(
382    context: Context, receiver: JSReceiver, initialReceiverMap: Map,
383    initialReceiverLength: Number): Boolean {
384  if (receiver.map != initialReceiverMap) return False;
385
386  dcheck(TaggedIsSmi(initialReceiverLength));
387  const array = UnsafeCast<JSArray>(receiver);
388  const originalLength = UnsafeCast<Smi>(initialReceiverLength);
389
390  return SelectBooleanConstant(UnsafeCast<Smi>(array.length) == originalLength);
391}
392
393CanUseSameAccessor<GenericElementsAccessor>(
394    _context: Context, _receiver: JSReceiver, _initialReceiverMap: Map,
395    _initialReceiverLength: Number): Boolean {
396  // Do nothing. We are already on the slow path.
397  return True;
398}
399
400// Re-loading the stack-size is done in a few places. The small macro allows
401// for easier invariant checks at all use sites.
402macro GetPendingRunsSize(implicit context: Context)(sortState: SortState): Smi {
403  const stackSize: Smi = sortState.pendingRunsSize;
404  dcheck(stackSize >= 0);
405  return stackSize;
406}
407
408macro GetPendingRunBase(implicit context: Context)(
409    pendingRuns: FixedArray, run: Smi): Smi {
410  return UnsafeCast<Smi>(pendingRuns.objects[run << 1]);
411}
412
413macro SetPendingRunBase(pendingRuns: FixedArray, run: Smi, value: Smi): void {
414  pendingRuns.objects[run << 1] = value;
415}
416
417macro GetPendingRunLength(implicit context: Context)(
418    pendingRuns: FixedArray, run: Smi): Smi {
419  return UnsafeCast<Smi>(pendingRuns.objects[(run << 1) + 1]);
420}
421
422macro SetPendingRunLength(pendingRuns: FixedArray, run: Smi, value: Smi): void {
423  pendingRuns.objects[(run << 1) + 1] = value;
424}
425
426macro PushRun(implicit context: Context)(
427    sortState: SortState, base: Smi, length: Smi): void {
428  dcheck(GetPendingRunsSize(sortState) < kMaxMergePending);
429
430  const stackSize: Smi = GetPendingRunsSize(sortState);
431  const pendingRuns: FixedArray = sortState.pendingRuns;
432
433  SetPendingRunBase(pendingRuns, stackSize, base);
434  SetPendingRunLength(pendingRuns, stackSize, length);
435
436  sortState.pendingRunsSize = stackSize + 1;
437}
438
439// Returns the temporary array and makes sure that it is big enough.
440// TODO(szuend): Implement a better re-size strategy.
441macro GetTempArray(implicit context: Context)(
442    sortState: SortState, requestedSize: Smi): FixedArray {
443  const minSize: Smi = SmiMax(kSortStateTempSize, requestedSize);
444
445  const currentSize: Smi = sortState.tempArray.length;
446  if (currentSize >= minSize) {
447    return sortState.tempArray;
448  }
449
450  const tempArray: FixedArray =
451      AllocateZeroedFixedArray(Convert<intptr>(minSize));
452
453  sortState.tempArray = tempArray;
454  return tempArray;
455}
456
457transitioning builtin
458Copy(implicit context: Context)(
459    source: FixedArray, srcPos: Smi, target: FixedArray, dstPos: Smi,
460    length: Smi): JSAny {
461  dcheck(srcPos >= 0);
462  dcheck(dstPos >= 0);
463  dcheck(srcPos <= source.length - length);
464  dcheck(dstPos <= target.length - length);
465
466  // TODO(szuend): Investigate whether this builtin should be replaced
467  //               by CopyElements/MoveElements for perfomance.
468
469  // source and target might be the same array. To avoid overwriting
470  // values in the case of overlaping ranges, elements are copied from
471  // the back when srcPos < dstPos.
472  if (srcPos < dstPos) {
473    let srcIdx: Smi = srcPos + length - 1;
474    let dstIdx: Smi = dstPos + length - 1;
475    while (srcIdx >= srcPos) {
476      target.objects[dstIdx--] = source.objects[srcIdx--];
477    }
478  } else {
479    let srcIdx: Smi = srcPos;
480    let dstIdx: Smi = dstPos;
481    const to: Smi = srcPos + length;
482
483    while (srcIdx < to) {
484      target.objects[dstIdx++] = source.objects[srcIdx++];
485    }
486  }
487  return kSuccess;
488}
489
490// BinaryInsertionSort is the best method for sorting small arrays: it
491// does few compares, but can do data movement quadratic in the number of
492// elements. This is an advantage since comparisons are more expensive due
493// to calling into JS.
494//
495//  [low, high) is a contiguous range of a array, and is sorted via
496// binary insertion. This sort is stable.
497//
498// On entry, must have low <= start <= high, and that [low, start) is
499// already sorted. Pass start == low if you do not know!.
500macro BinaryInsertionSort(implicit context: Context, sortState: SortState)(
501    low: Smi, startArg: Smi, high: Smi): void {
502  dcheck(low <= startArg && startArg <= high);
503
504  const workArray = sortState.workArray;
505
506  let start: Smi = low == startArg ? (startArg + 1) : startArg;
507
508  for (; start < high; ++start) {
509    // Set left to where a[start] belongs.
510    let left: Smi = low;
511    let right: Smi = start;
512
513    const pivot = UnsafeCast<JSAny>(workArray.objects[right]);
514
515    // Invariants:
516    //   pivot >= all in [low, left).
517    //   pivot  < all in [right, start).
518    dcheck(left < right);
519
520    // Find pivot insertion point.
521    while (left < right) {
522      const mid: Smi = left + ((right - left) >> 1);
523      const order =
524          sortState.Compare(pivot, UnsafeCast<JSAny>(workArray.objects[mid]));
525
526      if (order < 0) {
527        right = mid;
528      } else {
529        left = mid + 1;
530      }
531    }
532    dcheck(left == right);
533
534    // The invariants still hold, so:
535    //   pivot >= all in [low, left) and
536    //   pivot  < all in [left, start),
537    //
538    // so pivot belongs at left. Note that if there are elements equal
539    // to pivot, left points to the first slot after them -- that's why
540    // this sort is stable. Slide over to make room.
541    for (let p: Smi = start; p > left; --p) {
542      workArray.objects[p] = workArray.objects[p - 1];
543    }
544    workArray.objects[left] = pivot;
545  }
546}
547
548// Return the length of the run beginning at low, in the range [low,
549// high), low < high is required on entry. "A run" is the longest
550// ascending sequence, with
551//
552//   a[low] <= a[low + 1] <= a[low + 2] <= ...
553//
554// or the longest descending sequence, with
555//
556//   a[low] > a[low + 1] > a[low + 2] > ...
557//
558// For its intended use in stable mergesort, the strictness of the
559// definition of "descending" is needed so that the range can safely be
560// reversed without violating stability (strict ">" ensures there are no
561// equal elements to get out of order).
562//
563// In addition, if the run is "descending", it is reversed, so the
564// returned length is always an ascending sequence.
565macro CountAndMakeRun(implicit context: Context, sortState: SortState)(
566    lowArg: Smi, high: Smi): Smi {
567  dcheck(lowArg < high);
568
569  const workArray = sortState.workArray;
570
571  const low: Smi = lowArg + 1;
572  if (low == high) return 1;
573
574  let runLength: Smi = 2;
575
576  const elementLow = UnsafeCast<JSAny>(workArray.objects[low]);
577  const elementLowPred = UnsafeCast<JSAny>(workArray.objects[low - 1]);
578  let order = sortState.Compare(elementLow, elementLowPred);
579
580  // TODO(szuend): Replace with "order < 0" once Torque supports it.
581  //               Currently the operator<(Number, Number) has return type
582  //               'never' and uses two labels to branch.
583  const isDescending: bool = order < 0 ? true : false;
584
585  let previousElement: JSAny = elementLow;
586  for (let idx: Smi = low + 1; idx < high; ++idx) {
587    const currentElement = UnsafeCast<JSAny>(workArray.objects[idx]);
588    order = sortState.Compare(currentElement, previousElement);
589
590    if (isDescending) {
591      if (order >= 0) break;
592    } else {
593      if (order < 0) break;
594    }
595
596    previousElement = currentElement;
597    ++runLength;
598  }
599
600  if (isDescending) {
601    ReverseRange(workArray, lowArg, lowArg + runLength);
602  }
603
604  return runLength;
605}
606
607macro ReverseRange(array: FixedArray, from: Smi, to: Smi): void {
608  let low: Smi = from;
609  let high: Smi = to - 1;
610
611  while (low < high) {
612    const elementLow = array.objects[low];
613    const elementHigh = array.objects[high];
614    array.objects[low++] = elementHigh;
615    array.objects[high--] = elementLow;
616  }
617}
618
619// Merges the two runs at stack indices i and i + 1.
620// Returns kFailure if we need to bailout, kSuccess otherwise.
621transitioning builtin
622MergeAt(implicit context: Context, sortState: SortState)(i: Smi): Smi {
623  const stackSize: Smi = GetPendingRunsSize(sortState);
624
625  // We are only allowed to either merge the two top-most runs, or leave
626  // the top most run alone and merge the two next runs.
627  dcheck(stackSize >= 2);
628  dcheck(i >= 0);
629  dcheck(i == stackSize - 2 || i == stackSize - 3);
630
631  const workArray = sortState.workArray;
632
633  const pendingRuns: FixedArray = sortState.pendingRuns;
634  let baseA: Smi = GetPendingRunBase(pendingRuns, i);
635  let lengthA: Smi = GetPendingRunLength(pendingRuns, i);
636  const baseB: Smi = GetPendingRunBase(pendingRuns, i + 1);
637  let lengthB: Smi = GetPendingRunLength(pendingRuns, i + 1);
638  dcheck(lengthA > 0 && lengthB > 0);
639  dcheck(baseA + lengthA == baseB);
640
641  // Record the length of the combined runs; if i is the 3rd-last run now,
642  // also slide over the last run (which isn't involved in this merge).
643  // The current run i + 1 goes away in any case.
644  SetPendingRunLength(pendingRuns, i, lengthA + lengthB);
645  if (i == stackSize - 3) {
646    const base: Smi = GetPendingRunBase(pendingRuns, i + 2);
647    const length: Smi = GetPendingRunLength(pendingRuns, i + 2);
648    SetPendingRunBase(pendingRuns, i + 1, base);
649    SetPendingRunLength(pendingRuns, i + 1, length);
650  }
651  sortState.pendingRunsSize = stackSize - 1;
652
653  // Where does b start in a? Elements in a before that can be ignored,
654  // because they are already in place.
655  const keyRight = UnsafeCast<JSAny>(workArray.objects[baseB]);
656  const k: Smi = GallopRight(workArray, keyRight, baseA, lengthA, 0);
657  dcheck(k >= 0);
658
659  baseA = baseA + k;
660  lengthA = lengthA - k;
661  if (lengthA == 0) return kSuccess;
662  dcheck(lengthA > 0);
663
664  // Where does a end in b? Elements in b after that can be ignored,
665  // because they are already in place.
666  const keyLeft = UnsafeCast<JSAny>(workArray.objects[baseA + lengthA - 1]);
667  lengthB = GallopLeft(workArray, keyLeft, baseB, lengthB, lengthB - 1);
668  dcheck(lengthB >= 0);
669  if (lengthB == 0) return kSuccess;
670
671  // Merge what remains of the runs, using a temp array with
672  // min(lengthA, lengthB) elements.
673  if (lengthA <= lengthB) {
674    MergeLow(baseA, lengthA, baseB, lengthB);
675  } else {
676    MergeHigh(baseA, lengthA, baseB, lengthB);
677  }
678  return kSuccess;
679}
680
681// Locates the proper position of key in a sorted array; if the array
682// contains an element equal to key, return the position immediately to
683// the left of the leftmost equal element. (GallopRight does the same
684// except returns the position to the right of the rightmost equal element
685// (if any)).
686//
687// The array is sorted with "length" elements, starting at "base".
688// "length" must be > 0.
689//
690// "hint" is an index at which to begin the search, 0 <= hint < n. The
691// closer hint is to the final result, the faster this runs.
692//
693// The return value is the int offset in 0..length such that
694//
695// array[base + offset] < key <= array[base + offset + 1]
696//
697// pretending that array[base - 1] is minus infinity and array[base + len]
698// is plus infinity. In other words, key belongs at index base + k.
699builtin GallopLeft(implicit context: Context, sortState: SortState)(
700    array: FixedArray, key: JSAny, base: Smi, length: Smi, hint: Smi): Smi {
701  dcheck(length > 0 && base >= 0);
702  dcheck(0 <= hint && hint < length);
703
704  let lastOfs: Smi = 0;
705  let offset: Smi = 1;
706
707  const baseHintElement = UnsafeCast<JSAny>(array.objects[base + hint]);
708  let order = sortState.Compare(baseHintElement, key);
709
710  if (order < 0) {
711    // a[base + hint] < key: gallop right, until
712    // a[base + hint + lastOfs] < key <= a[base + hint + offset].
713
714    // a[base + length - 1] is highest.
715    const maxOfs: Smi = length - hint;
716    while (offset < maxOfs) {
717      const offsetElement =
718          UnsafeCast<JSAny>(array.objects[base + hint + offset]);
719      order = sortState.Compare(offsetElement, key);
720
721      // a[base + hint + offset] >= key? Break.
722      if (order >= 0) break;
723
724      lastOfs = offset;
725      offset = (offset << 1) + 1;
726
727      // Integer overflow.
728      if (offset <= 0) offset = maxOfs;
729    }
730
731    if (offset > maxOfs) offset = maxOfs;
732
733    // Translate back to positive offsets relative to base.
734    lastOfs = lastOfs + hint;
735    offset = offset + hint;
736  } else {
737    // key <= a[base + hint]: gallop left, until
738    // a[base + hint - offset] < key <= a[base + hint - lastOfs].
739    dcheck(order >= 0);
740
741    // a[base + hint] is lowest.
742    const maxOfs: Smi = hint + 1;
743    while (offset < maxOfs) {
744      const offsetElement =
745          UnsafeCast<JSAny>(array.objects[base + hint - offset]);
746      order = sortState.Compare(offsetElement, key);
747
748      if (order < 0) break;
749
750      lastOfs = offset;
751      offset = (offset << 1) + 1;
752
753      // Integer overflow.
754      if (offset <= 0) offset = maxOfs;
755    }
756
757    if (offset > maxOfs) offset = maxOfs;
758
759    // Translate back to positive offsets relative to base.
760    const tmp: Smi = lastOfs;
761    lastOfs = hint - offset;
762    offset = hint - tmp;
763  }
764
765  dcheck(-1 <= lastOfs && lastOfs < offset && offset <= length);
766
767  // Now a[base+lastOfs] < key <= a[base+offset], so key belongs
768  // somewhere to the right of lastOfs but no farther right than offset.
769  // Do a binary search, with invariant:
770  //   a[base + lastOfs - 1] < key <= a[base + offset].
771  lastOfs++;
772  while (lastOfs < offset) {
773    const m: Smi = lastOfs + ((offset - lastOfs) >> 1);
774
775    order = sortState.Compare(UnsafeCast<JSAny>(array.objects[base + m]), key);
776
777    if (order < 0) {
778      lastOfs = m + 1;  // a[base + m] < key.
779    } else {
780      offset = m;  // key <= a[base + m].
781    }
782  }
783  // so a[base + offset - 1] < key <= a[base + offset].
784  dcheck(lastOfs == offset);
785  dcheck(0 <= offset && offset <= length);
786  return offset;
787}
788
789// Exactly like GallopLeft, except that if key already exists in
790// [base, base + length), finds the position immediately to the right of
791// the rightmost equal value.
792//
793// The return value is the int offset in 0..length such that
794//
795// array[base + offset - 1] <= key < array[base + offset]
796//
797// or kFailure on error.
798builtin GallopRight(implicit context: Context, sortState: SortState)(
799    array: FixedArray, key: JSAny, base: Smi, length: Smi, hint: Smi): Smi {
800  dcheck(length > 0 && base >= 0);
801  dcheck(0 <= hint && hint < length);
802
803  let lastOfs: Smi = 0;
804  let offset: Smi = 1;
805
806  const baseHintElement = UnsafeCast<JSAny>(array.objects[base + hint]);
807  let order = sortState.Compare(key, baseHintElement);
808
809  if (order < 0) {
810    // key < a[base + hint]: gallop left, until
811    // a[base + hint - offset] <= key < a[base + hint - lastOfs].
812
813    // a[base + hint] is lowest.
814    const maxOfs: Smi = hint + 1;
815    while (offset < maxOfs) {
816      const offsetElement =
817          UnsafeCast<JSAny>(array.objects[base + hint - offset]);
818      order = sortState.Compare(key, offsetElement);
819
820      if (order >= 0) break;
821
822      lastOfs = offset;
823      offset = (offset << 1) + 1;
824
825      // Integer overflow.
826      if (offset <= 0) offset = maxOfs;
827    }
828
829    if (offset > maxOfs) offset = maxOfs;
830
831    // Translate back to positive offsets relative to base.
832    const tmp: Smi = lastOfs;
833    lastOfs = hint - offset;
834    offset = hint - tmp;
835  } else {
836    // a[base + hint] <= key: gallop right, until
837    // a[base + hint + lastOfs] <= key < a[base + hint + offset].
838
839    // a[base + length - 1] is highest.
840    const maxOfs: Smi = length - hint;
841    while (offset < maxOfs) {
842      const offsetElement =
843          UnsafeCast<JSAny>(array.objects[base + hint + offset]);
844      order = sortState.Compare(key, offsetElement);
845
846      // a[base + hint + ofs] <= key.
847      if (order < 0) break;
848
849      lastOfs = offset;
850      offset = (offset << 1) + 1;
851
852      // Integer overflow.
853      if (offset <= 0) offset = maxOfs;
854    }
855
856    if (offset > maxOfs) offset = maxOfs;
857
858    // Translate back to positive offests relative to base.
859    lastOfs = lastOfs + hint;
860    offset = offset + hint;
861  }
862  dcheck(-1 <= lastOfs && lastOfs < offset && offset <= length);
863
864  // Now a[base + lastOfs] <= key < a[base + ofs], so key belongs
865  // somewhere to the right of lastOfs but no farther right than ofs.
866  // Do a binary search, with invariant
867  // a[base + lastOfs - 1] < key <= a[base + ofs].
868  lastOfs++;
869  while (lastOfs < offset) {
870    const m: Smi = lastOfs + ((offset - lastOfs) >> 1);
871
872    order = sortState.Compare(key, UnsafeCast<JSAny>(array.objects[base + m]));
873
874    if (order < 0) {
875      offset = m;  // key < a[base + m].
876    } else {
877      lastOfs = m + 1;  // a[base + m] <= key.
878    }
879  }
880  // so a[base + offset - 1] <= key < a[base + offset].
881  dcheck(lastOfs == offset);
882  dcheck(0 <= offset && offset <= length);
883  return offset;
884}
885
886// Merge the lengthA elements starting at baseA with the lengthB elements
887// starting at baseB in a stable way, in-place. lengthA and lengthB must
888// be > 0, and baseA + lengthA == baseB. Must also have that
889// array[baseB] < array[baseA],
890// that array[baseA + lengthA - 1] belongs at the end of the merge,
891// and should have lengthA <= lengthB.
892transitioning macro MergeLow(implicit context: Context, sortState: SortState)(
893    baseA: Smi, lengthAArg: Smi, baseB: Smi, lengthBArg: Smi): void {
894  dcheck(0 < lengthAArg && 0 < lengthBArg);
895  dcheck(0 <= baseA && 0 < baseB);
896  dcheck(baseA + lengthAArg == baseB);
897
898  let lengthA: Smi = lengthAArg;
899  let lengthB: Smi = lengthBArg;
900
901  const workArray = sortState.workArray;
902  const tempArray: FixedArray = GetTempArray(sortState, lengthA);
903  Copy(workArray, baseA, tempArray, 0, lengthA);
904
905  let dest: Smi = baseA;
906  let cursorTemp: Smi = 0;
907  let cursorB: Smi = baseB;
908
909  workArray.objects[dest++] = workArray.objects[cursorB++];
910
911  try {
912    if (--lengthB == 0) goto Succeed;
913    if (lengthA == 1) goto CopyB;
914
915    let minGallop: Smi = sortState.minGallop;
916    // TODO(szuend): Replace with something that does not have a runtime
917    //               overhead as soon as its available in Torque.
918    while (Int32TrueConstant()) {
919      let nofWinsA: Smi = 0;  // # of times A won in a row.
920      let nofWinsB: Smi = 0;  // # of times B won in a row.
921
922      // Do the straightforward thing until (if ever) one run appears to
923      // win consistently.
924      // TODO(szuend): Replace with something that does not have a runtime
925      //               overhead as soon as its available in Torque.
926      while (Int32TrueConstant()) {
927        dcheck(lengthA > 1 && lengthB > 0);
928
929        const order = sortState.Compare(
930            UnsafeCast<JSAny>(workArray.objects[cursorB]),
931            UnsafeCast<JSAny>(tempArray.objects[cursorTemp]));
932
933        if (order < 0) {
934          workArray.objects[dest++] = workArray.objects[cursorB++];
935
936          ++nofWinsB;
937          --lengthB;
938          nofWinsA = 0;
939
940          if (lengthB == 0) goto Succeed;
941          if (nofWinsB >= minGallop) break;
942        } else {
943          workArray.objects[dest++] = tempArray.objects[cursorTemp++];
944
945          ++nofWinsA;
946          --lengthA;
947          nofWinsB = 0;
948
949          if (lengthA == 1) goto CopyB;
950          if (nofWinsA >= minGallop) break;
951        }
952      }
953
954      // One run is winning so consistently that galloping may be a huge
955      // win. So try that, and continue galloping until (if ever) neither
956      // run appears to be winning consistently anymore.
957      ++minGallop;
958      let firstIteration: bool = true;
959      while (nofWinsA >= kMinGallopWins || nofWinsB >= kMinGallopWins ||
960             firstIteration) {
961        firstIteration = false;
962        dcheck(lengthA > 1 && lengthB > 0);
963
964        minGallop = SmiMax(1, minGallop - 1);
965        sortState.minGallop = minGallop;
966
967        nofWinsA = GallopRight(
968            tempArray, UnsafeCast<JSAny>(workArray.objects[cursorB]),
969            cursorTemp, lengthA, 0);
970        dcheck(nofWinsA >= 0);
971
972        if (nofWinsA > 0) {
973          Copy(tempArray, cursorTemp, workArray, dest, nofWinsA);
974          dest = dest + nofWinsA;
975          cursorTemp = cursorTemp + nofWinsA;
976          lengthA = lengthA - nofWinsA;
977
978          if (lengthA == 1) goto CopyB;
979
980          // lengthA == 0 is impossible now if the comparison function is
981          // consistent, but we can't assume that it is.
982          if (lengthA == 0) goto Succeed;
983        }
984        workArray.objects[dest++] = workArray.objects[cursorB++];
985        if (--lengthB == 0) goto Succeed;
986
987        nofWinsB = GallopLeft(
988            workArray, UnsafeCast<JSAny>(tempArray.objects[cursorTemp]),
989            cursorB, lengthB, 0);
990        dcheck(nofWinsB >= 0);
991        if (nofWinsB > 0) {
992          Copy(workArray, cursorB, workArray, dest, nofWinsB);
993
994          dest = dest + nofWinsB;
995          cursorB = cursorB + nofWinsB;
996          lengthB = lengthB - nofWinsB;
997
998          if (lengthB == 0) goto Succeed;
999        }
1000        workArray.objects[dest++] = tempArray.objects[cursorTemp++];
1001        if (--lengthA == 1) goto CopyB;
1002      }
1003      ++minGallop;  // Penalize it for leaving galloping mode
1004      sortState.minGallop = minGallop;
1005    }
1006  } label Succeed {
1007    if (lengthA > 0) {
1008      Copy(tempArray, cursorTemp, workArray, dest, lengthA);
1009    }
1010  } label CopyB {
1011    dcheck(lengthA == 1 && lengthB > 0);
1012    // The last element of run A belongs at the end of the merge.
1013    Copy(workArray, cursorB, workArray, dest, lengthB);
1014    workArray.objects[dest + lengthB] = tempArray.objects[cursorTemp];
1015  }
1016}
1017
1018// Merge the lengthA elements starting at baseA with the lengthB elements
1019// starting at baseB in a stable way, in-place. lengthA and lengthB must
1020// be > 0. Must also have that array[baseA + lengthA - 1] belongs at the
1021// end of the merge and should have lengthA >= lengthB.
1022transitioning macro MergeHigh(implicit context: Context, sortState: SortState)(
1023    baseA: Smi, lengthAArg: Smi, baseB: Smi, lengthBArg: Smi): void {
1024  dcheck(0 < lengthAArg && 0 < lengthBArg);
1025  dcheck(0 <= baseA && 0 < baseB);
1026  dcheck(baseA + lengthAArg == baseB);
1027
1028  let lengthA: Smi = lengthAArg;
1029  let lengthB: Smi = lengthBArg;
1030
1031  const workArray = sortState.workArray;
1032  const tempArray: FixedArray = GetTempArray(sortState, lengthB);
1033  Copy(workArray, baseB, tempArray, 0, lengthB);
1034
1035  // MergeHigh merges the two runs backwards.
1036  let dest: Smi = baseB + lengthB - 1;
1037  let cursorTemp: Smi = lengthB - 1;
1038  let cursorA: Smi = baseA + lengthA - 1;
1039
1040  workArray.objects[dest--] = workArray.objects[cursorA--];
1041
1042  try {
1043    if (--lengthA == 0) goto Succeed;
1044    if (lengthB == 1) goto CopyA;
1045
1046    let minGallop: Smi = sortState.minGallop;
1047    // TODO(szuend): Replace with something that does not have a runtime
1048    //               overhead as soon as its available in Torque.
1049    while (Int32TrueConstant()) {
1050      let nofWinsA: Smi = 0;  // # of times A won in a row.
1051      let nofWinsB: Smi = 0;  // # of times B won in a row.
1052
1053      // Do the straightforward thing until (if ever) one run appears to
1054      // win consistently.
1055      // TODO(szuend): Replace with something that does not have a runtime
1056      //               overhead as soon as its available in Torque.
1057      while (Int32TrueConstant()) {
1058        dcheck(lengthA > 0 && lengthB > 1);
1059
1060        const order = sortState.Compare(
1061            UnsafeCast<JSAny>(tempArray.objects[cursorTemp]),
1062            UnsafeCast<JSAny>(workArray.objects[cursorA]));
1063
1064        if (order < 0) {
1065          workArray.objects[dest--] = workArray.objects[cursorA--];
1066
1067          ++nofWinsA;
1068          --lengthA;
1069          nofWinsB = 0;
1070
1071          if (lengthA == 0) goto Succeed;
1072          if (nofWinsA >= minGallop) break;
1073        } else {
1074          workArray.objects[dest--] = tempArray.objects[cursorTemp--];
1075
1076          ++nofWinsB;
1077          --lengthB;
1078          nofWinsA = 0;
1079
1080          if (lengthB == 1) goto CopyA;
1081          if (nofWinsB >= minGallop) break;
1082        }
1083      }
1084
1085      // One run is winning so consistently that galloping may be a huge
1086      // win. So try that, and continue galloping until (if ever) neither
1087      // run appears to be winning consistently anymore.
1088      ++minGallop;
1089      let firstIteration: bool = true;
1090      while (nofWinsA >= kMinGallopWins || nofWinsB >= kMinGallopWins ||
1091             firstIteration) {
1092        firstIteration = false;
1093
1094        dcheck(lengthA > 0 && lengthB > 1);
1095
1096        minGallop = SmiMax(1, minGallop - 1);
1097        sortState.minGallop = minGallop;
1098
1099        let k: Smi = GallopRight(
1100            workArray, UnsafeCast<JSAny>(tempArray.objects[cursorTemp]), baseA,
1101            lengthA, lengthA - 1);
1102        dcheck(k >= 0);
1103        nofWinsA = lengthA - k;
1104
1105        if (nofWinsA > 0) {
1106          dest = dest - nofWinsA;
1107          cursorA = cursorA - nofWinsA;
1108          Copy(workArray, cursorA + 1, workArray, dest + 1, nofWinsA);
1109
1110          lengthA = lengthA - nofWinsA;
1111          if (lengthA == 0) goto Succeed;
1112        }
1113        workArray.objects[dest--] = tempArray.objects[cursorTemp--];
1114        if (--lengthB == 1) goto CopyA;
1115
1116        k = GallopLeft(
1117            tempArray, UnsafeCast<JSAny>(workArray.objects[cursorA]), 0,
1118            lengthB, lengthB - 1);
1119        dcheck(k >= 0);
1120        nofWinsB = lengthB - k;
1121
1122        if (nofWinsB > 0) {
1123          dest = dest - nofWinsB;
1124          cursorTemp = cursorTemp - nofWinsB;
1125          Copy(tempArray, cursorTemp + 1, workArray, dest + 1, nofWinsB);
1126
1127          lengthB = lengthB - nofWinsB;
1128          if (lengthB == 1) goto CopyA;
1129
1130          // lengthB == 0 is impossible now if the comparison function is
1131          // consistent, but we can't assume that it is.
1132          if (lengthB == 0) goto Succeed;
1133        }
1134        workArray.objects[dest--] = workArray.objects[cursorA--];
1135        if (--lengthA == 0) goto Succeed;
1136      }
1137      ++minGallop;
1138      sortState.minGallop = minGallop;
1139    }
1140  } label Succeed {
1141    if (lengthB > 0) {
1142      dcheck(lengthA == 0);
1143      Copy(tempArray, 0, workArray, dest - (lengthB - 1), lengthB);
1144    }
1145  } label CopyA {
1146    dcheck(lengthB == 1 && lengthA > 0);
1147
1148    // The first element of run B belongs at the front of the merge.
1149    dest = dest - lengthA;
1150    cursorA = cursorA - lengthA;
1151    Copy(workArray, cursorA + 1, workArray, dest + 1, lengthA);
1152    workArray.objects[dest] = tempArray.objects[cursorTemp];
1153  }
1154}
1155
1156// Compute a good value for the minimum run length; natural runs shorter
1157// than this are boosted artificially via binary insertion sort.
1158//
1159// If n < 64, return n (it's too small to bother with fancy stuff).
1160// Else if n is an exact power of 2, return 32.
1161// Else return an int k, 32 <= k <= 64, such that n/k is close to, but
1162// strictly less than, an exact power of 2.
1163//
1164// See listsort.txt for more info.
1165macro ComputeMinRunLength(nArg: Smi): Smi {
1166  let n: Smi = nArg;
1167  let r: Smi = 0;  // Becomes 1 if any 1 bits are shifted off.
1168
1169  dcheck(n >= 0);
1170  while (n >= 64) {
1171    r = r | (n & 1);
1172    n = n >> 1;
1173  }
1174
1175  const minRunLength: Smi = n + r;
1176  dcheck(nArg < 64 || (32 <= minRunLength && minRunLength <= 64));
1177  return minRunLength;
1178}
1179
1180// Returns true iff run_length(n - 2) > run_length(n - 1) + run_length(n).
1181macro RunInvariantEstablished(implicit context: Context)(
1182    pendingRuns: FixedArray, n: Smi): bool {
1183  if (n < 2) return true;
1184
1185  const runLengthN: Smi = GetPendingRunLength(pendingRuns, n);
1186  const runLengthNM: Smi = GetPendingRunLength(pendingRuns, n - 1);
1187  const runLengthNMM: Smi = GetPendingRunLength(pendingRuns, n - 2);
1188
1189  return runLengthNMM > runLengthNM + runLengthN;
1190}
1191
1192// Examines the stack of runs waiting to be merged, merging adjacent runs
1193// until the stack invariants are re-established:
1194//
1195//   1. run_length(i - 3) > run_length(i - 2) + run_length(i - 1)
1196//   2. run_length(i - 2) > run_length(i - 1)
1197//
1198// TODO(szuend): Remove unnecessary loads. This macro was refactored to
1199//               improve readability, introducing unnecessary loads in the
1200//               process. Determine if all these extra loads are ok.
1201transitioning macro MergeCollapse(
1202    context: Context, sortState: SortState): void {
1203  const pendingRuns: FixedArray = sortState.pendingRuns;
1204
1205  // Reload the stack size because MergeAt might change it.
1206  while (GetPendingRunsSize(sortState) > 1) {
1207    let n: Smi = GetPendingRunsSize(sortState) - 2;
1208
1209    if (!RunInvariantEstablished(pendingRuns, n + 1) ||
1210        !RunInvariantEstablished(pendingRuns, n)) {
1211      if (GetPendingRunLength(pendingRuns, n - 1) <
1212          GetPendingRunLength(pendingRuns, n + 1)) {
1213        --n;
1214      }
1215
1216      MergeAt(n);
1217    } else if (
1218        GetPendingRunLength(pendingRuns, n) <=
1219        GetPendingRunLength(pendingRuns, n + 1)) {
1220      MergeAt(n);
1221    } else {
1222      break;
1223    }
1224  }
1225}
1226
1227// Regardless of invariants, merge all runs on the stack until only one
1228// remains. This is used at the end of the mergesort.
1229transitioning macro
1230MergeForceCollapse(context: Context, sortState: SortState): void {
1231  const pendingRuns: FixedArray = sortState.pendingRuns;
1232
1233  // Reload the stack size becuase MergeAt might change it.
1234  while (GetPendingRunsSize(sortState) > 1) {
1235    let n: Smi = GetPendingRunsSize(sortState) - 2;
1236
1237    if (n > 0 &&
1238        GetPendingRunLength(pendingRuns, n - 1) <
1239            GetPendingRunLength(pendingRuns, n + 1)) {
1240      --n;
1241    }
1242    MergeAt(n);
1243  }
1244}
1245
1246transitioning macro
1247ArrayTimSortImpl(context: Context, sortState: SortState, length: Smi): void {
1248  if (length < 2) return;
1249  let remaining: Smi = length;
1250
1251  // March over the array once, left to right, finding natural runs,
1252  // and extending short natural runs to minrun elements.
1253  let low: Smi = 0;
1254  const minRunLength: Smi = ComputeMinRunLength(remaining);
1255  while (remaining != 0) {
1256    let currentRunLength: Smi = CountAndMakeRun(low, low + remaining);
1257
1258    // If the run is short, extend it to min(minRunLength, remaining).
1259    if (currentRunLength < minRunLength) {
1260      const forcedRunLength: Smi = SmiMin(minRunLength, remaining);
1261      BinaryInsertionSort(low, low + currentRunLength, low + forcedRunLength);
1262      currentRunLength = forcedRunLength;
1263    }
1264
1265    // Push run onto pending-runs stack, and maybe merge.
1266    PushRun(sortState, low, currentRunLength);
1267
1268    MergeCollapse(context, sortState);
1269
1270    // Advance to find next run.
1271    low = low + currentRunLength;
1272    remaining = remaining - currentRunLength;
1273  }
1274
1275  MergeForceCollapse(context, sortState);
1276  dcheck(GetPendingRunsSize(sortState) == 1);
1277  dcheck(GetPendingRunLength(sortState.pendingRuns, 0) == length);
1278}
1279
1280transitioning macro
1281CompactReceiverElementsIntoWorkArray(
1282    implicit context: Context, sortState: SortState)(): Smi {
1283  let growableWorkArray = growable_fixed_array::GrowableFixedArray{
1284    array: sortState.workArray,
1285    capacity: Convert<intptr>(sortState.workArray.length),
1286    length: 0
1287  };
1288
1289  const loadFn = sortState.loadFn;
1290
1291  // TODO(szuend): Implement full range sorting, not only up to MaxSmi.
1292  //               https://crbug.com/v8/7970.
1293  const receiverLength: Number = sortState.initialReceiverLength;
1294  dcheck(IsNumberNormalized(receiverLength));
1295
1296  const sortLength: Smi = TaggedIsSmi(receiverLength) ?
1297      UnsafeCast<Smi>(receiverLength) :
1298      Convert<PositiveSmi>(kSmiMax) otherwise unreachable;
1299
1300  // Move all non-undefined elements into {sortState.workArray}, holes
1301  // are ignored.
1302  let numberOfUndefined: Smi = 0;
1303  for (let i: Smi = 0; i < receiverLength; ++i) {
1304    const element: JSAny|TheHole = loadFn(context, sortState, i);
1305
1306    if (element == TheHole) {
1307      // Do nothing for holes. The result is that elements are
1308      // compacted at the front of the work array.
1309    } else if (element == Undefined) {
1310      numberOfUndefined++;
1311    } else {
1312      growableWorkArray.Push(element);
1313    }
1314  }
1315
1316  // Reset the workArray on the frameState, as it may have grown.
1317  sortState.workArray = growableWorkArray.array;
1318  sortState.sortLength = sortLength;
1319  sortState.numberOfUndefined = numberOfUndefined;
1320
1321  return Convert<Smi>(growableWorkArray.length);
1322}
1323
1324transitioning macro
1325CopyWorkArrayToReceiver(implicit context: Context, sortState: SortState)(
1326    numberOfNonUndefined: Smi): void {
1327  const storeFn = sortState.storeFn;
1328  const workArray = sortState.workArray;
1329
1330  dcheck(numberOfNonUndefined <= workArray.length);
1331  dcheck(
1332      numberOfNonUndefined + sortState.numberOfUndefined <=
1333      sortState.sortLength);
1334
1335  // Writing the elements back is a 3 step process:
1336  //   1. Copy the sorted elements from the workarray to the receiver.
1337  //   2. Add {nOfUndefined} undefineds to the receiver.
1338  //   3. Depending on the backing store either delete properties or
1339  //      set them to the TheHole up to {sortState.sortLength}.
1340  let index: Smi = 0;
1341  for (; index < numberOfNonUndefined; ++index) {
1342    storeFn(
1343        context, sortState, index, UnsafeCast<JSAny>(workArray.objects[index]));
1344  }
1345
1346  const numberOfUndefinedEnd: Smi =
1347      sortState.numberOfUndefined + numberOfNonUndefined;
1348  for (; index < numberOfUndefinedEnd; ++index) {
1349    storeFn(context, sortState, index, Undefined);
1350  }
1351
1352  const end: Smi = sortState.sortLength;
1353  const deleteFn = sortState.deleteFn;
1354  for (; index < end; ++index) {
1355    deleteFn(context, sortState, index);
1356  }
1357}
1358
1359transitioning builtin
1360ArrayTimSort(context: Context, sortState: SortState): JSAny {
1361  const numberOfNonUndefined: Smi = CompactReceiverElementsIntoWorkArray();
1362  ArrayTimSortImpl(context, sortState, numberOfNonUndefined);
1363
1364  try {
1365    // The comparison function or toString might have changed the
1366    // receiver, if that is the case, we switch to the slow path.
1367    sortState.CheckAccessor() otherwise Slow;
1368  } label Slow deferred {
1369    sortState.ResetToGenericAccessor();
1370  }
1371
1372  CopyWorkArrayToReceiver(numberOfNonUndefined);
1373  return kSuccess;
1374}
1375
1376// https://tc39.github.io/ecma262/#sec-array.prototype.sort
1377transitioning javascript builtin
1378ArrayPrototypeSort(
1379    js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
1380  // 1. If comparefn is not undefined and IsCallable(comparefn) is false,
1381  //    throw a TypeError exception.
1382  const comparefnObj: JSAny = arguments[0];
1383  const comparefn = Cast<(Undefined | Callable)>(comparefnObj) otherwise
1384  ThrowTypeError(MessageTemplate::kBadSortComparisonFunction, comparefnObj);
1385
1386  // 2. Let obj be ? ToObject(this value).
1387  const obj: JSReceiver = ToObject(context, receiver);
1388
1389  // 3. Let len be ? ToLength(? Get(obj, "length")).
1390  const len: Number = GetLengthProperty(obj);
1391
1392  if (len < 2) return obj;
1393
1394  const sortState: SortState = NewSortState(obj, comparefn, len);
1395  ArrayTimSort(context, sortState);
1396
1397  return obj;
1398}
1399}
1400