mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1121937 - Implement %TypedArray%.prototype.sort; r=jorendorff
This commit is contained in:
parent
cdc32ec0f5
commit
90999d1029
@ -903,6 +903,182 @@ function TypedArraySome(callbackfn, thisArg = undefined) {
|
||||
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.");
|
||||
|
||||
// Steps 6 - 7.
|
||||
var diff = x - y;
|
||||
if (diff)
|
||||
return diff;
|
||||
|
||||
// Steps 8 - 10.
|
||||
if (x === 0 && y === 0)
|
||||
return (1/x > 0 ? 1 : 0) - (1/y > 0 ? 1 : 0);
|
||||
|
||||
// Step 2. Implemented in TypedArraySort
|
||||
|
||||
// Step 3.
|
||||
if (Number_isNaN(x) && Number_isNaN(y))
|
||||
return 0;
|
||||
|
||||
// Steps 4 - 5.
|
||||
if (Number_isNaN(x) || Number_isNaN(y))
|
||||
return Number_isNaN(x) ? 1 : -1;
|
||||
|
||||
}
|
||||
|
||||
// ES6 draft 20151210 22.2.3.26 %TypedArray%.prototype.sort ( comparefn ).
|
||||
function TypedArraySort(comparefn) {
|
||||
// This function is not generic.
|
||||
if (!IsObject(this) || !IsTypedArray(this)) {
|
||||
return callFunction(CallTypedArrayMethodIfWrapped, this, comparefn,
|
||||
"TypedArraySort");
|
||||
}
|
||||
|
||||
// Step 1.
|
||||
var obj = this;
|
||||
|
||||
// Step 2.
|
||||
var buffer = TypedArrayBuffer(obj);
|
||||
if (IsDetachedBuffer(buffer))
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
|
||||
|
||||
// Step 3.
|
||||
var len = TypedArrayLength(obj);
|
||||
|
||||
if (comparefn === undefined) {
|
||||
comparefn = TypedArrayCompare;
|
||||
} else {
|
||||
// To satisfy step 2 from TypedArray SortCompare described in 22.2.3.26
|
||||
// the user supplied comparefn is wrapped.
|
||||
var wrappedCompareFn = comparefn;
|
||||
comparefn = function(x, y) {
|
||||
// Step a.
|
||||
var v = wrappedCompareFn(x, y);
|
||||
// Step b.
|
||||
if (IsDetachedBuffer(buffer))
|
||||
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
|
||||
// Step c. is redundant, see:
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1121937#c36
|
||||
// Step d.
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
return QuickSort(obj, len, comparefn);
|
||||
}
|
||||
|
||||
// ES6 draft 20150304 %TypedArray%.prototype.subarray
|
||||
function TypedArraySubarray(begin, end) {
|
||||
// Step 1.
|
||||
|
95
js/src/tests/ecma_6/TypedArray/sort_basics.js
Normal file
95
js/src/tests/ecma_6/TypedArray/sort_basics.js
Normal file
@ -0,0 +1,95 @@
|
||||
// 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))
|
||||
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.
|
||||
function lte(a, b) {
|
||||
if (isNaN(b))
|
||||
return true;
|
||||
return a <= b;
|
||||
}
|
||||
|
||||
// A a >= b counterpart to the helper function above.
|
||||
function gte(a, b) {
|
||||
return lte(b, a);
|
||||
}
|
||||
|
||||
// A custom comparator.
|
||||
function cmp(a, b) {
|
||||
return lte(a, b) ? gte(a, b) ? 0 : -1 : 1;
|
||||
}
|
||||
|
||||
function SortTest(dataType, dataSource) {
|
||||
let typedArray = new dataType(dataSource);
|
||||
let originalValues = Array.from(typedArray);
|
||||
|
||||
// Test the default comparator
|
||||
typedArray.sort();
|
||||
|
||||
// Test against regular array sort
|
||||
assertDeepEq(Array.from(typedArray), Array.from(originalValues).sort(cmp),
|
||||
`The array is not properly sorted! seed: ${SEED}`);
|
||||
|
||||
// Another sanity check
|
||||
for (let i=0; i < typedArray.length - 1; i++)
|
||||
assertEq(lte(typedArray[i], typedArray[i + 1]), true,
|
||||
`The array is not properly sorted! ${typedArray[i]} > ${typedArray[i + 1]}, seed: ${SEED}`)
|
||||
|
||||
// Test custom comparators
|
||||
typedArray.sort((x, y) => cmp(y, x));
|
||||
|
||||
// The array should be in reverse order
|
||||
for (let i=typedArray.length - 2; i >= 0; i--)
|
||||
assertEq(gte(typedArray[i], typedArray[i + 1]), true,
|
||||
`The array is not properly sorted! ${typedArray[i]} < ${typedArray[i + 1]}, seed: ${SEED}`)
|
||||
}
|
||||
|
||||
function RunTests(arrayLength) {
|
||||
SortTest(Int32Array, genRandomArrayBuffer(arrayLength, 32, SEED));
|
||||
SortTest(Uint32Array, genRandomArrayBuffer(arrayLength, 32, SEED));
|
||||
SortTest(Int16Array, genRandomArrayBuffer(arrayLength, 16, SEED));
|
||||
SortTest(Uint16Array, genRandomArrayBuffer(arrayLength, 16, SEED));
|
||||
SortTest(Int8Array, genRandomArrayBuffer(arrayLength, 8, SEED));
|
||||
SortTest(Uint8Array, genRandomArrayBuffer(arrayLength, 8, SEED));
|
||||
SortTest(Float32Array, genRandomArrayBuffer(arrayLength, 32, SEED));
|
||||
SortTest(Float64Array, genRandomArrayBuffer(arrayLength, 64, SEED));
|
||||
}
|
||||
|
||||
RunTests(256);
|
||||
RunTests(16);
|
||||
RunTests(0);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
32
js/src/tests/ecma_6/TypedArray/sort_comparators.js
Normal file
32
js/src/tests/ecma_6/TypedArray/sort_comparators.js
Normal file
@ -0,0 +1,32 @@
|
||||
// Ensure that sorts finish even if a comparator adds items
|
||||
// Note: the array is not expected to be properly sorted.
|
||||
let outsideArray = new Int32Array([1, 99, 2]);
|
||||
function addingComparator(x, y) {
|
||||
if (x == 99 || y == 99) {
|
||||
outsideArray[0] = 101;
|
||||
outsideArray[outsideArray.length - 1] = 102;
|
||||
}
|
||||
return x - y;
|
||||
}
|
||||
outsideArray.sort(addingComparator);
|
||||
assertEq(outsideArray.every(x => [1, 2, 99, 101, 102].includes(x)), true);
|
||||
|
||||
// Ensure that sorts finish even if a comparator calls sort again
|
||||
// Note: the array is not expected to be properly sorted.
|
||||
outsideArray = new Int32Array([1, 99, 2]);
|
||||
function recursiveComparator(x, y) {
|
||||
outsideArray.sort();
|
||||
return x - y;
|
||||
}
|
||||
outsideArray.sort(recursiveComparator);
|
||||
assertEq(outsideArray.every(x => outsideArray.includes(x)), true)
|
||||
|
||||
// Ensure that NaN's returned from custom comparators behave as / are converted
|
||||
// to +0s.
|
||||
let nanComparatorData = [2112, 42, 1111, 34];
|
||||
let nanComparatorArray = new Int32Array(nanComparatorData);
|
||||
nanComparatorArray.sort((x, y) => NaN);
|
||||
assertEq(nanComparatorData.every(x => nanComparatorArray.includes(x)), true);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
49
js/src/tests/ecma_6/TypedArray/sort_errors.js
Normal file
49
js/src/tests/ecma_6/TypedArray/sort_errors.js
Normal file
@ -0,0 +1,49 @@
|
||||
// |reftest| skip-if(!xulRuntime.shell)
|
||||
|
||||
// Ensure that TypedArrays throw when attempting to sort a detached ArrayBuffer
|
||||
assertThrowsInstanceOf(() => {
|
||||
let buffer = new ArrayBuffer(32);
|
||||
let array = new Int32Array(buffer);
|
||||
neuter(buffer, "change-data");
|
||||
array.sort();
|
||||
}, TypeError);
|
||||
|
||||
// Ensure that TypedArray.prototype.sort will not sort non-TypedArrays
|
||||
assertThrowsInstanceOf(() => {
|
||||
let array = [4, 3, 2, 1];
|
||||
Int32Array.prototype.sort.call(array);
|
||||
}, TypeError);
|
||||
|
||||
assertThrowsInstanceOf(() => {
|
||||
Int32Array.prototype.sort.call({a: 1, b: 2});
|
||||
}, TypeError);
|
||||
|
||||
assertThrowsInstanceOf(() => {
|
||||
Int32Array.prototype.sort.call(Int32Array.prototype);
|
||||
}, TypeError);
|
||||
|
||||
assertThrowsInstanceOf(() => {
|
||||
let buf = new ArrayBuffer(32);
|
||||
Int32Array.prototype.sort.call(buf);
|
||||
}, TypeError);
|
||||
|
||||
// Ensure that comparator errors are propagataed
|
||||
function badComparator(x, y) {
|
||||
if (x == 99 && y == 99)
|
||||
throw new TypeError;
|
||||
return x - y;
|
||||
}
|
||||
|
||||
assertThrowsInstanceOf(() => {
|
||||
let array = new Uint8Array([99, 99, 99, 99]);
|
||||
array.sort(badComparator);
|
||||
}, TypeError);
|
||||
|
||||
assertThrowsInstanceOf(() => {
|
||||
let array = new Uint8Array([1, 99, 2, 99]);
|
||||
array.sort(badComparator);
|
||||
}, TypeError);
|
||||
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
9
js/src/tests/ecma_6/TypedArray/sort_globals.js
Normal file
9
js/src/tests/ecma_6/TypedArray/sort_globals.js
Normal file
@ -0,0 +1,9 @@
|
||||
// TypedArray.prototype.sort should work across globals
|
||||
let g2 = newGlobal();
|
||||
assertDeepEq(
|
||||
Int32Array.prototype.sort.call(new g2.Int32Array([3, 2, 1])),
|
||||
new Int32Array([1, 2, 3])
|
||||
);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
54
js/src/tests/ecma_6/TypedArray/sort_small.js
Normal file
54
js/src/tests/ecma_6/TypedArray/sort_small.js
Normal file
@ -0,0 +1,54 @@
|
||||
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]
|
||||
|
||||
// Test the behavior in the default comparator as described in 22.2.3.26.
|
||||
// The spec boils down to, -0s come before +0s, and NaNs always come last.
|
||||
// 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))
|
||||
assertDeepEq((new dataType(permutation)).sort(), reference);
|
||||
}
|
||||
|
||||
sortAllPermutations(Int32Array, i32);
|
||||
sortAllPermutations(Uint32Array, u32);
|
||||
sortAllPermutations(Int16Array, i16);
|
||||
sortAllPermutations(Uint16Array, u16);
|
||||
sortAllPermutations(Int8Array, i8);
|
||||
sortAllPermutations(Uint8Array, u8);
|
||||
sortAllPermutations(Float32Array, f32);
|
||||
sortAllPermutations(Float64Array, f64);
|
||||
sortAllPermutations(Float32Array, nans);
|
||||
sortAllPermutations(Float64Array, nans);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
72
js/src/tests/ecma_6/TypedArray/sort_snans.js
Normal file
72
js/src/tests/ecma_6/TypedArray/sort_snans.js
Normal file
@ -0,0 +1,72 @@
|
||||
// Ensure that signaling NaN's don't cause problems while sorting
|
||||
|
||||
function getNaNArray(length) {
|
||||
let a = [];
|
||||
for (let i = 0; i < length; i++)
|
||||
a.push(NaN);
|
||||
return a;
|
||||
}
|
||||
|
||||
// Test every skipNth value in some range n, where start <= n <= end
|
||||
// and start/stop should be 32-bit integers with bit patterns that
|
||||
// form Float32 NaNs.
|
||||
function testFloat32NaNRanges(start, end) {
|
||||
let skipN = 10e3;
|
||||
|
||||
// sample the space of possible NaNs to save time
|
||||
let sampleSize = Math.floor((end - start)/ skipN);
|
||||
|
||||
let NaNArray = new Float32Array(getNaNArray(sampleSize));
|
||||
let buffer = new ArrayBuffer(4 * sampleSize);
|
||||
let uintView = new Uint32Array(buffer);
|
||||
let floatView = new Float32Array(buffer);
|
||||
|
||||
uintView[0] = start;
|
||||
for (let i = 1; i < sampleSize; i++) {
|
||||
uintView[i] = uintView[0] + (i * skipN);
|
||||
}
|
||||
|
||||
floatView.sort();
|
||||
assertDeepEq(floatView, NaNArray);
|
||||
}
|
||||
|
||||
// Test every skipNth value in some range n, where start <= n <= end
|
||||
// and startHi, startLow and endHi, endLow should be 32-bit integers which,
|
||||
// when combined (Hi + Low), form Float64 NaNs.
|
||||
function testFloat64NaNRanges(startHi, startLow, endHi, endLow) {
|
||||
let skipN = 10e6;
|
||||
|
||||
let sampleSizeHi = Math.floor((endHi - startHi)/skipN);
|
||||
let sampleSizeLow = Math.floor((endLow - startLow)/skipN);
|
||||
|
||||
let NaNArray = new Float64Array(getNaNArray(sampleSizeHi + sampleSizeLow));
|
||||
let buffer = new ArrayBuffer(8 * (sampleSizeHi + sampleSizeLow));
|
||||
let uintView = new Uint32Array(buffer);
|
||||
let floatView = new Float64Array(buffer);
|
||||
|
||||
// Fill in all of the low bits first.
|
||||
for (let i = 0; i <= sampleSizeLow; i++) {
|
||||
uintView[i * 2] = startLow + (i * skipN);
|
||||
uintView[(i * 2) + 1] = startHi;
|
||||
}
|
||||
|
||||
// Then the high bits.
|
||||
for (let i = sampleSizeLow; i <= sampleSizeLow + sampleSizeHi; i++) {
|
||||
uintView[i * 2] = endLow;
|
||||
uintView[(i * 2) + 1] = startHi + ((i - sampleSizeLow) * skipN);
|
||||
}
|
||||
|
||||
floatView.sort();
|
||||
assertDeepEq(floatView, NaNArray);
|
||||
}
|
||||
|
||||
// Float32 Signaling NaN ranges
|
||||
testFloat32NaNRanges(0x7F800001, 0x7FBFFFFF);
|
||||
testFloat32NaNRanges(0xFF800001, 0xFFBFFFFF);
|
||||
|
||||
// Float64 Signaling NaN ranges
|
||||
testFloat64NaNRanges(0x7FF00000, 0x00000001, 0x7FF7FFFF, 0xFFFFFFFF);
|
||||
testFloat64NaNRanges(0xFFF00000, 0x00000001, 0xFFF7FFFF, 0xFFFFFFFF);
|
||||
|
||||
if (typeof reportCompare === "function")
|
||||
reportCompare(true, true);
|
@ -865,6 +865,7 @@ TypedArrayObject::protoFunctions[] = {
|
||||
JS_SELF_HOSTED_FN("reverse", "TypedArrayReverse", 0, 0),
|
||||
JS_SELF_HOSTED_FN("slice", "TypedArraySlice", 2, 0),
|
||||
JS_SELF_HOSTED_FN("some", "TypedArraySome", 2, 0),
|
||||
JS_SELF_HOSTED_FN("sort", "TypedArraySort", 1, 0),
|
||||
JS_SELF_HOSTED_FN("entries", "TypedArrayEntries", 0, 0),
|
||||
JS_SELF_HOSTED_FN("keys", "TypedArrayKeys", 0, 0),
|
||||
// Both of these are actually defined to the same object in FinishTypedArrayInit.
|
||||
|
Loading…
Reference in New Issue
Block a user