| author | Morgan Phillips <winter2718@gmail.com> |
| Sun, 24 Jan 2016 19:32:22 -0600 | |
| changeset 281471 | 1c4b0a89fd5b |
| parent 281470 | 4bd447a3d7da |
| child 281472 | a86aa13dc19c |
| push id | 29940 |
| push user | cbook@mozilla.com |
| push date | 2016-01-25 10:50 +0000 |
| treeherder | mozilla-central@67c66c2878ae [default view] [failures only] |
| perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
| reviewers | till |
| bugs | 715181 |
| milestone | 46.0a1 |
| first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
| last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/js/src/builtin/Array.js +++ b/js/src/builtin/Array.js @@ -190,16 +190,46 @@ function ArrayStaticSome(list, callbackf if (arguments.length < 2) ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'Array.some'); if (!IsCallable(callbackfn)) ThrowTypeError(JSMSG_NOT_FUNCTION, DecompileArg(1, callbackfn)); var T = arguments.length > 2 ? arguments[2] : void 0; return callFunction(ArraySome, list, callbackfn, T); } +/* ES6 draft 2016-1-15 22.1.3.25 Array.prototype.sort (comparefn) */ +function ArraySort(comparefn) { + /* Step 1. */ + var O = ToObject(this); + + /* Step 2. */ + var len = TO_UINT32(O.length); + + /* 22.1.3.25.1 Runtime Semantics: SortCompare( x, y ) */ + var wrappedCompareFn = comparefn; + comparefn = function(x, y) { + /* Steps 1-3. */ + if (x === undefined) { + if (y === undefined) + return 0; + return 1; + } + if (y === undefined) + return -1; + + /* Step 4.a. */ + var v = ToNumber(wrappedCompareFn(x, y)); + + /* Step 4.b-c. */ + return v !== v ? 0 : v; + } + + return MergeSort(O, len, comparefn); +} + /* ES5 15.4.4.18. */ function ArrayForEach(callbackfn/*, thisArg*/) { /* Step 1. */ var O = ToObject(this); /* Steps 2-3. */ var len = TO_UINT32(O.length);
new file mode 100644 --- /dev/null +++ b/js/src/builtin/Sorting.js @@ -0,0 +1,185 @@ +/* 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 small arrays. +function InsertionSort(array, from, to, comparefn) { + var item, swap; + for (var i = from + 1; i <= to; i++) { + item = array[i]; + for (var 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(array, start, mid, end, lBuffer, rBuffer, comparefn) { + var i, j, k; + + var sizeLeft = mid - start + 1; + var sizeRight = end - mid; + + // Copy our virtual arrays into separate buffers. + for (i = 0; i < sizeLeft; i++) + lBuffer[i] = array[start + i]; + + for (j = 0; j < sizeRight; j++) + rBuffer[j] = array[mid + 1 + j]; + + i = 0; + j = 0; + k = start; + while (i < sizeLeft && j < sizeRight) { + if (comparefn(lBuffer[i], rBuffer[j]) <= 0) { + array[k] = lBuffer[i]; + i++; + } else { + array[k] = rBuffer[j]; + j++; + } + k++; + } + + // Empty out any remaining elements in the buffer. + while (i < sizeLeft) { + array[k] = lBuffer[i]; + i++; + k++; + } + + while (j < sizeRight) { + array[k] = rBuffer[j]; + j++; + k++; + } +} + +// Iterative, bottom up, mergesort. +function MergeSort(array, len, comparefn) { + // Insertion sort for small arrays, where "small" is defined by performance + // testing. + if (len < 24) { + InsertionSort(array, 0, len - 1, comparefn); + 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 < len; windowSize = 2*windowSize) { + for (var start = 0; start < len - 1; start += 2*windowSize) { + assert(windowSize < len, "The window size is larger than the array length!"); + // The midpoint between the two subarrays. + mid = start + windowSize - 1; + // To keep from going over the edge. + end = start + 2 * windowSize - 1; + end = end < len - 1 ? end : len - 1; + // Skip lopsided runs to avoid doing useless work + if (mid > end) + continue; + Merge(array, start, mid, end, lBuffer, rBuffer, comparefn); + } + } + 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; +}
--- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -934,122 +934,16 @@ function TypedArraySome(callbackfn, this if (testResult) return true; } // Step 10. return false; } -// For sorting small arrays -function InsertionSort(array, from, to, comparefn) { - var item, swap; - for (var i = from + 1; i <= to; i++) { - item = array[i]; - for (var 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; -} - -// 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 median_i = (from + to) >> 1; - - var i = from + 1; - var j = to; - - SwapArrayElements(array, median_i, 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 pivot_i = i; - - // Hoare partition method - for(;;) { - do i++; while (comparefn(array[i], array[pivot_i]) < 0); - do j--; while (comparefn(array[j], array[pivot_i]) > 0); - if (i > j) - break; - SwapArrayElements(array, i, j); - } - - SwapArrayElements(array, pivot_i, 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 pivot_i, i, j, l_len, r_len; - - 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 { - pivot_i = 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) - l_len = (pivot_i - 1) - start; - r_len = end - (pivot_i + 1); - - if (r_len > l_len) { - stack[top++] = start; - stack[top++] = pivot_i - 1; - start = pivot_i + 1; - } else { - stack[top++] = pivot_i + 1; - stack[top++] = end; - end = pivot_i - 1; - } - - } - } - return array; -} - // ES6 draft 20151210 22.2.3.26 // Cases are ordered according to likelihood of occurrence // as opposed to the ordering in the spec. function TypedArrayCompare(x, y) { // Step 1. assert(typeof x === "number" && typeof y === "number", "x and y are not numbers.");
--- a/js/src/jit-test/tests/self-hosting/invoke-self-hosted-with-primitive-this.js +++ b/js/src/jit-test/tests/self-hosting/invoke-self-hosted-with-primitive-this.js @@ -1,7 +1,7 @@ try { [0,0].sort(Array.some) "".replace(RegExp(), Array.reduce) } catch (error) { - if (!(error instanceof TypeError && error.message == "0 is not a function")) + if (!(error instanceof TypeError && /^\w is not a function$/.test(error.message))) throw error; -} \ No newline at end of file +}
--- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -1812,16 +1812,53 @@ js::array_sort(JSContext* cx, unsigned a } else { fval.setNull(); } RootedObject obj(cx, ToObject(cx, args.thisv())); if (!obj) return false; + ComparatorMatchResult comp = MatchNumericComparator(cx, fval); + if (comp == Match_Failure) + return false; + + if (!fval.isNull() && comp == Match_None) { + /* + * Non-optimized user supplied comparators perform much better when + * called from within a self-hosted sorting function. + */ + RootedAtom selfHostedSortAtom(cx, Atomize(cx, "ArraySort", 9)); + RootedPropertyName selfHostedSortName(cx, selfHostedSortAtom->asPropertyName()); + RootedValue selfHostedSortValue(cx); + + if (!GlobalObject::getIntrinsicValue(cx, cx->global(), selfHostedSortName, + &selfHostedSortValue)) { + return false; + } + + MOZ_ASSERT(selfHostedSortValue.isObject()); + MOZ_ASSERT(selfHostedSortValue.toObject().is<JSFunction>()); + + InvokeArgs iargs(cx); + + if (!iargs.init(1)) + return false; + + iargs.setCallee(selfHostedSortValue); + iargs.setThis(args.thisv()); + iargs[0].set(fval); + + if (!Invoke(cx, iargs)) + return false; + + args.rval().set(iargs.rval()); + return true; + } + uint32_t len; if (!GetLengthProperty(cx, obj, &len)) return false; if (len < 2) { /* [] and [a] remain unchanged when sorted. */ args.rval().setObject(*obj); return true; } @@ -1912,37 +1949,23 @@ js::array_sort(JSContext* cx, unsigned a SortComparatorLexicographicInt32())) { return false; } } else { if (!SortLexicographically(cx, &vec, n)) return false; } } else { - ComparatorMatchResult comp = MatchNumericComparator(cx, fval); - if (comp == Match_Failure) - return false; - - if (comp != Match_None) { - if (allInts) { - JS_ALWAYS_TRUE(vec.resize(n * 2)); - if (!MergeSort(vec.begin(), n, vec.begin() + n, SortComparatorInt32s[comp])) - return false; - } else { - if (!SortNumerically(cx, &vec, n, comp)) - return false; - } + if (allInts) { + JS_ALWAYS_TRUE(vec.resize(n * 2)); + if (!MergeSort(vec.begin(), n, vec.begin() + n, SortComparatorInt32s[comp])) + return false; } else { - FastInvokeGuard fig(cx, fval); - JS_ALWAYS_TRUE(vec.resize(n * 2)); - if (!MergeSort(vec.begin(), n, vec.begin() + n, - SortComparatorFunction(cx, fval, fig))) - { + if (!SortNumerically(cx, &vec, n, comp)) return false; - } } } if (!InitArrayElements(cx, obj, 0, uint32_t(n), vec.begin(), ShouldUpdateTypes::DontUpdate)) return false; } /* Set undefs that sorted after the rest of elements. */
--- a/js/src/moz.build +++ b/js/src/moz.build @@ -706,16 +706,17 @@ selfhosted.inputs = [ 'builtin/Map.js', 'builtin/Module.js', 'builtin/Number.js', 'builtin/Object.js', 'builtin/Reflect.js', 'builtin/RegExp.js', 'builtin/String.js', 'builtin/Set.js', + 'builtin/Sorting.js', 'builtin/TypedArray.js', 'builtin/TypedObject.js', 'builtin/WeakSet.js' ] if CONFIG['JS_HAS_CTYPES']: if CONFIG['MOZ_NATIVE_FFI']: CXXFLAGS += CONFIG['MOZ_FFI_CFLAGS']
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Array/sort_basics.js @@ -0,0 +1,34 @@ +// Note: failed runs should include their "SEED" value in error messages, +// setting "const SEED" to that value will recreate the data from any such run. +const SEED = (Math.random() * 10) + 1; + +// Create an array filled with random values, 'size' is the desired length of +// the array and 'seed' is an initial value supplied to a pseudo-random number +// generator. +function genRandomArray(size, seed) { + return Array.from(XorShiftGenerator(seed, size)); +} + +function SortTest(size, seed) { + let arrOne = genRandomArray(size, seed); + let arrTwo = Array.from(arrOne); + let arrThree = Array.from(arrOne); + + // Test numeric comparators against typed array sort. + assertDeepEq(Array.from((Int32Array.from(arrOne)).sort()), + arrTwo.sort((x, y) => (x - y)), + `The arr is not properly sorted! seed: ${SEED}`); + + // Use multiplication to kill comparator optimization and trigger + // self-hosted sorting. + assertDeepEq(Array.from((Int32Array.from(arrOne)).sort()), + arrThree.sort((x, y) => (1*x - 1*y)), + `The arr is not properly sorted! seed: ${SEED}`); +} + +SortTest(2048, SEED); +SortTest(16, SEED); +SortTest(0, SEED); + +if (typeof reportCompare === "function") + reportCompare(true, true);
new file mode 100644 --- /dev/null +++ b/js/src/tests/ecma_6/Array/sort_small.js @@ -0,0 +1,33 @@ +// Sort every possible permutation of some arrays. +function sortAllPermutations(data, comparefn) { + for (let permutation of Permutations(Array.from(data))) { + let sorted = (Array.from(permutation)).sort(comparefn); + for (let i in sorted) { + assertEq(sorted[i], data[i], + [`[${permutation}].sort(${comparefn})`, + `returned ${sorted}, expected ${data}`].join(' ')); + } + } +} + +let lex = [2112, "bob", "is", "my", "name"]; +let nans = [1/undefined, NaN, Number.NaN] + +let num1 = [-11, 0, 0, 100, 101]; +let num2 = [-11, 100, 201234.23, undefined, undefined]; + +sortAllPermutations(lex); +sortAllPermutations(nans); + +sortAllPermutations(nans, (x, y) => x - y); +// Multiplication kills comparator optimization. +sortAllPermutations(nans, (x, y) => (1*x - 1*y)); + +sortAllPermutations(num1, (x, y) => x - y); +sortAllPermutations(num1, (x, y) => (1*x - 1*y)); + +sortAllPermutations(num2, (x, y) => x - y); +sortAllPermutations(num2, (x, y) => (1*x - 1*y)); + +if (typeof reportCompare === "function") + reportCompare(true, true);
--- a/js/src/tests/ecma_6/TypedArray/sort_basics.js +++ b/js/src/tests/ecma_6/TypedArray/sort_basics.js @@ -1,37 +1,23 @@ // Note: failed runs should include their "SEED" value in error messages, // setting "const SEED" to that value will recreate the data from any such run. const SEED = (Math.random() * 10) + 1; -// An xorshift pseudo-random number generator see: -// https://en.wikipedia.org/wiki/Xorshift#xorshift.2A -// This generator will always produce a value, n, where -// 0 <= n <= 255 -function *xorShiftGenerator(seed, size) { - let x = seed; - for (let i = 0; i < size; i++) { - x ^= x >> 12; - x ^= x << 25; - x ^= x >> 27; - yield x % 256; - } -} - // Fill up an array buffer with random values and return it in raw form. // 'size' is the desired length of the view we will place atop the buffer, // 'width' is the bit-width of the view we plan on placing atop the buffer, // and 'seed' is an initial value supplied to a pseudo-random number generator. function genRandomArrayBuffer(size, width, seed) { let buf = new ArrayBuffer((width / 8) * size); let arr = new Uint8Array(buf); let len = 0; // We generate a random number, n, where 0 <= n <= 255 for every space // available in our buffer. - for (let n of xorShiftGenerator(seed, buf.byteLength)) + for (let n of XorShiftGenerator(seed, buf.byteLength)) arr[len++] = n; return buf; } // Because we can generate any possible combination of bits, some floating point // entries will take on -Infinity, Infinity, and NaN values. This function ensures // that a is <= b, where, like the default comparator, -Infinity < Infinity and // every non-NaN < NaN.
--- a/js/src/tests/ecma_6/TypedArray/sort_small.js +++ b/js/src/tests/ecma_6/TypedArray/sort_small.js @@ -1,27 +1,8 @@ -function swapElements(arr, i, j) { - var swap = arr[i]; - arr[i] = arr[j]; - arr[j] = swap; -} - -// Yield every permutation of the elements in some iterable. -function *permutations(items) { - if (items.length == 0) { - yield []; - } else { - for (let i = 0; i < items.length; i++) { - swapElements(items, 0, i); - for (let e of permutations(items.slice(1, items.length))) - yield [items[0]].concat(e); - } - } -} - // Pre-sorted test data, it's important that these arrays remain in ascending order. let i32 = [-2147483648, -320000, -244000, 2147483647] let u32 = [0, 987632, 4294967295] let i16 = [-32768, -999, 1942, 32767] let u16 = [0, 65535, 65535] let i8 = [-128, 127] let u8 = [255] @@ -30,17 +11,17 @@ let u8 = [255] // Float Arrays are used because all other types convert -0 and NaN to +0. let f32 = [-2147483647, -2147483646.99, -0, 0, 2147483646.99, NaN] let f64 = [-2147483646.99, -0, 0, 4147483646.99, NaN] let nans = [1/undefined, NaN, Number.NaN] // Sort every possible permutation of an arrays function sortAllPermutations(dataType, testData) { let reference = new dataType(testData); - for (let permutation of permutations(testData)) + for (let permutation of Permutations(testData)) assertDeepEq((new dataType(permutation)).sort(), reference); } sortAllPermutations(Int32Array, i32); sortAllPermutations(Uint32Array, u32); sortAllPermutations(Int16Array, i16); sortAllPermutations(Uint16Array, u16); sortAllPermutations(Int8Array, i8);
--- a/js/src/tests/shell.js +++ b/js/src/tests/shell.js @@ -368,16 +368,50 @@ function enterFunc (funcName) { if (!funcName.match(/\(\)$/)) funcName += "()"; callStack.push(funcName); } /* + * An xorshift pseudo-random number generator see: + * https://en.wikipedia.org/wiki/Xorshift#xorshift.2A + * This generator will always produce a value, n, where + * 0 <= n <= 255 + */ +function *XorShiftGenerator(seed, size) { + let x = seed; + for (let i = 0; i < size; i++) { + x ^= x >> 12; + x ^= x << 25; + x ^= x >> 27; + yield x % 256; + } +} + +/* + * Yield every permutation of the elements in some iterable. + */ +function *Permutations(items) { + if (items.length == 0) { + yield []; + } else { + let swap; + for (let i = 0; i < items.length; i++) { + swap = items[0]; + items[0] = items[i]; + items[i] = swap; + for (let e of Permutations(items.slice(1, items.length))) + yield [items[0]].concat(e); + } + } +} + +/* * Pops the top funcName off the call stack. funcName is optional, and can be * used to check push-pop balance. */ function exitFunc (funcName) { var lastFunc = callStack.pop(); if (funcName)