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