← All issues

JSC PowerSort Galloping Merge

cd9b986

Source/JavaScriptCore/runtime/StableSort.h

-template<typename T, typename Compare>
-static void extendRunRight(T* array, size_t& right, size_t last, const Compare& compare)
+template<typename T, typename Compare>
+static void extendAndNormalizeRun(T* array, size_t& right, size_t last, const Compare& compare)
{
- // Only extends ascending runs
+ // Detect descending run and reverse in place to normalize
+ bool descending = ...
+ if (descending)
+ std::reverse(array + start, array + right + 1);
}
 
+template<typename T, typename Compare>
+static size_t gallopLeft(T* array, size_t base, size_t len, const T& key, const Compare& compare)
+{
+ // Exponential probe forward, then binary search to find insertion point
+ size_t lastOfs = 0, ofs = 1;
+ while (ofs < len && compare(array[base + ofs], key))
+ lastOfs = ofs, ofs <<= 1;
+ ofs = std::min(ofs, len);
+ // binary search in [lastOfs, ofs]
+ ...
+}
 
+template<typename T, typename Compare>
+static size_t gallopRight(T* array, size_t base, size_t len, const T& key, const Compare& compare)
+{
+ // Mirror of gallopLeft — probe for elements that should follow key
+ ...
+}

PowerSort is JSC's implementation of Array.prototype.sort (and TypedArray sort) — a natural merge sort variant that identifies pre-sorted "runs" in the input and merges them in an order determined by a merge tree. This commit introduces three optimizations: it eliminates the initial unnecessary buffer copy (src is now always the output, dst is only a working scratch), extends run detection to recognize and normalize descending runs via in-place reversal, and introduces galloping merge borrowed from Python's TimSort.

Galloping changes the inner merge loop: when one run dominates repeatedly (many consecutive elements all less-than the other run's current element), instead of comparing one-by-one, it does an exponential probe (1, 2, 4, 8… positions forward) to find a boundary, then bulk-copies the entire dominated region. Both the regular Array path (ArrayPrototype.cpp) and the TypedArray path (JSGenericTypedArrayViewPrototypeFunctions.h) are updated.

Standard merge inner loop:       Galloping merge inner loop:
  compare A[i] vs B[j]              gallopRight: probe B at 1,2,4,8… positions
  copy winner, advance one           find k where B[k] >= A[i]
  compare A[i] vs B[j+1]            bulk-copy B[j..j+k] to output
  ...O(n) comparisons               resume element-by-element

Yields a ~1.34x speedup on the magic-forest benchmark, and galloping introduces new bulk-copy code paths that interact with user-controlled comparator functions — a historically fertile area for type confusion and out-of-bounds bugs in JS engines.

🔒

New bulk-copy paths with user-controlled callbacks and changed buffer ownership — several edge cases in this fast path are worth security investigation.

Subscribe to read more