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