// qsort + bsearch — own translation unit so the indirect cmp call
// and the byte-swap inner loop don't perturb other libc code.
//
// qsort uses insertion sort (O(n^2)) rather than recursion-driven
// quicksort.  Originally chosen because the W65816 greedy regalloc
// mis-ordered spills in iterative quicksort (#70 — since fixed by a
// W65816StackSlotCleanup safety check), but kept because the typical
// IIgs C program sorts dozens of items, not thousands, and the
// constant-factor win of insertion sort dominates at that scale.

typedef unsigned long size_t;
typedef int (*CmpFnT)(const void *, const void *);


// Swap `size` bytes between a and b using a 1-byte scratch.  Tight
// forward loop is what the backend handles best on this target.
static void byteSwap(unsigned char *a, unsigned char *b, size_t size) {
    for (size_t i = 0; i < size; i++) {
        unsigned char t = a[i];
        a[i] = b[i];
        b[i] = t;
    }
}


// Iterative binary search.  Previously had `optnone` to dodge a
// regalloc OOM under ptr32 + multiple simultaneously-live Wide32
// vregs.  The IMG-clobber + LDAfi-IMG-store backend fixes (2026-05-07)
// resolved the underlying regalloc pressure issue and bsearch now
// compiles cleanly at -O2.
void *bsearch(const void *key, const void *base, size_t nmemb,
              size_t size, CmpFnT cmp) {
    const unsigned char *baseP = (const unsigned char *)base;
    size_t lo = 0;
    size_t hi = nmemb;
    while (lo < hi) {
        size_t mid = lo + (hi - lo) / 2;
        const unsigned char *p = baseP + mid * size;
        int r = cmp(key, p);
        if (r == 0) {
            return (void *)p;
        }
        if (r < 0) {
            hi = mid;
        } else {
            lo = mid + 1;
        }
    }
    return (void *)0;
}


// Inner loop of qsort's insertion sort: starting from `cur` (the
// element at index i) walk left through `[base, cur)`, swapping
// adjacent pairs while out-of-order.  Hoisted out of qsort so the
// outer loop's frame stays small enough for basic regalloc; without
// the split, qsort's i32-pointer pressure under ptr32 produces
// ADCEfi tied-def chains the inline-spiller can't allocate ("ran
// out of registers" failure).
static void qsortInner(unsigned char *base, unsigned char *cur,
                       size_t size, CmpFnT cmp) {
    while (cur > base) {
        unsigned char *prev = cur - size;
        if (cmp(prev, cur) <= 0) {
            return;
        }
        byteSwap(prev, cur, size);
        cur = prev;
    }
}


void qsort(void *base, size_t nmemb, size_t size, CmpFnT cmp) {
    if (nmemb < 2 || size == 0) {
        return;
    }
    unsigned char *baseP = (unsigned char *)base;
    // Insertion sort: for each i in 1..n-1, hand the element at i to
    // the inner-loop helper which walks left swapping out-of-order
    // pairs.  Stable, in-place.
    for (size_t i = 1; i < nmemb; i++) {
        qsortInner(baseP, baseP + i * size, size, cmp);
    }
}
