sortByRank = (passables, compare) => {
  /** @type {T[]} mutable for in-place sorting, but with hardened elements */
  let unsorted;
  if (Array.isArray(passables)) {
    harden(passables);
    // Calling isRankSorted gives it a chance to get memoized for
    // this `compare` function even if it was already memoized for a different
    // `compare` function.
    if (isRankSorted(passables, compare)) {
      return passables;
    }
    unsorted = [...passables];
  } else {
    unsorted = Array.from(passables, harden);
  }
  const sorted = unsorted.sort(compare);
  // For reverse comparison, move `undefined` values from the end to the start.
  // Note that passStylePrefixes (@see {@link ./encodePassable.js}) MUST NOT
  // sort any category after `undefined`.
  if (compare(true, undefined) > 0) {
    let i = sorted.length - 1;
    while (i >= 0 && sorted[i] === undefined) i -= 1;
    const n = sorted.length - i - 1;
    if (n > 0 && n < sorted.length) {
      sorted.copyWithin(n, 0);
      sorted.fill(/** @type {T} */ (undefined), 0, n);
    }
  }
  harden(sorted);
  const subMemoOfSorted = memoOfSorted.get(compare);
  assert(subMemoOfSorted !== undefined);
  subMemoOfSorted.add(sorted);
  return sorted;
}
@template {Passable} T
@param {Iterable<T>} passables
@param {RankCompare} compare
@returns {T[]}