%PDF- <> %âãÏÓ endobj 2 0 obj <> endobj 3 0 obj <>/ExtGState<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI] >>/Annots[ 28 0 R 29 0 R] /MediaBox[ 0 0 595.5 842.25] /Contents 4 0 R/Group<>/Tabs/S>> endobj ºaâÚÎΞ-ÌE1ÍØÄ÷{òò2ÿ ÛÖ^ÔÀá TÎ{¦?§®¥kuµùÕ5sLOšuY>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<> endobj 2 0 obj<>endobj 2 0 obj<>es 3 0 R>> endobj 2 0 obj<> ox[ 0.000000 0.000000 609.600000 935.600000]/Fi endobj 3 0 obj<> endobj 7 1 obj<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]>>/Subtype/Form>> stream
// Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, // 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Python Software Foundation; // All Rights Reserved // This file implements a stable, adapative merge sort variant called TimSort. // // It was first implemented in python and this Torque implementation // is based on the current version: // // https://github.com/python/cpython/blob/master/Objects/listobject.c // // Detailed analysis and a description of the algorithm can be found at: // // https://github.com/python/cpython/blob/master/Objects/listsort.txt namespace array { class SortState extends HeapObject { macro Compare(implicit context: Context)(x: JSAny, y: JSAny): Number { const sortCompare: CompareBuiltinFn = this.sortComparePtr; return sortCompare(context, this.userCmpFn, x, y); } macro CheckAccessor(implicit context: Context)() labels Bailout { if (!IsFastJSArray(this.receiver, context)) goto Bailout; const canUseSameAccessorFn: CanUseSameAccessorFn = this.canUseSameAccessorFn; if (!canUseSameAccessorFn( context, this.receiver, this.initialReceiverMap, this.initialReceiverLength)) { goto Bailout; } } macro ResetToGenericAccessor() { this.loadFn = Load<GenericElementsAccessor>; this.storeFn = Store<GenericElementsAccessor>; this.deleteFn = Delete<GenericElementsAccessor>; } // The receiver of the Array.p.sort call. receiver: JSReceiver; // The initial map and length of the receiver. After calling into JS, these // are reloaded and checked. If they changed we bail to the baseline // GenericElementsAccessor. initialReceiverMap: Map; initialReceiverLength: Number; // If the user provided a comparison function, it is stored here. userCmpFn: Undefined|Callable; // Function pointer to the comparison function. This can either be a builtin // that calls the user-provided comparison function or "SortDefault", which // uses ToString and a lexicographical compare. sortComparePtr: CompareBuiltinFn; // The following four function pointer represent a Accessor/Path. // These are used to Load/Store/Delete elements and to check whether // to bail to the baseline GenericElementsAccessor. loadFn: LoadFn; storeFn: StoreFn; deleteFn: DeleteFn; canUseSameAccessorFn: CanUseSameAccessorFn; // This controls when we get *into* galloping mode. It's initialized to // kMinGallop. mergeLow and mergeHigh tend to nudge it higher for random // data, and lower for highly structured data. minGallop: Smi; // A stack of sortState.pendingRunsSize pending runs yet to be merged. // Run #i starts at sortState.pendingRuns[2 * i] and extends for // sortState.pendingRuns[2 * i + 1] elements: // // [..., base (i-1), length (i-1), base i, length i] // // It's always true (so long as the indices are in bounds) that // // base of run #i + length of run #i == base of run #i + 1 // pendingRunsSize: Smi; pendingRuns: FixedArray; // This is a copy of the original array/object that needs sorting. // workArray is never exposed to user-code, and as such cannot change // shape and won't be left-trimmed. workArray: FixedArray; // Pointer to the temporary array. tempArray: FixedArray; // The initialReceiverLength converted and clamped to Smi. sortLength: Smi; // The number of undefined that need to be inserted after sorting // when the elements are copied back from the workArray to the receiver. numberOfUndefined: Smi; } type FastSmiElements extends ElementsKind; type FastObjectElements extends ElementsKind; // With the pre-processing step in Torque, the exact number of elements // to sort is unknown at the time the sort state is created. // The 'length' property is an upper bound (as per spec), // while the actual size of the backing store is a good guess. // After the pre-processing step, the workarray won't change in length. macro CalculateWorkArrayLength( receiver: JSReceiver, initialReceiverLength: Number): intptr { // TODO(szuend): Implement full range sorting, not only up to MaxSmi. // https://crbug.com/v8/7970. let clampedReceiverLength: uintptr; try { clampedReceiverLength = ChangeSafeIntegerNumberToUintPtr(initialReceiverLength) otherwise UIntPtrOverflow; if (clampedReceiverLength > kSmiMaxValue) { clampedReceiverLength = kSmiMaxValue; } } label UIntPtrOverflow { clampedReceiverLength = kSmiMaxValue; } let workArrayLength: intptr = Convert<intptr>(clampedReceiverLength); try { const object = Cast<JSObject>(receiver) otherwise NoJsObject; const elementsLength = Convert<intptr>(object.elements.length); // In some cases, elements are only on prototypes, but not on the receiver // itself. Do nothing then, as {workArrayLength} got initialized with the // {length} property. if (elementsLength != 0) { workArrayLength = IntPtrMin(workArrayLength, elementsLength); } } label NoJsObject {} return workArrayLength; } transitioning macro NewSortState(implicit context: Context)( receiver: JSReceiver, comparefn: Undefined|Callable, initialReceiverLength: Number): SortState { const sortComparePtr = comparefn != Undefined ? SortCompareUserFn : SortCompareDefault; const map = receiver.map; let loadFn: LoadFn; let storeFn: StoreFn; let deleteFn: DeleteFn; let canUseSameAccessorFn: CanUseSameAccessorFn; try { const a: FastJSArray = Cast<FastJSArray>(receiver) otherwise Slow; // Copy copy-on-write (COW) arrays. array::EnsureWriteableFastElements(a); const elementsKind: ElementsKind = map.elements_kind; if (IsDoubleElementsKind(elementsKind)) { loadFn = Load<FastDoubleElements>; storeFn = Store<FastDoubleElements>; deleteFn = Delete<FastDoubleElements>; canUseSameAccessorFn = CanUseSameAccessor<FastDoubleElements>; } else if (IsFastSmiElementsKind(elementsKind)) { loadFn = Load<FastSmiElements>; storeFn = Store<FastSmiElements>; deleteFn = Delete<FastSmiElements>; canUseSameAccessorFn = CanUseSameAccessor<FastSmiElements>; } else { loadFn = Load<FastObjectElements>; storeFn = Store<FastObjectElements>; deleteFn = Delete<FastObjectElements>; canUseSameAccessorFn = CanUseSameAccessor<FastObjectElements>; } } label Slow { loadFn = Load<GenericElementsAccessor>; storeFn = Store<GenericElementsAccessor>; deleteFn = Delete<GenericElementsAccessor>; canUseSameAccessorFn = CanUseSameAccessor<GenericElementsAccessor>; } const workArrayLength = CalculateWorkArrayLength(receiver, initialReceiverLength); return new SortState{ receiver, initialReceiverMap: map, initialReceiverLength, userCmpFn: comparefn, sortComparePtr, loadFn, storeFn, deleteFn, canUseSameAccessorFn, minGallop: kMinGallopWins, pendingRunsSize: 0, pendingRuns: AllocateZeroedFixedArray(Convert<intptr>(kMaxMergePending)), workArray: AllocateZeroedFixedArray(workArrayLength), tempArray: kEmptyFixedArray, sortLength: 0, numberOfUndefined: 0 }; } const kSuccess: Smi = 0; // The maximum number of entries in a SortState's pending-runs stack. // This is enough to sort arrays of size up to about // 32 * phi ** kMaxMergePending // where phi ~= 1.618. 85 is ridiculously large enough, good for an array with // 2 ** 64 elements. const kMaxMergePending: constexpr int31 = 85; // When we get into galloping mode, we stay there until both runs win less // often then kMinGallop consecutive times. See listsort.txt for more info. const kMinGallopWins: constexpr int31 = 7; // Default size of the temporary array. The temporary array is allocated when // it is first requested, but it has always at least this size. const kSortStateTempSize: Smi = 32; type LoadFn = builtin(Context, SortState, Smi) => (JSAny|TheHole); type StoreFn = builtin(Context, SortState, Smi, JSAny) => Smi; type DeleteFn = builtin(Context, SortState, Smi) => Smi; type CanUseSameAccessorFn = builtin(Context, JSReceiver, Map, Number) => Boolean; type CompareBuiltinFn = builtin(Context, JSAny, JSAny, JSAny) => Number; // The following builtins implement Load/Store for all the Accessors. // The most generic baseline version uses Get-/SetProperty. We do not need // to worry about the prototype chain, because the pre-processing step has // copied values from the prototype chain to the receiver if they were visible // through a hole. transitioning builtin Load<ElementsAccessor : type extends ElementsKind>( context: Context, sortState: SortState, index: Smi): JSAny|TheHole { const receiver = sortState.receiver; if (!HasProperty_Inline(receiver, index)) return TheHole; return GetProperty(receiver, index); } Load<FastSmiElements>( context: Context, sortState: SortState, index: Smi): JSAny|TheHole { const object = UnsafeCast<JSObject>(sortState.receiver); const elements = UnsafeCast<FixedArray>(object.elements); return UnsafeCast<(JSAny | TheHole)>(elements.objects[index]); } Load<FastObjectElements>( context: Context, sortState: SortState, index: Smi): JSAny|TheHole { const object = UnsafeCast<JSObject>(sortState.receiver); const elements = UnsafeCast<FixedArray>(object.elements); return UnsafeCast<(JSAny | TheHole)>(elements.objects[index]); } Load<FastDoubleElements>( context: Context, sortState: SortState, index: Smi): JSAny|TheHole { try { const object = UnsafeCast<JSObject>(sortState.receiver); const elements = UnsafeCast<FixedDoubleArray>(object.elements); const value = elements.floats[index].Value() otherwise IfHole; return AllocateHeapNumberWithValue(value); } label IfHole { return TheHole; } } transitioning builtin Store<ElementsAccessor : type extends ElementsKind>( context: Context, sortState: SortState, index: Smi, value: JSAny): Smi { SetProperty(sortState.receiver, index, value); return kSuccess; } Store<FastSmiElements>( context: Context, sortState: SortState, index: Smi, value: JSAny): Smi { const object = UnsafeCast<JSObject>(sortState.receiver); const elements = UnsafeCast<FixedArray>(object.elements); const value = UnsafeCast<Smi>(value); StoreFixedArrayElement(elements, index, value); return kSuccess; } Store<FastObjectElements>( context: Context, sortState: SortState, index: Smi, value: JSAny): Smi { const object = UnsafeCast<JSObject>(sortState.receiver); const elements = UnsafeCast<FixedArray>(object.elements); elements.objects[index] = value; return kSuccess; } Store<FastDoubleElements>( context: Context, sortState: SortState, index: Smi, value: JSAny): Smi { const object = UnsafeCast<JSObject>(sortState.receiver); const elements = UnsafeCast<FixedDoubleArray>(object.elements); const heapVal = UnsafeCast<HeapNumber>(value); const val = Convert<float64>(heapVal); StoreFixedDoubleArrayElement(elements, index, val); return kSuccess; } transitioning builtin Delete<ElementsAccessor : type extends ElementsKind>( context: Context, sortState: SortState, index: Smi): Smi { const receiver = sortState.receiver; DeleteProperty(receiver, index, LanguageMode::kStrict); return kSuccess; } Delete<FastSmiElements>( context: Context, sortState: SortState, index: Smi): Smi { assert(IsHoleyFastElementsKind(sortState.receiver.map.elements_kind)); const object = UnsafeCast<JSObject>(sortState.receiver); const elements = UnsafeCast<FixedArray>(object.elements); elements.objects[index] = TheHole; return kSuccess; } Delete<FastObjectElements>( context: Context, sortState: SortState, index: Smi): Smi { assert(IsHoleyFastElementsKind(sortState.receiver.map.elements_kind)); const object = UnsafeCast<JSObject>(sortState.receiver); const elements = UnsafeCast<FixedArray>(object.elements); elements.objects[index] = TheHole; return kSuccess; } Delete<FastDoubleElements>( context: Context, sortState: SortState, index: Smi): Smi { assert(IsHoleyFastElementsKind(sortState.receiver.map.elements_kind)); const object = UnsafeCast<JSObject>(sortState.receiver); const elements = UnsafeCast<FixedDoubleArray>(object.elements); elements.floats[index] = kDoubleHole; return kSuccess; } transitioning builtin SortCompareDefault( context: Context, comparefn: JSAny, x: JSAny, y: JSAny): Number { assert(comparefn == Undefined); if (TaggedIsSmi(x) && TaggedIsSmi(y)) { return SmiLexicographicCompare(UnsafeCast<Smi>(x), UnsafeCast<Smi>(y)); } // 5. Let xString be ? ToString(x). const xString = ToString_Inline(x); // 6. Let yString be ? ToString(y). const yString = ToString_Inline(y); // 7. Let xSmaller be the result of performing // Abstract Relational Comparison xString < yString. // 8. If xSmaller is true, return -1. if (StringLessThan(context, xString, yString) == True) return -1; // 9. Let ySmaller be the result of performing // Abstract Relational Comparison yString < xString. // 10. If ySmaller is true, return 1. if (StringLessThan(context, yString, xString) == True) return 1; // 11. Return +0. return 0; } transitioning builtin SortCompareUserFn( context: Context, comparefn: JSAny, x: JSAny, y: JSAny): Number { assert(comparefn != Undefined); const cmpfn = UnsafeCast<Callable>(comparefn); // a. Let v be ? ToNumber(? Call(comparefn, undefined, x, y)). const v = ToNumber_Inline(Call(context, cmpfn, Undefined, x, y)); // b. If v is NaN, return +0. if (NumberIsNaN(v)) return 0; // c. return v. return v; } builtin CanUseSameAccessor<ElementsAccessor : type extends ElementsKind>( context: Context, receiver: JSReceiver, initialReceiverMap: Map, initialReceiverLength: Number): Boolean { if (receiver.map != initialReceiverMap) return False; assert(TaggedIsSmi(initialReceiverLength)); const array = UnsafeCast<JSArray>(receiver); const originalLength = UnsafeCast<Smi>(initialReceiverLength); return SelectBooleanConstant(UnsafeCast<Smi>(array.length) == originalLength); } CanUseSameAccessor<GenericElementsAccessor>( _context: Context, _receiver: JSReceiver, _initialReceiverMap: Map, _initialReceiverLength: Number): Boolean { // Do nothing. We are already on the slow path. return True; } // Re-loading the stack-size is done in a few places. The small macro allows // for easier invariant checks at all use sites. macro GetPendingRunsSize(implicit context: Context)(sortState: SortState): Smi { const stackSize: Smi = sortState.pendingRunsSize; assert(stackSize >= 0); return stackSize; } macro GetPendingRunBase(implicit context: Context)( pendingRuns: FixedArray, run: Smi): Smi { return UnsafeCast<Smi>(pendingRuns.objects[run << 1]); } macro SetPendingRunBase(pendingRuns: FixedArray, run: Smi, value: Smi) { pendingRuns.objects[run << 1] = value; } macro GetPendingRunLength(implicit context: Context)( pendingRuns: FixedArray, run: Smi): Smi { return UnsafeCast<Smi>(pendingRuns.objects[(run << 1) + 1]); } macro SetPendingRunLength(pendingRuns: FixedArray, run: Smi, value: Smi) { pendingRuns.objects[(run << 1) + 1] = value; } macro PushRun(implicit context: Context)( sortState: SortState, base: Smi, length: Smi) { assert(GetPendingRunsSize(sortState) < kMaxMergePending); const stackSize: Smi = GetPendingRunsSize(sortState); const pendingRuns: FixedArray = sortState.pendingRuns; SetPendingRunBase(pendingRuns, stackSize, base); SetPendingRunLength(pendingRuns, stackSize, length); sortState.pendingRunsSize = stackSize + 1; } // Returns the temporary array and makes sure that it is big enough. // TODO(szuend): Implement a better re-size strategy. macro GetTempArray(implicit context: Context)( sortState: SortState, requestedSize: Smi): FixedArray { const minSize: Smi = SmiMax(kSortStateTempSize, requestedSize); const currentSize: Smi = sortState.tempArray.length; if (currentSize >= minSize) { return sortState.tempArray; } const tempArray: FixedArray = AllocateZeroedFixedArray(Convert<intptr>(minSize)); sortState.tempArray = tempArray; return tempArray; } transitioning builtin Copy(implicit context: Context)( source: FixedArray, srcPos: Smi, target: FixedArray, dstPos: Smi, length: Smi): JSAny { assert(srcPos >= 0); assert(dstPos >= 0); assert(srcPos <= source.length - length); assert(dstPos <= target.length - length); // TODO(szuend): Investigate whether this builtin should be replaced // by CopyElements/MoveElements for perfomance. // source and target might be the same array. To avoid overwriting // values in the case of overlaping ranges, elements are copied from // the back when srcPos < dstPos. if (srcPos < dstPos) { let srcIdx: Smi = srcPos + length - 1; let dstIdx: Smi = dstPos + length - 1; while (srcIdx >= srcPos) { target.objects[dstIdx--] = source.objects[srcIdx--]; } } else { let srcIdx: Smi = srcPos; let dstIdx: Smi = dstPos; const to: Smi = srcPos + length; while (srcIdx < to) { target.objects[dstIdx++] = source.objects[srcIdx++]; } } return kSuccess; } // BinaryInsertionSort is the best method for sorting small arrays: it // does few compares, but can do data movement quadratic in the number of // elements. This is an advantage since comparisons are more expensive due // to calling into JS. // // [low, high) is a contiguous range of a array, and is sorted via // binary insertion. This sort is stable. // // On entry, must have low <= start <= high, and that [low, start) is // already sorted. Pass start == low if you do not know!. macro BinaryInsertionSort(implicit context: Context, sortState: SortState)( low: Smi, startArg: Smi, high: Smi) { assert(low <= startArg && startArg <= high); const workArray = sortState.workArray; let start: Smi = low == startArg ? (startArg + 1) : startArg; for (; start < high; ++start) { // Set left to where a[start] belongs. let left: Smi = low; let right: Smi = start; const pivot = UnsafeCast<JSAny>(workArray.objects[right]); // Invariants: // pivot >= all in [low, left). // pivot < all in [right, start). assert(left < right); // Find pivot insertion point. while (left < right) { const mid: Smi = left + ((right - left) >> 1); const order = sortState.Compare(pivot, UnsafeCast<JSAny>(workArray.objects[mid])); if (order < 0) { right = mid; } else { left = mid + 1; } } assert(left == right); // The invariants still hold, so: // pivot >= all in [low, left) and // pivot < all in [left, start), // // so pivot belongs at left. Note that if there are elements equal // to pivot, left points to the first slot after them -- that's why // this sort is stable. Slide over to make room. for (let p: Smi = start; p > left; --p) { workArray.objects[p] = workArray.objects[p - 1]; } workArray.objects[left] = pivot; } } // Return the length of the run beginning at low, in the range [low, // high), low < high is required on entry. "A run" is the longest // ascending sequence, with // // a[low] <= a[low + 1] <= a[low + 2] <= ... // // or the longest descending sequence, with // // a[low] > a[low + 1] > a[low + 2] > ... // // For its intended use in stable mergesort, the strictness of the // definition of "descending" is needed so that the range can safely be // reversed without violating stability (strict ">" ensures there are no // equal elements to get out of order). // // In addition, if the run is "descending", it is reversed, so the // returned length is always an ascending sequence. macro CountAndMakeRun(implicit context: Context, sortState: SortState)( lowArg: Smi, high: Smi): Smi { assert(lowArg < high); const workArray = sortState.workArray; const low: Smi = lowArg + 1; if (low == high) return 1; let runLength: Smi = 2; const elementLow = UnsafeCast<JSAny>(workArray.objects[low]); const elementLowPred = UnsafeCast<JSAny>(workArray.objects[low - 1]); let order = sortState.Compare(elementLow, elementLowPred); // TODO(szuend): Replace with "order < 0" once Torque supports it. // Currently the operator<(Number, Number) has return type // 'never' and uses two labels to branch. const isDescending: bool = order < 0 ? true : false; let previousElement: JSAny = elementLow; for (let idx: Smi = low + 1; idx < high; ++idx) { const currentElement = UnsafeCast<JSAny>(workArray.objects[idx]); order = sortState.Compare(currentElement, previousElement); if (isDescending) { if (order >= 0) break; } else { if (order < 0) break; } previousElement = currentElement; ++runLength; } if (isDescending) { ReverseRange(workArray, lowArg, lowArg + runLength); } return runLength; } macro ReverseRange(array: FixedArray, from: Smi, to: Smi) { let low: Smi = from; let high: Smi = to - 1; while (low < high) { const elementLow = array.objects[low]; const elementHigh = array.objects[high]; array.objects[low++] = elementHigh; array.objects[high--] = elementLow; } } // Merges the two runs at stack indices i and i + 1. // Returns kFailure if we need to bailout, kSuccess otherwise. transitioning builtin MergeAt(implicit context: Context, sortState: SortState)(i: Smi): Smi { const stackSize: Smi = GetPendingRunsSize(sortState); // We are only allowed to either merge the two top-most runs, or leave // the top most run alone and merge the two next runs. assert(stackSize >= 2); assert(i >= 0); assert(i == stackSize - 2 || i == stackSize - 3); const workArray = sortState.workArray; const pendingRuns: FixedArray = sortState.pendingRuns; let baseA: Smi = GetPendingRunBase(pendingRuns, i); let lengthA: Smi = GetPendingRunLength(pendingRuns, i); const baseB: Smi = GetPendingRunBase(pendingRuns, i + 1); let lengthB: Smi = GetPendingRunLength(pendingRuns, i + 1); assert(lengthA > 0 && lengthB > 0); assert(baseA + lengthA == baseB); // Record the length of the combined runs; if i is the 3rd-last run now, // also slide over the last run (which isn't involved in this merge). // The current run i + 1 goes away in any case. SetPendingRunLength(pendingRuns, i, lengthA + lengthB); if (i == stackSize - 3) { const base: Smi = GetPendingRunBase(pendingRuns, i + 2); const length: Smi = GetPendingRunLength(pendingRuns, i + 2); SetPendingRunBase(pendingRuns, i + 1, base); SetPendingRunLength(pendingRuns, i + 1, length); } sortState.pendingRunsSize = stackSize - 1; // Where does b start in a? Elements in a before that can be ignored, // because they are already in place. const keyRight = UnsafeCast<JSAny>(workArray.objects[baseB]); const k: Smi = GallopRight(workArray, keyRight, baseA, lengthA, 0); assert(k >= 0); baseA = baseA + k; lengthA = lengthA - k; if (lengthA == 0) return kSuccess; assert(lengthA > 0); // Where does a end in b? Elements in b after that can be ignored, // because they are already in place. const keyLeft = UnsafeCast<JSAny>(workArray.objects[baseA + lengthA - 1]); lengthB = GallopLeft(workArray, keyLeft, baseB, lengthB, lengthB - 1); assert(lengthB >= 0); if (lengthB == 0) return kSuccess; // Merge what remains of the runs, using a temp array with // min(lengthA, lengthB) elements. if (lengthA <= lengthB) { MergeLow(baseA, lengthA, baseB, lengthB); } else { MergeHigh(baseA, lengthA, baseB, lengthB); } return kSuccess; } // Locates the proper position of key in a sorted array; if the array // contains an element equal to key, return the position immediately to // the left of the leftmost equal element. (GallopRight does the same // except returns the position to the right of the rightmost equal element // (if any)). // // The array is sorted with "length" elements, starting at "base". // "length" must be > 0. // // "hint" is an index at which to begin the search, 0 <= hint < n. The // closer hint is to the final result, the faster this runs. // // The return value is the int offset in 0..length such that // // array[base + offset] < key <= array[base + offset + 1] // // pretending that array[base - 1] is minus infinity and array[base + len] // is plus infinity. In other words, key belongs at index base + k. builtin GallopLeft(implicit context: Context, sortState: SortState)( array: FixedArray, key: JSAny, base: Smi, length: Smi, hint: Smi): Smi { assert(length > 0 && base >= 0); assert(0 <= hint && hint < length); let lastOfs: Smi = 0; let offset: Smi = 1; const baseHintElement = UnsafeCast<JSAny>(array.objects[base + hint]); let order = sortState.Compare(baseHintElement, key); if (order < 0) { // a[base + hint] < key: gallop right, until // a[base + hint + lastOfs] < key <= a[base + hint + offset]. // a[base + length - 1] is highest. const maxOfs: Smi = length - hint; while (offset < maxOfs) { const offsetElement = UnsafeCast<JSAny>(array.objects[base + hint + offset]); order = sortState.Compare(offsetElement, key); // a[base + hint + offset] >= key? Break. if (order >= 0) break; lastOfs = offset; offset = (offset << 1) + 1; // Integer overflow. if (offset <= 0) offset = maxOfs; } if (offset > maxOfs) offset = maxOfs; // Translate back to positive offsets relative to base. lastOfs = lastOfs + hint; offset = offset + hint; } else { // key <= a[base + hint]: gallop left, until // a[base + hint - offset] < key <= a[base + hint - lastOfs]. assert(order >= 0); // a[base + hint] is lowest. const maxOfs: Smi = hint + 1; while (offset < maxOfs) { const offsetElement = UnsafeCast<JSAny>(array.objects[base + hint - offset]); order = sortState.Compare(offsetElement, key); if (order < 0) break; lastOfs = offset; offset = (offset << 1) + 1; // Integer overflow. if (offset <= 0) offset = maxOfs; } if (offset > maxOfs) offset = maxOfs; // Translate back to positive offsets relative to base. const tmp: Smi = lastOfs; lastOfs = hint - offset; offset = hint - tmp; } assert(-1 <= lastOfs && lastOfs < offset && offset <= length); // Now a[base+lastOfs] < key <= a[base+offset], so key belongs // somewhere to the right of lastOfs but no farther right than offset. // Do a binary search, with invariant: // a[base + lastOfs - 1] < key <= a[base + offset]. lastOfs++; while (lastOfs < offset) { const m: Smi = lastOfs + ((offset - lastOfs) >> 1); order = sortState.Compare(UnsafeCast<JSAny>(array.objects[base + m]), key); if (order < 0) { lastOfs = m + 1; // a[base + m] < key. } else { offset = m; // key <= a[base + m]. } } // so a[base + offset - 1] < key <= a[base + offset]. assert(lastOfs == offset); assert(0 <= offset && offset <= length); return offset; } // Exactly like GallopLeft, except that if key already exists in // [base, base + length), finds the position immediately to the right of // the rightmost equal value. // // The return value is the int offset in 0..length such that // // array[base + offset - 1] <= key < array[base + offset] // // or kFailure on error. builtin GallopRight(implicit context: Context, sortState: SortState)( array: FixedArray, key: JSAny, base: Smi, length: Smi, hint: Smi): Smi { assert(length > 0 && base >= 0); assert(0 <= hint && hint < length); let lastOfs: Smi = 0; let offset: Smi = 1; const baseHintElement = UnsafeCast<JSAny>(array.objects[base + hint]); let order = sortState.Compare(key, baseHintElement); if (order < 0) { // key < a[base + hint]: gallop left, until // a[base + hint - offset] <= key < a[base + hint - lastOfs]. // a[base + hint] is lowest. const maxOfs: Smi = hint + 1; while (offset < maxOfs) { const offsetElement = UnsafeCast<JSAny>(array.objects[base + hint - offset]); order = sortState.Compare(key, offsetElement); if (order >= 0) break; lastOfs = offset; offset = (offset << 1) + 1; // Integer overflow. if (offset <= 0) offset = maxOfs; } if (offset > maxOfs) offset = maxOfs; // Translate back to positive offsets relative to base. const tmp: Smi = lastOfs; lastOfs = hint - offset; offset = hint - tmp; } else { // a[base + hint] <= key: gallop right, until // a[base + hint + lastOfs] <= key < a[base + hint + offset]. // a[base + length - 1] is highest. const maxOfs: Smi = length - hint; while (offset < maxOfs) { const offsetElement = UnsafeCast<JSAny>(array.objects[base + hint + offset]); order = sortState.Compare(key, offsetElement); // a[base + hint + ofs] <= key. if (order < 0) break; lastOfs = offset; offset = (offset << 1) + 1; // Integer overflow. if (offset <= 0) offset = maxOfs; } if (offset > maxOfs) offset = maxOfs; // Translate back to positive offests relative to base. lastOfs = lastOfs + hint; offset = offset + hint; } assert(-1 <= lastOfs && lastOfs < offset && offset <= length); // Now a[base + lastOfs] <= key < a[base + ofs], so key belongs // somewhere to the right of lastOfs but no farther right than ofs. // Do a binary search, with invariant // a[base + lastOfs - 1] < key <= a[base + ofs]. lastOfs++; while (lastOfs < offset) { const m: Smi = lastOfs + ((offset - lastOfs) >> 1); order = sortState.Compare(key, UnsafeCast<JSAny>(array.objects[base + m])); if (order < 0) { offset = m; // key < a[base + m]. } else { lastOfs = m + 1; // a[base + m] <= key. } } // so a[base + offset - 1] <= key < a[base + offset]. assert(lastOfs == offset); assert(0 <= offset && offset <= length); return offset; } // Merge the lengthA elements starting at baseA with the lengthB elements // starting at baseB in a stable way, in-place. lengthA and lengthB must // be > 0, and baseA + lengthA == baseB. Must also have that // array[baseB] < array[baseA], // that array[baseA + lengthA - 1] belongs at the end of the merge, // and should have lengthA <= lengthB. transitioning macro MergeLow(implicit context: Context, sortState: SortState)( baseA: Smi, lengthAArg: Smi, baseB: Smi, lengthBArg: Smi) { assert(0 < lengthAArg && 0 < lengthBArg); assert(0 <= baseA && 0 < baseB); assert(baseA + lengthAArg == baseB); let lengthA: Smi = lengthAArg; let lengthB: Smi = lengthBArg; const workArray = sortState.workArray; const tempArray: FixedArray = GetTempArray(sortState, lengthA); Copy(workArray, baseA, tempArray, 0, lengthA); let dest: Smi = baseA; let cursorTemp: Smi = 0; let cursorB: Smi = baseB; workArray.objects[dest++] = workArray.objects[cursorB++]; try { if (--lengthB == 0) goto Succeed; if (lengthA == 1) goto CopyB; let minGallop: Smi = sortState.minGallop; // TODO(szuend): Replace with something that does not have a runtime // overhead as soon as its available in Torque. while (Int32TrueConstant()) { let nofWinsA: Smi = 0; // # of times A won in a row. let nofWinsB: Smi = 0; // # of times B won in a row. // Do the straightforward thing until (if ever) one run appears to // win consistently. // TODO(szuend): Replace with something that does not have a runtime // overhead as soon as its available in Torque. while (Int32TrueConstant()) { assert(lengthA > 1 && lengthB > 0); const order = sortState.Compare( UnsafeCast<JSAny>(workArray.objects[cursorB]), UnsafeCast<JSAny>(tempArray.objects[cursorTemp])); if (order < 0) { workArray.objects[dest++] = workArray.objects[cursorB++]; ++nofWinsB; --lengthB; nofWinsA = 0; if (lengthB == 0) goto Succeed; if (nofWinsB >= minGallop) break; } else { workArray.objects[dest++] = tempArray.objects[cursorTemp++]; ++nofWinsA; --lengthA; nofWinsB = 0; if (lengthA == 1) goto CopyB; if (nofWinsA >= minGallop) break; } } // One run is winning so consistently that galloping may be a huge // win. So try that, and continue galloping until (if ever) neither // run appears to be winning consistently anymore. ++minGallop; let firstIteration: bool = true; while (nofWinsA >= kMinGallopWins || nofWinsB >= kMinGallopWins || firstIteration) { firstIteration = false; assert(lengthA > 1 && lengthB > 0); minGallop = SmiMax(1, minGallop - 1); sortState.minGallop = minGallop; nofWinsA = GallopRight( tempArray, UnsafeCast<JSAny>(workArray.objects[cursorB]), cursorTemp, lengthA, 0); assert(nofWinsA >= 0); if (nofWinsA > 0) { Copy(tempArray, cursorTemp, workArray, dest, nofWinsA); dest = dest + nofWinsA; cursorTemp = cursorTemp + nofWinsA; lengthA = lengthA - nofWinsA; if (lengthA == 1) goto CopyB; // lengthA == 0 is impossible now if the comparison function is // consistent, but we can't assume that it is. if (lengthA == 0) goto Succeed; } workArray.objects[dest++] = workArray.objects[cursorB++]; if (--lengthB == 0) goto Succeed; nofWinsB = GallopLeft( workArray, UnsafeCast<JSAny>(tempArray.objects[cursorTemp]), cursorB, lengthB, 0); assert(nofWinsB >= 0); if (nofWinsB > 0) { Copy(workArray, cursorB, workArray, dest, nofWinsB); dest = dest + nofWinsB; cursorB = cursorB + nofWinsB; lengthB = lengthB - nofWinsB; if (lengthB == 0) goto Succeed; } workArray.objects[dest++] = tempArray.objects[cursorTemp++]; if (--lengthA == 1) goto CopyB; } ++minGallop; // Penalize it for leaving galloping mode sortState.minGallop = minGallop; } } label Succeed { if (lengthA > 0) { Copy(tempArray, cursorTemp, workArray, dest, lengthA); } } label CopyB { assert(lengthA == 1 && lengthB > 0); // The last element of run A belongs at the end of the merge. Copy(workArray, cursorB, workArray, dest, lengthB); workArray.objects[dest + lengthB] = tempArray.objects[cursorTemp]; } } // Merge the lengthA elements starting at baseA with the lengthB elements // starting at baseB in a stable way, in-place. lengthA and lengthB must // be > 0. Must also have that array[baseA + lengthA - 1] belongs at the // end of the merge and should have lengthA >= lengthB. transitioning macro MergeHigh(implicit context: Context, sortState: SortState)( baseA: Smi, lengthAArg: Smi, baseB: Smi, lengthBArg: Smi) { assert(0 < lengthAArg && 0 < lengthBArg); assert(0 <= baseA && 0 < baseB); assert(baseA + lengthAArg == baseB); let lengthA: Smi = lengthAArg; let lengthB: Smi = lengthBArg; const workArray = sortState.workArray; const tempArray: FixedArray = GetTempArray(sortState, lengthB); Copy(workArray, baseB, tempArray, 0, lengthB); // MergeHigh merges the two runs backwards. let dest: Smi = baseB + lengthB - 1; let cursorTemp: Smi = lengthB - 1; let cursorA: Smi = baseA + lengthA - 1; workArray.objects[dest--] = workArray.objects[cursorA--]; try { if (--lengthA == 0) goto Succeed; if (lengthB == 1) goto CopyA; let minGallop: Smi = sortState.minGallop; // TODO(szuend): Replace with something that does not have a runtime // overhead as soon as its available in Torque. while (Int32TrueConstant()) { let nofWinsA: Smi = 0; // # of times A won in a row. let nofWinsB: Smi = 0; // # of times B won in a row. // Do the straightforward thing until (if ever) one run appears to // win consistently. // TODO(szuend): Replace with something that does not have a runtime // overhead as soon as its available in Torque. while (Int32TrueConstant()) { assert(lengthA > 0 && lengthB > 1); const order = sortState.Compare( UnsafeCast<JSAny>(tempArray.objects[cursorTemp]), UnsafeCast<JSAny>(workArray.objects[cursorA])); if (order < 0) { workArray.objects[dest--] = workArray.objects[cursorA--]; ++nofWinsA; --lengthA; nofWinsB = 0; if (lengthA == 0) goto Succeed; if (nofWinsA >= minGallop) break; } else { workArray.objects[dest--] = tempArray.objects[cursorTemp--]; ++nofWinsB; --lengthB; nofWinsA = 0; if (lengthB == 1) goto CopyA; if (nofWinsB >= minGallop) break; } } // One run is winning so consistently that galloping may be a huge // win. So try that, and continue galloping until (if ever) neither // run appears to be winning consistently anymore. ++minGallop; let firstIteration: bool = true; while (nofWinsA >= kMinGallopWins || nofWinsB >= kMinGallopWins || firstIteration) { firstIteration = false; assert(lengthA > 0 && lengthB > 1); minGallop = SmiMax(1, minGallop - 1); sortState.minGallop = minGallop; let k: Smi = GallopRight( workArray, UnsafeCast<JSAny>(tempArray.objects[cursorTemp]), baseA, lengthA, lengthA - 1); assert(k >= 0); nofWinsA = lengthA - k; if (nofWinsA > 0) { dest = dest - nofWinsA; cursorA = cursorA - nofWinsA; Copy(workArray, cursorA + 1, workArray, dest + 1, nofWinsA); lengthA = lengthA - nofWinsA; if (lengthA == 0) goto Succeed; } workArray.objects[dest--] = tempArray.objects[cursorTemp--]; if (--lengthB == 1) goto CopyA; k = GallopLeft( tempArray, UnsafeCast<JSAny>(workArray.objects[cursorA]), 0, lengthB, lengthB - 1); assert(k >= 0); nofWinsB = lengthB - k; if (nofWinsB > 0) { dest = dest - nofWinsB; cursorTemp = cursorTemp - nofWinsB; Copy(tempArray, cursorTemp + 1, workArray, dest + 1, nofWinsB); lengthB = lengthB - nofWinsB; if (lengthB == 1) goto CopyA; // lengthB == 0 is impossible now if the comparison function is // consistent, but we can't assume that it is. if (lengthB == 0) goto Succeed; } workArray.objects[dest--] = workArray.objects[cursorA--]; if (--lengthA == 0) goto Succeed; } ++minGallop; sortState.minGallop = minGallop; } } label Succeed { if (lengthB > 0) { assert(lengthA == 0); Copy(tempArray, 0, workArray, dest - (lengthB - 1), lengthB); } } label CopyA { assert(lengthB == 1 && lengthA > 0); // The first element of run B belongs at the front of the merge. dest = dest - lengthA; cursorA = cursorA - lengthA; Copy(workArray, cursorA + 1, workArray, dest + 1, lengthA); workArray.objects[dest] = tempArray.objects[cursorTemp]; } } // Compute a good value for the minimum run length; natural runs shorter // than this are boosted artificially via binary insertion sort. // // If n < 64, return n (it's too small to bother with fancy stuff). // Else if n is an exact power of 2, return 32. // Else return an int k, 32 <= k <= 64, such that n/k is close to, but // strictly less than, an exact power of 2. // // See listsort.txt for more info. macro ComputeMinRunLength(nArg: Smi): Smi { let n: Smi = nArg; let r: Smi = 0; // Becomes 1 if any 1 bits are shifted off. assert(n >= 0); while (n >= 64) { r = r | (n & 1); n = n >> 1; } const minRunLength: Smi = n + r; assert(nArg < 64 || (32 <= minRunLength && minRunLength <= 64)); return minRunLength; } // Returns true iff run_length(n - 2) > run_length(n - 1) + run_length(n). macro RunInvariantEstablished(implicit context: Context)( pendingRuns: FixedArray, n: Smi): bool { if (n < 2) return true; const runLengthN: Smi = GetPendingRunLength(pendingRuns, n); const runLengthNM: Smi = GetPendingRunLength(pendingRuns, n - 1); const runLengthNMM: Smi = GetPendingRunLength(pendingRuns, n - 2); return runLengthNMM > runLengthNM + runLengthN; } // Examines the stack of runs waiting to be merged, merging adjacent runs // until the stack invariants are re-established: // // 1. run_length(i - 3) > run_length(i - 2) + run_length(i - 1) // 2. run_length(i - 2) > run_length(i - 1) // // TODO(szuend): Remove unnecessary loads. This macro was refactored to // improve readability, introducing unnecessary loads in the // process. Determine if all these extra loads are ok. transitioning macro MergeCollapse(context: Context, sortState: SortState) { const pendingRuns: FixedArray = sortState.pendingRuns; // Reload the stack size because MergeAt might change it. while (GetPendingRunsSize(sortState) > 1) { let n: Smi = GetPendingRunsSize(sortState) - 2; if (!RunInvariantEstablished(pendingRuns, n + 1) || !RunInvariantEstablished(pendingRuns, n)) { if (GetPendingRunLength(pendingRuns, n - 1) < GetPendingRunLength(pendingRuns, n + 1)) { --n; } MergeAt(n); } else if ( GetPendingRunLength(pendingRuns, n) <= GetPendingRunLength(pendingRuns, n + 1)) { MergeAt(n); } else { break; } } } // Regardless of invariants, merge all runs on the stack until only one // remains. This is used at the end of the mergesort. transitioning macro MergeForceCollapse(context: Context, sortState: SortState) { const pendingRuns: FixedArray = sortState.pendingRuns; // Reload the stack size becuase MergeAt might change it. while (GetPendingRunsSize(sortState) > 1) { let n: Smi = GetPendingRunsSize(sortState) - 2; if (n > 0 && GetPendingRunLength(pendingRuns, n - 1) < GetPendingRunLength(pendingRuns, n + 1)) { --n; } MergeAt(n); } } transitioning macro ArrayTimSortImpl(context: Context, sortState: SortState, length: Smi) { if (length < 2) return; let remaining: Smi = length; // March over the array once, left to right, finding natural runs, // and extending short natural runs to minrun elements. let low: Smi = 0; const minRunLength: Smi = ComputeMinRunLength(remaining); while (remaining != 0) { let currentRunLength: Smi = CountAndMakeRun(low, low + remaining); // If the run is short, extend it to min(minRunLength, remaining). if (currentRunLength < minRunLength) { const forcedRunLength: Smi = SmiMin(minRunLength, remaining); BinaryInsertionSort(low, low + currentRunLength, low + forcedRunLength); currentRunLength = forcedRunLength; } // Push run onto pending-runs stack, and maybe merge. PushRun(sortState, low, currentRunLength); MergeCollapse(context, sortState); // Advance to find next run. low = low + currentRunLength; remaining = remaining - currentRunLength; } MergeForceCollapse(context, sortState); assert(GetPendingRunsSize(sortState) == 1); assert(GetPendingRunLength(sortState.pendingRuns, 0) == length); } transitioning macro CompactReceiverElementsIntoWorkArray( implicit context: Context, sortState: SortState)(): Smi { let growableWorkArray = growable_fixed_array::GrowableFixedArray{ array: sortState.workArray, capacity: Convert<intptr>(sortState.workArray.length), length: 0 }; const loadFn = sortState.loadFn; // TODO(szuend): Implement full range sorting, not only up to MaxSmi. // https://crbug.com/v8/7970. const receiverLength: Number = sortState.initialReceiverLength; assert(IsNumberNormalized(receiverLength)); const sortLength: Smi = TaggedIsSmi(receiverLength) ? UnsafeCast<Smi>(receiverLength) : Convert<PositiveSmi>(kSmiMax) otherwise unreachable; // Move all non-undefined elements into {sortState.workArray}, holes // are ignored. let numberOfUndefined: Smi = 0; for (let i: Smi = 0; i < receiverLength; ++i) { const element: JSAny|TheHole = loadFn(context, sortState, i); if (element == TheHole) { // Do nothing for holes. The result is that elements are // compacted at the front of the work array. } else if (element == Undefined) { numberOfUndefined++; } else { growableWorkArray.Push(element); } } // Reset the workArray on the frameState, as it may have grown. sortState.workArray = growableWorkArray.array; sortState.sortLength = sortLength; sortState.numberOfUndefined = numberOfUndefined; return Convert<Smi>(growableWorkArray.length); } transitioning macro CopyWorkArrayToReceiver(implicit context: Context, sortState: SortState)( numberOfNonUndefined: Smi) { const storeFn = sortState.storeFn; const workArray = sortState.workArray; assert(numberOfNonUndefined <= workArray.length); assert( numberOfNonUndefined + sortState.numberOfUndefined <= sortState.sortLength); // Writing the elements back is a 3 step process: // 1. Copy the sorted elements from the workarray to the receiver. // 2. Add {nOfUndefined} undefineds to the receiver. // 3. Depending on the backing store either delete properties or // set them to the TheHole up to {sortState.sortLength}. let index: Smi = 0; for (; index < numberOfNonUndefined; ++index) { storeFn( context, sortState, index, UnsafeCast<JSAny>(workArray.objects[index])); } const numberOfUndefinedEnd: Smi = sortState.numberOfUndefined + numberOfNonUndefined; for (; index < numberOfUndefinedEnd; ++index) { storeFn(context, sortState, index, Undefined); } const end: Smi = sortState.sortLength; const deleteFn = sortState.deleteFn; for (; index < end; ++index) { deleteFn(context, sortState, index); } } transitioning builtin ArrayTimSort(context: Context, sortState: SortState): JSAny { const numberOfNonUndefined: Smi = CompactReceiverElementsIntoWorkArray(); ArrayTimSortImpl(context, sortState, numberOfNonUndefined); try { // The comparison function or toString might have changed the // receiver, if that is the case, we switch to the slow path. sortState.CheckAccessor() otherwise Slow; } label Slow deferred { sortState.ResetToGenericAccessor(); } CopyWorkArrayToReceiver(numberOfNonUndefined); return kSuccess; } // https://tc39.github.io/ecma262/#sec-array.prototype.sort transitioning javascript builtin ArrayPrototypeSort( js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny { // 1. If comparefn is not undefined and IsCallable(comparefn) is false, // throw a TypeError exception. const comparefnObj: JSAny = arguments[0]; const comparefn = Cast<(Undefined | Callable)>(comparefnObj) otherwise ThrowTypeError(MessageTemplate::kBadSortComparisonFunction, comparefnObj); // 2. Let obj be ? ToObject(this value). const obj: JSReceiver = ToObject(context, receiver); // 3. Let len be ? ToLength(? Get(obj, "length")). const len: Number = GetLengthProperty(obj); if (len < 2) return obj; const sortState: SortState = NewSortState(obj, comparefn, len); ArrayTimSort(context, sortState); return obj; } }