From 5c32512076137735acce9fb41430d6209528dfac Mon Sep 17 00:00:00 2001 From: CodeforEvolution Date: Wed, 1 Apr 2020 19:22:39 -0500 Subject: [PATCH] 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 --- headers/compatibility/gnu/stdlib.h | 36 +++++ src/libs/gnu/Jamfile | 1 + src/libs/gnu/qsort.c | 210 +++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 headers/compatibility/gnu/stdlib.h create mode 100644 src/libs/gnu/qsort.c diff --git a/headers/compatibility/gnu/stdlib.h b/headers/compatibility/gnu/stdlib.h new file mode 100644 index 0000000000..c02073d933 --- /dev/null +++ b/headers/compatibility/gnu/stdlib.h @@ -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 + + +#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_ */ diff --git a/src/libs/gnu/Jamfile b/src/libs/gnu/Jamfile index e958b05ad4..0a2544a82f 100644 --- a/src/libs/gnu/Jamfile +++ b/src/libs/gnu/Jamfile @@ -11,6 +11,7 @@ for architectureObject in [ MultiArchSubDirSetup ] { on $(architectureObject) { SharedLibrary [ MultiArchDefaultGristFiles libgnu.so ] : memmem.c + qsort.c xattr.cpp ; } diff --git a/src/libs/gnu/qsort.c b/src/libs/gnu/qsort.c new file mode 100644 index 0000000000..154008cacf --- /dev/null +++ b/src/libs/gnu/qsort.c @@ -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 +#include + + +#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); +}