GNU Compatibility: Implement qsort_r

This is a version of the qsort function allowing you to pass a "cookie" to the function.

Change-Id: I60c645213b9c9590e38b112634fcac1d7969b6d9
Reviewed-on: https://review.haiku-os.org/c/haiku/+/2449
Reviewed-by: Adrien Destugues <pulkomandy@gmail.com>
This commit is contained in:
CodeforEvolution 2020-04-01 19:22:39 -05:00 committed by Adrien Destugues
parent 1c889d23fe
commit 5c32512076
3 changed files with 247 additions and 0 deletions

View File

@ -0,0 +1,36 @@
/*
* 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
*/
#ifndef _GNU_STDLIB_H_
#define _GNU_STDLIB_H_
#include_next <stdlib.h>
#ifdef _GNU_SOURCE
#ifdef __cplusplus
extern "C" {
#endif
typedef int (*_compare_function_qsort_r)(const void*, const void*, void*);
extern void qsort_r(void* base, size_t numElements, size_t sizeOfElement,
_compare_function_qsort_r, void* cookie);
#ifdef __cplusplus
}
#endif
#endif /* _GNU_SOURCE */
#endif /* _GNU_STDLIB_H_ */

View File

@ -11,6 +11,7 @@ for architectureObject in [ MultiArchSubDirSetup ] {
on $(architectureObject) {
SharedLibrary [ MultiArchDefaultGristFiles libgnu.so ] :
memmem.c
qsort.c
xattr.cpp
;
}

210
src/libs/gnu/qsort.c Normal file
View File

@ -0,0 +1,210 @@
/*
* 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))
/* swap itemA and itemB */
/* itemA and itemB must not be equal! */
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);
}
/* swap itemA and itemB if itemA > itemB */
/* itemA and itemB must not be equal! */
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);
}
}
/* Note: quicksort is not stable, equivalent values may be swapped */
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) {
// Insertion sort for arbitrarily small inputs
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 {
// Quicksort when numElements >= 10
int cmp;
char* nextPivCmpItem; // pl
char* nextPivEqualsPos; // ple
char* lastPivCmpItem; // pr
char* lastPivEqualsPos; // pre
char* pivot;
char* last = base + sizeOfElement * (numElements - 1);
char* tmp;
// Use median of second, middle and second-last items as pivot.
// First and last may have been swapped with pivot and therefore be
// extreme.
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);
}
// Swap mid value (pivList[1]), and last element to put pivot as last
// element.
if (pivList[1] != last)
sort_r_swap(pivList[1], last, sizeOfElement);
// v- end (beyond the array)
// EEEEEELLLLLLLLuuuuuuuuGGGGGGGEEEEEEEE.
// ^- base ^- ple ^- pl ^- pr ^- pre ^- last (where the pivot is)
// Pivot comparison key:
// E = equal, L = less than, u = unknown, G = greater than, E = equal
pivot = last;
nextPivEqualsPos = nextPivCmpItem = base;
lastPivEqualsPos = lastPivCmpItem = last;
// Strategy:
// Loop into the list from the left and right at the same time to find:
// - an item on the left that is greater than the pivot
// - an item on the right that is less than the pivot
// Once found, they are swapped and the loop continues.
// Meanwhile items that are equal to the pivot are moved to the edges
// of the array.
while (nextPivCmpItem < lastPivCmpItem) {
// Move left hand items which are equal to the pivot to the far
// left. Break when we find an item that is greater than the pivot.
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;
}
}
// break if last batch of left hand items were equal to pivot
if (nextPivCmpItem >= lastPivCmpItem)
break;
// Move right hand items which are equal to the pivot to the far
// right. Break when we find an item that is less than the pivot.
for (; nextPivCmpItem < lastPivCmpItem;) {
lastPivCmpItem -= sizeOfElement;
// Move right pointer onto an unprocessed item
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;
// lastPivCmpItem may have gone below nextPivCmpItem
// Now we need to go from: EEELLLGGGGEEEE
// to: LLLEEEEEEEGGGG
// Pivot comparison key:
// E = equal, L = less than, u = unknown, G = greater than, E = equal
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);
}