Skip to content

Commit cab7a45

Browse files
committed
[JSC] Inline small sorting in DFG / FTL
https://bugs.webkit.org/show_bug.cgi?id=313668 rdar://175871778 Reviewed by Yijia Huang. This patch enables fully-inlining Array.prototype.sort function in DFG / FTL graph, including inlining a comparator call. 1. We fully inline Contiguous / Int32 / Undecided array sorting up to 16 elements. Right now, we do not support Double array. 2. The form is scratch = ArraySortCompact(array) ... sorting scratch ... ArraySortCommit(scratch, array) And we do sorting inline against scratch. ArraySortCompact filters hole array case etc., and they are going to the slow path call (which just uses normal sort). ArraySortCommit will apply sorted result back to array. 3. The most notable interesting part is OSR exit handling. In Array.prototype.sort, all OSR exit will be handled as "retry entire sorting again". This is fine because the spec does not define anything about how the comparator is called. This characteristics is leveraged in V8 too. 4. When comparator is inlined and OSR exit happens, then comparator must continue running its code. But after returning from the comparator, we are returning to array_sort_comparator_return_trampoline. Then it adjust the next PC and restart `op_call` of Array.prototype.sort again. Tests: JSTests/stress/array-sort-inline-closure-comparator.js JSTests/stress/array-sort-inline-constant-comparator.js JSTests/stress/array-sort-inline-contiguous.js JSTests/stress/array-sort-inline-full.js JSTests/stress/array-sort-inline-holey.js JSTests/stress/array-sort-inline-len2.js JSTests/stress/array-sort-inline-osr-exit-in-comparator.js JSTests/stress/array-sort-inline-small.js JSTests/stress/array-sort-inline-stack-trace.js * JSTests/stress/array-sort-inline-boolean-comparator.js: Added. (check): (sortIt): (expected): (i.const.cmp): (i.const.neg.valueOf): (i.const.pos.valueOf): (i.const.zero.valueOf): (i.catch): * JSTests/stress/array-sort-inline-closure-comparator.js: Added. (test): (descendingTest): * JSTests/stress/array-sort-inline-comparator-speculation-exit.js: Added. (cmp): (sortIt): (assertSorted): (throwingCmp): (catch): * JSTests/stress/array-sort-inline-constant-comparator.js: Added. (cmp): (test): * JSTests/stress/array-sort-inline-contiguous.js: Added. (sortIt): (return.a.sort): (throw.new.Error): * JSTests/stress/array-sort-inline-full.js: Added. (sortAsc): (runSize): (bigTest): (stableTest): (catch): * JSTests/stress/array-sort-inline-holey.js: Added. (cmp): (sortIt): (runHoley): * JSTests/stress/array-sort-inline-large.js: Added. (cmp): (cmpDesc): (doSort): (makeReversed): (makeRandomish): (assertSorted): (alternating): (catch): * JSTests/stress/array-sort-inline-len2.js: Added. (cmpAsc): (sortWith): (countingCmp): * JSTests/stress/array-sort-inline-mixed-sizes.js: Added. (cmp): (makeSmall): (makeLarge): (harness): (vm.useFTLJIT): (check): (large.sort): * JSTests/stress/array-sort-inline-noncallable-typeerror.js: Added. (expectThrow): (makeEmpty): (makeSingle): (sortIt): (i.expectThrow.sortIt.makeEmpty): (i.expectThrow.sortIt.makeSingle): * JSTests/stress/array-sort-inline-osr-exit-in-comparator-with-spread.js: Added. (cmp): (sortAndSpread): (check): * JSTests/stress/array-sort-inline-osr-exit-in-comparator.js: Added. (inlinedComparator): (test): (multiset): (sameMultiset): * JSTests/stress/array-sort-inline-polymorphic-comparator.js: Added. (cmpAsc): (cmpDesc): (cmpMod3Asc): (cmpByAbs): (doSort): (expectedFor): (w.try.doSort.seed.slice): (w.catch): (cmpThrow): (catch): * JSTests/stress/array-sort-inline-small.js: Added. (cmp): (callSort): * JSTests/stress/array-sort-inline-stack-trace.js: Added. (inlined): (test): (catch): * Source/JavaScriptCore/bytecode/BytecodeList.rb: * Source/JavaScriptCore/bytecode/InlineCallFrame.cpp: (WTF::printInternal): * Source/JavaScriptCore/bytecode/InlineCallFrame.h: (JSC::InlineCallFrame::callModeFor): (JSC::InlineCallFrame::specializationKindFor): * Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h: (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects): * Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp: (JSC::DFG::ByteCodeParser::emitExitOK): (JSC::DFG::ByteCodeParser::handleCallVariant): (JSC::DFG::ByteCodeParser::handleIntrinsicCall): (JSC::DFG::ByteCodeParser::handleArraySort): * Source/JavaScriptCore/dfg/DFGClobberize.h: (JSC::DFG::clobberize): * Source/JavaScriptCore/dfg/DFGDoesGC.cpp: (JSC::DFG::doesGC): * Source/JavaScriptCore/dfg/DFGFixupPhase.cpp: (JSC::DFG::FixupPhase::fixupNode): * Source/JavaScriptCore/dfg/DFGMayExit.cpp: * Source/JavaScriptCore/dfg/DFGNode.h: (JSC::DFG::Node::hasArrayMode): * Source/JavaScriptCore/dfg/DFGNodeType.h: * Source/JavaScriptCore/dfg/DFGOSRExitCompilerCommon.cpp: (JSC::DFG::callerReturnPC): * Source/JavaScriptCore/dfg/DFGOperations.cpp: (JSC::DFG::JSC_DEFINE_JIT_OPERATION): * Source/JavaScriptCore/dfg/DFGOperations.h: * Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp: * Source/JavaScriptCore/dfg/DFGSafeToExecute.h: (JSC::DFG::safeToExecute): * Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp: * Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h: * Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp: (JSC::DFG::SpeculativeJIT::compile): * Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp: (JSC::DFG::SpeculativeJIT::compile): * Source/JavaScriptCore/dfg/DFGStoreBarrierInsertionPhase.cpp: * Source/JavaScriptCore/ftl/FTLCapabilities.cpp: (JSC::FTL::canCompile): * Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp: (JSC::FTL::DFG::LowerDFGToB3::compileNode): (JSC::FTL::DFG::LowerDFGToB3::compileGetCellButterflySlot): (JSC::FTL::DFG::LowerDFGToB3::compilePutCellButterflySlot): (JSC::FTL::DFG::LowerDFGToB3::compileArraySortCompact): (JSC::FTL::DFG::LowerDFGToB3::compileArraySortCommit): * Source/JavaScriptCore/llint/LLIntSlowPaths.cpp: (JSC::LLInt::dispatchToCurrentInstructionDuringExit): (JSC::LLInt::llint_slow_path_array_sort_comparator_return): * Source/JavaScriptCore/llint/LLIntSlowPaths.h: * Source/JavaScriptCore/llint/LLIntThunks.cpp: (JSC::LLInt::arraySortComparatorReturnTrampolineThunk): * Source/JavaScriptCore/llint/LLIntThunks.h: * Source/JavaScriptCore/llint/LowLevelInterpreter.asm: * Source/JavaScriptCore/runtime/ArrayPrototype.cpp: (JSC::ArrayPrototype::finishCreation): * Source/JavaScriptCore/runtime/Intrinsic.h: * Source/JavaScriptCore/runtime/VM.cpp: (JSC::VM::VM): (JSC::VM::visitAggregateImpl): * Source/JavaScriptCore/runtime/VM.h: Canonical link: https://commits.webkit.org/312983@main
1 parent 4c86535 commit cab7a45

48 files changed

Lines changed: 2050 additions & 11 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// The DFG ArraySortIntrinsic's inline insertion sort must interpret the comparator
2+
// result the same way runtime/StableSort.h's coerceComparatorResultToBoolean does:
3+
//
4+
// - Int32: shift iff `< 0`.
5+
// - Boolean: shift iff `!result` (true->no shift, false->shift) -- webkit.org/b/47825.
6+
// - Other: shift iff `toNumber(result) < 0` (NaN coerces to 0 in V8 but in JSC's
7+
// path `NaN < 0` is false, matching StableSort.h's `toNumber() < 0` for NaN).
8+
//
9+
// Before the fix the intrinsic did only `cmp < 0`, which gave false for boolean
10+
// `false` (since 0 < 0 is false), so a `() => false` comparator was a no-op and
11+
// the legacy `(a, b) => a > b` pattern never sorted. The fix routes `cmp === false`
12+
// through an extra branch that also goes to the shift target.
13+
14+
function check(got, expected) {
15+
if (got.length !== expected.length)
16+
throw new Error("length mismatch: " + JSON.stringify(got) + " vs " + JSON.stringify(expected));
17+
for (let i = 0; i < expected.length; ++i) {
18+
if (!Object.is(got[i], expected[i]))
19+
throw new Error("index " + i + " mismatch: " + JSON.stringify(got) + " vs " + JSON.stringify(expected));
20+
}
21+
}
22+
23+
function sortIt(a, c) { return a.sort(c); }
24+
25+
// Warm up with a well-typed Int32 comparator so DFG inlines the intrinsic on the
26+
// hot doSort path.
27+
for (let i = 0; i < testLoopCount; ++i)
28+
sortIt([5, 3, 1, 4, 2], (a, b) => a - b);
29+
30+
// Expected results are computed via the generic baseline sort on a fresh array
31+
// (no DFG intrinsic cached state). Matches runtime/StableSort.h exactly.
32+
function expected(input, cmp) { return [...input].sort(cmp); }
33+
34+
// --- Case A: Int32 comparator (primary fast path). ----------------------------
35+
for (let i = 0; i < testLoopCount; ++i) {
36+
const input = [5, 3, 1, 4, 2];
37+
check(sortIt(input.slice(), (a, b) => a - b), [1, 2, 3, 4, 5]);
38+
check(sortIt(input.slice(), (a, b) => b - a), [5, 4, 3, 2, 1]);
39+
}
40+
41+
// Int32 boundary: zero / MIN_VALUE / MAX_VALUE / negative / positive.
42+
for (let i = 0; i < testLoopCount; ++i) {
43+
const input = [5, 3, 1, 4, 2];
44+
const cmp = (a, b) => {
45+
const d = a - b;
46+
if (d === 0) return 0;
47+
return d < 0 ? -2147483648 : 2147483647; // extreme Int32s
48+
};
49+
check(sortIt(input.slice(), cmp), expected(input, cmp));
50+
}
51+
52+
// --- Case B: Boolean comparator (legacy b/47825 path). -----------------------
53+
// Constant-false: every comparison is "shift", insertion sort pushes each pivot
54+
// to the front.
55+
for (let i = 0; i < testLoopCount; ++i) {
56+
const input = [5, 3, 1, 4, 2];
57+
check(sortIt(input.slice(), () => false), expected(input, () => false));
58+
}
59+
60+
// Constant-true: every comparison is "no shift", array stays as-is.
61+
for (let i = 0; i < testLoopCount; ++i) {
62+
const input = [5, 3, 1, 4, 2];
63+
check(sortIt(input.slice(), () => true), expected(input, () => true));
64+
}
65+
66+
// The real-world legacy pattern: comparator returns a boolean. Both these must
67+
// end up ascending / descending respectively, matching baseline.
68+
for (let i = 0; i < testLoopCount; ++i) {
69+
const input = [5, 3, 1, 4, 2];
70+
check(sortIt(input.slice(), (a, b) => a > b), [1, 2, 3, 4, 5]);
71+
check(sortIt(input.slice(), (a, b) => a < b), [5, 4, 3, 2, 1]);
72+
}
73+
74+
// Mixed boolean / int return in a single comparator -- exercises both the
75+
// CompareLess branch (shift when < 0) and the CompareStrictEq branch (shift when
76+
// boolean false) in the same sort.
77+
for (let i = 0; i < testLoopCount; ++i) {
78+
const input = [7, 2, 5, 3, 8, 1, 6, 4, 0, 9];
79+
const cmp = (a, b) => {
80+
if ((a ^ b) & 1) return a > b; // boolean
81+
return a - b; // int32
82+
};
83+
check(sortIt(input.slice(), cmp), expected(input, cmp));
84+
}
85+
86+
// --- Case C: Double comparator. --------------------------------------------
87+
for (let i = 0; i < testLoopCount; ++i) {
88+
const input = [5, 3, 1, 4, 2];
89+
const cmp = (a, b) => (a - b) + 0.5 - 0.5; // Double result.
90+
check(sortIt(input.slice(), cmp), expected(input, cmp));
91+
}
92+
93+
// Double edge cases: -0, +0, NaN, Infinity.
94+
for (let i = 0; i < testLoopCount; ++i) {
95+
const input = [5, 3, 1, 4, 2];
96+
const table = new Map([
97+
['lt', -Infinity],
98+
['gt', Infinity],
99+
['eq', 0],
100+
['negZero', -0], // -0 < 0 is false per IEEE 754
101+
['nan', NaN], // NaN < 0 is false
102+
]);
103+
for (const [, v] of table) {
104+
const cmp = (a, b) => a === b ? 0 : (a < b ? v : -v);
105+
check(sortIt(input.slice(), cmp), expected(input, cmp));
106+
}
107+
}
108+
109+
// --- Case D: String comparator. Falls back to toNumber() < 0 path. -----------
110+
for (let i = 0; i < testLoopCount; ++i) {
111+
const input = [5, 3, 1, 4, 2];
112+
const cmp = (a, b) => a === b ? "0" : (a < b ? "-1" : "1");
113+
check(sortIt(input.slice(), cmp), expected(input, cmp));
114+
}
115+
116+
// String coerces to NaN → NaN < 0 → false → no shift.
117+
for (let i = 0; i < testLoopCount; ++i) {
118+
const input = [5, 3, 1, 4, 2];
119+
check(sortIt(input.slice(), () => "abc"), expected(input, () => "abc"));
120+
}
121+
122+
// --- Case E: null / undefined. ------------------------------------------------
123+
// toNumber(null) = 0 → 0 < 0 = false → no shift.
124+
for (let i = 0; i < testLoopCount; ++i) {
125+
const input = [5, 3, 1, 4, 2];
126+
check(sortIt(input.slice(), () => null), expected(input, () => null));
127+
check(sortIt(input.slice(), () => undefined), expected(input, () => undefined));
128+
}
129+
130+
// --- Case F: Object with valueOf. toPrimitive then toNumber. ------------------
131+
for (let i = 0; i < testLoopCount; ++i) {
132+
const input = [5, 3, 1, 4, 2];
133+
const neg = { valueOf() { return -1; } };
134+
const pos = { valueOf() { return 1; } };
135+
const zero = { valueOf() { return 0; } };
136+
// Return objects -- each sort iteration triggers valueOf.
137+
check(sortIt(input.slice(), (a, b) => a < b ? neg : a > b ? pos : zero), [1, 2, 3, 4, 5]);
138+
check(sortIt(input.slice(), () => neg), expected(input, () => neg));
139+
}
140+
141+
// --- Case G: BigInt comparator. toNumber throws TypeError. -------------------
142+
// Both baseline and intrinsic must throw.
143+
for (let i = 0; i < testLoopCount; ++i) {
144+
let threw = false;
145+
try { sortIt([5, 3, 1], () => -1n); }
146+
catch (e) { threw = e instanceof TypeError; }
147+
if (!threw) throw new Error("BigInt comparator must throw TypeError");
148+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Verifies the DFG ArraySortIntrinsic's comparator body-inlining path for a
2+
// NewArrowFunction / NewFunction closure comparator allocated fresh on every
3+
// sort call. This is the `.sort((a,b) => a - b)` idiom that every real-world
4+
// sort caller uses; it's the path the parser-level inliner must detect via
5+
// Node::isFunctionAllocation().
6+
7+
function test(arr) {
8+
arr.sort((a, b) => a - b);
9+
}
10+
11+
const original = [5, 3, 8, 1, 9, 4, 7, 2, 6, 0, 11, 14, 13, 10, 12, 15];
12+
const expected = original.slice().sort((a, b) => a - b);
13+
14+
for (let i = 0; i < testLoopCount; i++) {
15+
const copy = original.slice();
16+
test(copy);
17+
for (let j = 0; j < expected.length; j++) {
18+
if (copy[j] !== expected[j])
19+
throw new Error("iter " + i + " index " + j
20+
+ ": got " + copy[j] + " expected " + expected[j]);
21+
}
22+
}
23+
24+
// Descending comparator -- separate closure shape, also re-allocated per call.
25+
function descendingTest(arr) {
26+
arr.sort((a, b) => b - a);
27+
}
28+
29+
const expectedDesc = original.slice().sort((a, b) => b - a);
30+
for (let i = 0; i < testLoopCount; i++) {
31+
const copy = original.slice();
32+
descendingTest(copy);
33+
for (let j = 0; j < expectedDesc.length; j++) {
34+
if (copy[j] !== expectedDesc[j])
35+
throw new Error("desc iter " + i + " index " + j
36+
+ ": got " + copy[j] + " expected " + expectedDesc[j]);
37+
}
38+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Verifies that a naturally-occurring OSR exit inside the DFG-inlined comparator
2+
// (via Int32 speculation failure on ValueSub) recovers correctly and the sort
3+
// call completes with the right result. The existing
4+
// array-sort-inline-osr-exit-in-comparator.js uses `$vm.OSRExit()` to force an
5+
// exit synthetically; this test exercises the same code path that a real-world
6+
// caller would hit: warm up with Int32 inputs so DFG speculates Int32, then
7+
// feed doubles and watch the speculation fail mid-sort.
8+
//
9+
// The exit lands inside the comparator's inlined body. Per
10+
// InlineCallFrame::Kind::ArraySortComparatorCall, the comparator's baseline
11+
// resumes, completes, and returns via arraySortComparatorReturnTrampoline --
12+
// which discards the return value and re-dispatches the caller's op_call.
13+
// Baseline then calls sort generically with the correctly-preserved arguments.
14+
// The observable result must still be a correctly sorted array.
15+
16+
function cmp(a, b) { return a - b; }
17+
18+
function sortIt(a, c) { return a.sort(c); }
19+
20+
function assertSorted(a, reference, label) {
21+
if (a.length !== reference.length)
22+
throw new Error(label + ": length " + a.length + " vs " + reference.length);
23+
for (let i = 0; i < reference.length; ++i) {
24+
if (a[i] !== reference[i])
25+
throw new Error(label + ": @" + i + " got " + a[i] + " want " + reference[i]);
26+
}
27+
}
28+
29+
// --- Phase 1: warm up with Int32 arrays so DFG inlines sort + inlines the
30+
// comparator. DFG/FTL will profile `cmp`'s ValueSub as Int32Use.
31+
const int32Seed = [5, 3, 1, 4, 2, 8, 7, 6, 0, 9, 11, 14, 13, 10, 12, 15];
32+
const int32Expected = int32Seed.slice().sort(cmp);
33+
for (let w = 0; w < testLoopCount; ++w)
34+
assertSorted(sortIt(int32Seed.slice(), cmp), int32Expected, "warmup int32 " + w);
35+
36+
// --- Phase 2: now feed doubles. The comparator's Int32-speculated ValueSub
37+
// sees double operands and OSR-exits inside the comparator body. Baseline
38+
// finishes the comparator and jumps through the trampoline back to op_call,
39+
// which re-runs sort generically. Each iteration must still produce a
40+
// correctly sorted array.
41+
const doubleSeed = [5.5, 3.25, 1.75, 4.5, 2.125, 8.8, 7.1, 6.6, 0.5, 9.9, 11.1, 14.4, 13.3, 10.0, 12.2, 15.5];
42+
const doubleExpected = doubleSeed.slice().sort(cmp);
43+
for (let w = 0; w < testLoopCount; ++w)
44+
assertSorted(sortIt(doubleSeed.slice(), cmp), doubleExpected, "double " + w);
45+
46+
// --- Phase 3: alternate back to Int32. The call site has tiered back up by
47+
// now; must still sort correctly.
48+
for (let w = 0; w < testLoopCount; ++w)
49+
assertSorted(sortIt(int32Seed.slice(), cmp), int32Expected, "back-to-int " + w);
50+
51+
// --- Phase 4: mix Int32 and Double in the same array. Each iteration may
52+
// exit mid-sort as the comparator hits a double.
53+
const mixedSeed = [5, 3.25, 1, 4.5, 2, 8, 7.1, 0, 6.6, 9];
54+
const mixedExpected = mixedSeed.slice().sort(cmp);
55+
for (let w = 0; w < testLoopCount; ++w)
56+
assertSorted(sortIt(mixedSeed.slice(), cmp), mixedExpected, "mixed " + w);
57+
58+
// --- Phase 5: arrays of varying size around the 16-element boundary, each
59+
// with doubles, so the exit fires on both the insertion-sort fast path and
60+
// (for len > 16) the generic slow path. The array length 1 case is boring
61+
// (no comparator call) so start at 2.
62+
for (let len = 2; len <= 20; ++len) {
63+
const seed = [];
64+
for (let i = 0; i < len; ++i) seed.push((len - i) + 0.5);
65+
const expected = seed.slice().sort(cmp);
66+
for (let w = 0; w < 100; ++w)
67+
assertSorted(sortIt(seed.slice(), cmp), expected, "len " + len + " w " + w);
68+
}
69+
70+
// --- Phase 6: comparator that throws inside the inlined body. If OSR exit
71+
// handling is wrong, the exception might propagate from the wrong frame or
72+
// the sort call may appear to return a value (and spread would then crash,
73+
// which is the WebGPU bug we started from). Here we just assert the thrown
74+
// error is observed intact at the caller.
75+
let shouldThrow = false;
76+
function throwingCmp(a, b) {
77+
if (shouldThrow)
78+
throw new Error("boom");
79+
return a - b;
80+
}
81+
// Warm up so the comparator is inlined.
82+
for (let w = 0; w < testLoopCount; ++w) sortIt([3, 1, 2], throwingCmp);
83+
84+
shouldThrow = true;
85+
let threw = false;
86+
try { sortIt([5, 3, 1, 4, 2], throwingCmp); }
87+
catch (e) { threw = e.message === "boom"; }
88+
if (!threw)
89+
throw new Error("throwing comparator did not propagate through the sort");
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Verifies the DFG ArraySortIntrinsic's comparator body-inlining path when
2+
// the comparator is a constant JSFunction (named global / hoisted) rather
3+
// than a closure. The parser detects this via Node::hasConstant() +
4+
// dynamicCastConstant<JSFunction*>() and routes through handleCall with
5+
// CallLinkStatus(CallVariant(function)).
6+
7+
function cmp(a, b) {
8+
return a - b;
9+
}
10+
11+
function test(arr) {
12+
arr.sort(cmp);
13+
}
14+
15+
const original = [5, 3, 8, 1, 9, 4, 7, 2, 6, 0, 11, 14, 13, 10, 12, 15];
16+
const expected = original.slice().sort(cmp);
17+
18+
for (let i = 0; i < testLoopCount; i++) {
19+
const copy = original.slice();
20+
test(copy);
21+
for (let j = 0; j < expected.length; j++) {
22+
if (copy[j] !== expected[j])
23+
throw new Error("iter " + i + " index " + j
24+
+ ": got " + copy[j] + " expected " + expected[j]);
25+
}
26+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Verifies the DFG ArraySortIntrinsic's fast path handles ArrayWithContiguous arrays -- arrays
2+
// whose elements include non-Int32 values (doubles, strings, objects).
3+
4+
function sortIt(a, cmp) { return a.sort(cmp); }
5+
6+
// Contiguous (mixed-type) arrays: objects.
7+
{
8+
const input = [{v:5},{v:3},{v:1},{v:4},{v:2}];
9+
const expected = [1,2,3,4,5];
10+
for (let w = 0; w < testLoopCount; w++) {
11+
const a = input.slice();
12+
const r = sortIt(a, (x, y) => x.v - y.v);
13+
for (let i = 0; i < 5; i++)
14+
if (r[i].v !== expected[i])
15+
throw new Error("iter " + w + " index " + i + ": got " + r[i].v + " expected " + expected[i]);
16+
}
17+
}
18+
19+
// Contiguous (strings).
20+
{
21+
const input = ["dd", "bb", "aa", "cc"];
22+
const expected = ["aa", "bb", "cc", "dd"];
23+
for (let w = 0; w < testLoopCount; w++) {
24+
const a = input.slice();
25+
const r = sortIt(a, (x, y) => x < y ? -1 : x > y ? 1 : 0);
26+
for (let i = 0; i < 4; i++)
27+
if (r[i] !== expected[i])
28+
throw new Error("iter " + w + " index " + i + ": got " + r[i] + " expected " + expected[i]);
29+
}
30+
}
31+
32+
// Contiguous (doubles stored via contiguous indexing since allocated from literal with ints then a double).
33+
{
34+
const input = [3.5, 1.2, 2.8, 0.4];
35+
const expected = [0.4, 1.2, 2.8, 3.5];
36+
for (let w = 0; w < testLoopCount; w++) {
37+
const a = input.slice();
38+
const r = sortIt(a, (x, y) => x - y);
39+
for (let i = 0; i < 4; i++)
40+
if (r[i] !== expected[i])
41+
throw new Error("iter " + w + " index " + i + ": got " + r[i] + " expected " + expected[i]);
42+
}
43+
}

0 commit comments

Comments
 (0)