/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // We use varying sorts across the self-hosted codebase. All sorts are // consolidated here to avoid confusion and re-implementation of existing // algorithms. // For sorting values with limited range; uint8 and int8. function CountingSort(array, len, signed) { var buffer = new List(); var min = 0; // Map int8 values onto the uint8 range when storing in buffer. if (signed) { min = -128; } for (var i = 0; i < 256; i++) { buffer[i] = 0; } // Populate the buffer for (var i = 0; i < len; i++) { var val = array[i]; buffer[val - min]++ } // Traverse the buffer in order and write back elements to array var val = 0; for (var i = 0; i < len; i++) { // Invariant: sum(buffer[val:]) == len-i while (true) { if (buffer[val] > 0) { array[i] = val + min; buffer[val]--; break; } else { val++; } } } return array; } // Helper for RadixSort function ByteAtCol(x, pos) { return (x >> (pos * 8)) & 0xFF; } function SortByColumn(array, len, aux, col) { const R = 256; let counts = new List(); // |counts| is used to compute the starting index position for each key. // Letting counts[0] always be 0, simplifies the transform step below. // Example: // // Computing frequency counts for the input [1 2 1] gives: // 0 1 2 3 ... (keys) // 0 0 2 1 (frequencies) // // Transforming frequencies to indexes gives: // 0 1 2 3 ... (keys) // 0 0 2 3 (indexes) for (let r = 0; r < R + 1; r++) { counts[r] = 0; } // Compute frequency counts for (let i = 0; i < len; i++) { let val = array[i]; let b = ByteAtCol(val, col); counts[b + 1]++; } // Transform counts to indices. for (let r = 0; r < R; r++) { counts[r+1] += counts[r]; } // Distribute for (let i = 0; i < len; i++) { let val = array[i]; let b = ByteAtCol(val, col); aux[counts[b]++] = val; } // Copy back for (let i = 0; i < len; i++) { array[i] = aux[i]; } } // Sorts integers and float32. |signed| is true for int16 and int32, |floating| // is true for float32. function RadixSort(array, len, buffer, nbytes, signed, floating, comparefn) { // Determined by performance testing. if (len < 128) { QuickSort(array, len, comparefn); return array; } let aux = new List(); for (let i = 0; i < len; i++) { aux[i] = 0; } let view = array; let signMask = 1 << nbytes * 8 - 1; // Preprocess if (floating) { // This happens if the array object is constructed under JIT if (buffer === null) { buffer = callFunction(std_TypedArray_buffer, array); } // Verify that the buffer is non-null assert(buffer !== null, "Attached data buffer should be reified when array length is >= 128."); view = new Int32Array(buffer); // Flip sign bit for positive numbers; flip all bits for negative // numbers for (let i = 0; i < len; i++) { if (view[i] & signMask) { view[i] ^= 0xFFFFFFFF; } else { view[i] ^= signMask } } } else if (signed) { // Flip sign bit for (let i = 0; i < len; i++) { view[i] ^= signMask } } // Sort for (let col = 0; col < nbytes; col++) { SortByColumn(view, len, aux, col); } // Restore original bit representation if (floating) { for (let i = 0; i < len; i++) { if (view[i] & signMask) { view[i] ^= signMask; } else { view[i] ^= 0xFFFFFFFF; } } } else if (signed) { for (let i = 0; i < len; i++) { view[i] ^= signMask } } return array; } // For sorting small arrays. function InsertionSort(array, from, to, comparefn) { let item, swap, i, j; for (i = from + 1; i <= to; i++) { item = array[i]; for (j = i - 1; j >= from; j--) { swap = array[j]; if (comparefn(swap, item) <= 0) break; array[j + 1] = swap; } array[j + 1] = item; } } function SwapArrayElements(array, i, j) { var swap = array[i]; array[i] = array[j]; array[j] = swap; } // A helper function for MergeSort. function Merge(list, start, mid, end, lBuffer, rBuffer, comparefn) { var i, j, k; var sizeLeft = mid - start + 1; var sizeRight = end - mid; // Copy our virtual lists into separate buffers. for (i = 0; i < sizeLeft; i++) lBuffer[i] = list[start + i]; for (j = 0; j < sizeRight; j++) rBuffer[j] = list[mid + 1 + j]; i = 0; j = 0; k = start; while (i < sizeLeft && j < sizeRight) { if (comparefn(lBuffer[i], rBuffer[j]) <= 0) { list[k] = lBuffer[i]; i++; } else { list[k] = rBuffer[j]; j++; } k++; } // Empty out any remaining elements in the buffer. while (i < sizeLeft) { list[k] =lBuffer[i]; i++; k++; } while (j < sizeRight) { list[k] =rBuffer[j]; j++; k++; } } // Helper function for overwriting a sparse array with a // dense array, filling remaining slots with holes. function MoveHoles(sparse, sparseLen, dense, denseLen) { for (var i = 0; i < denseLen; i++) sparse[i] = dense[i]; for (var j = denseLen; j < sparseLen; j++) delete sparse[j]; } // Iterative, bottom up, mergesort. function MergeSort(array, len, comparefn) { // Until recently typed arrays had no sort method. To work around that // many users passed them to Array.prototype.sort. Now that we have a // typed array specific sorting method it makes sense to divert to it // when possible. if (IsPossiblyWrappedTypedArray(array)) { return callFunction(TypedArraySort, array, comparefn); } // To save effort we will do all of our work on a dense list, // then create holes at the end. var denseList = new List(); var denseLen = 0; for (var i = 0; i < len; i++) { if (i in array) denseList[denseLen++] = array[i]; } if (denseLen < 1) return array; // Insertion sort for small arrays, where "small" is defined by performance // testing. if (denseLen < 24) { InsertionSort(denseList, 0, denseLen - 1, comparefn); MoveHoles(array, len, denseList, denseLen); return array; } // We do all of our allocating up front var lBuffer = new List(); var rBuffer = new List(); var mid, end, endOne, endTwo; for (var windowSize = 1; windowSize < denseLen; windowSize = 2 * windowSize) { for (var start = 0; start < denseLen - 1; start += 2 * windowSize) { assert(windowSize < denseLen, "The window size is larger than the array denseLength!"); // The midpoint between the two subarrays. mid = start + windowSize - 1; // To keep from going over the edge. end = start + 2 * windowSize - 1; end = end < denseLen - 1 ? end : denseLen - 1; // Skip lopsided runs to avoid doing useless work if (mid > end) continue; Merge(denseList, start, mid, end, lBuffer, rBuffer, comparefn); } } MoveHoles(array, len, denseList, denseLen); return array; } // Rearranges the elements in array[from:to + 1] and returns an index j such that: // - from < j < to // - each element in array[from:j] is less than or equal to array[j] // - each element in array[j + 1:to + 1] greater than or equal to array[j]. function Partition(array, from, to, comparefn) { assert(to - from >= 3, "Partition will not work with less than three elements"); var medianIndex = (from + to) >> 1; var i = from + 1; var j = to; SwapArrayElements(array, medianIndex, i); // Median of three pivot selection. if (comparefn(array[from], array[to]) > 0) SwapArrayElements(array, from, to); if (comparefn(array[i], array[to]) > 0) SwapArrayElements(array, i, to); if (comparefn(array[from], array[i]) > 0) SwapArrayElements(array, from, i); var pivotIndex = i; // Hoare partition method. for(;;) { do i++; while (comparefn(array[i], array[pivotIndex]) < 0); do j--; while (comparefn(array[j], array[pivotIndex]) > 0); if (i > j) break; SwapArrayElements(array, i, j); } SwapArrayElements(array, pivotIndex, j); return j; } // In-place QuickSort. function QuickSort(array, len, comparefn) { // Managing the stack ourselves seems to provide a small performance boost. var stack = new List(); var top = 0; var start = 0; var end = len - 1; var pivotIndex, i, j, leftLen, rightLen; for (;;) { // Insertion sort for the first N elements where N is some value // determined by performance testing. if (end - start <= 23) { InsertionSort(array, start, end, comparefn); if (top < 1) break; end = stack[--top]; start = stack[--top]; } else { pivotIndex = Partition(array, start, end, comparefn); // Calculate the left and right sub-array lengths and save // stack space by directly modifying start/end so that // we sort the longest of the two during the next iteration. // This reduces the maximum stack size to log2(len). leftLen = (pivotIndex - 1) - start; rightLen = end - (pivotIndex + 1); if (rightLen > leftLen) { stack[top++] = start; stack[top++] = pivotIndex - 1; start = pivotIndex + 1; } else { stack[top++] = pivotIndex + 1; stack[top++] = end; end = pivotIndex - 1; } } } return array; }