* Copyright 2020 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Isaac Turner, turner.isaac@gmail.com
* Jacob Secunda, secundaja@gmail.com
*/
#include <stdlib.h>
#include <string.h>
#define SORT_R_SWAP(a, b, tmp) ((tmp) = (a), (a) = (b), (b) = (tmp))
static inline void
sort_r_swap(char* __restrict itemA, char* __restrict itemB,
size_t sizeOfElement)
{
char tmp;
char* end = itemA + sizeOfElement;
for (; itemA < end; itemA++, itemB++)
SORT_R_SWAP(* itemA, * itemB, tmp);
}
static inline int
sort_r_cmpswap(char* __restrict itemA, char* __restrict itemB,
size_t sizeOfElement, _compare_function_qsort_r cmpFunc, void* cookie)
{
if (cmpFunc(itemA, itemB, cookie) > 0) {
sort_r_swap(itemA, itemB, sizeOfElement);
return 1;
}
return 0;
}
Swap consecutive blocks of bytes of size na and nb starting at memory addr
ptr, with the smallest swap so that the blocks are in the opposite order.
Blocks may be internally re-ordered e.g.
12345ab -> ab34512
123abc -> abc123
12abcde -> deabc12
*/
static inline void
sort_r_swap_blocks(char* ptr, size_t numBytesA, size_t numBytesB)
{
if (numBytesA > 0 && numBytesB > 0) {
if (numBytesA > numBytesB)
sort_r_swap(ptr, ptr + numBytesA, numBytesB);
else
sort_r_swap(ptr, ptr + numBytesB, numBytesA);
}
}
static inline void
sort_r_simple(char* base, size_t numElements, size_t sizeOfElement,
_compare_function_qsort_r cmpFunc, void* cookie)
{
char* end = base + (numElements * sizeOfElement);
if (numElements < 10) {
char* pivIndexA;
char* pivIndexB;
for (pivIndexA = base + sizeOfElement; pivIndexA < end;
pivIndexA += sizeOfElement) {
pivIndexB = pivIndexA;
while (pivIndexB > base
&& sort_r_cmpswap(pivIndexB - sizeOfElement , pivIndexB,
sizeOfElement, cmpFunc, cookie)) {
pivIndexB -= sizeOfElement;
}
}
} else {
int cmp;
char* nextPivCmpItem;
char* nextPivEqualsPos;
char* lastPivCmpItem;
char* lastPivEqualsPos;
char* pivot;
char* last = base + sizeOfElement * (numElements - 1);
char* tmp;
char* pivList[3];
pivList[0] = base + sizeOfElement;
pivList[1] = base + sizeOfElement * (numElements / 2);
pivList[2] = last - sizeOfElement;
if (cmpFunc(pivList[0], pivList[1], cookie) > 0)
SORT_R_SWAP(pivList[0], pivList[1], tmp);
if (cmpFunc(pivList[1], pivList[2], cookie) > 0) {
SORT_R_SWAP(pivList[1], pivList[2], tmp);
if (cmpFunc(pivList[0], pivList[1], cookie) > 0)
SORT_R_SWAP(pivList[0], pivList[1], tmp);
}
if (pivList[1] != last)
sort_r_swap(pivList[1], last, sizeOfElement);
pivot = last;
nextPivEqualsPos = nextPivCmpItem = base;
lastPivEqualsPos = lastPivCmpItem = last;
while (nextPivCmpItem < lastPivCmpItem) {
for (; nextPivCmpItem < lastPivCmpItem;
nextPivCmpItem += sizeOfElement) {
cmp = cmpFunc(nextPivCmpItem, pivot, cookie);
if (cmp > 0)
break;
else if (cmp == 0) {
if (nextPivEqualsPos < nextPivCmpItem) {
sort_r_swap(nextPivEqualsPos, nextPivCmpItem,
sizeOfElement);
}
nextPivEqualsPos += sizeOfElement;
}
}
if (nextPivCmpItem >= lastPivCmpItem)
break;
for (; nextPivCmpItem < lastPivCmpItem;) {
lastPivCmpItem -= sizeOfElement;
cmp = cmpFunc(lastPivCmpItem, pivot, cookie);
if (cmp == 0) {
lastPivEqualsPos -= sizeOfElement;
if (lastPivCmpItem < lastPivEqualsPos) {
sort_r_swap(lastPivCmpItem, lastPivEqualsPos,
sizeOfElement);
}
} else if (cmp < 0) {
if (nextPivCmpItem < lastPivCmpItem) {
sort_r_swap(nextPivCmpItem, lastPivCmpItem,
sizeOfElement);
}
nextPivCmpItem += sizeOfElement;
break;
}
}
}
nextPivCmpItem = lastPivCmpItem;
sort_r_swap_blocks(base, nextPivEqualsPos - base,
nextPivCmpItem - nextPivEqualsPos);
sort_r_swap_blocks(lastPivCmpItem, lastPivEqualsPos - lastPivCmpItem,
end - lastPivEqualsPos);
sort_r_simple(base, (nextPivCmpItem - nextPivEqualsPos) / sizeOfElement,
sizeOfElement, cmpFunc, cookie);
sort_r_simple(end - (lastPivEqualsPos - lastPivCmpItem),
(lastPivEqualsPos - lastPivCmpItem) / sizeOfElement, sizeOfElement,
cmpFunc, cookie);
}
}
inline void
qsort_r(void* base, size_t numElements, size_t sizeOfElement,
_compare_function_qsort_r cmpFunc, void* cookie)
{
sort_r_simple((char*)base, numElements, sizeOfElement,
cmpFunc, cookie);
}