From 551c82b4f02a713608ef948aa88e50953b93f4d8 Mon Sep 17 00:00:00 2001 From: Andrei Alexeyev Date: Sun, 16 Jun 2024 21:50:48 +0200 Subject: [PATCH] util: add portable sort_r() --- meson.build | 68 ++++++++++ src/util/meson.build | 1 + src/util/sort_r.c | 306 +++++++++++++++++++++++++++++++++++++++++++ src/util/sort_r.h | 22 ++++ 4 files changed, 397 insertions(+) create mode 100644 src/util/sort_r.c create mode 100644 src/util/sort_r.h diff --git a/meson.build b/meson.build index 0028df4f..88d4d532 100644 --- a/meson.build +++ b/meson.build @@ -365,6 +365,74 @@ config.set('TAISEI_BUILDCONF_HAVE_STATIC_ASSERT_WITHOUT_MSG', cc.compiles( args : ['-Werror'] )) +config.set('TAISEI_BUILDCONF_HAVE_GNU_QSORT_R', cc.links( + ''' + #define _GNU_SOURCE + #include + #include + int cmp(const void *a, const void *b, void *ctx) { + (*(int*)ctx)++; + return *(int*)a - *(int*)b; + } + int sort_test(size_t size, int array[size]) { + int i = 0; + qsort_r(array, size, sizeof(*array), cmp, &i); + return i; + } + int main(int argc, char **argv) { + volatile int a[] = { 1, 3, 3, 7, 9, 0, 0, 1 }; + return sort_test(sizeof(a)/sizeof(*a), (int*)a); + } + ''', + name : 'GNU qsort_r()', + args : ['-Wall', '-Werror'] +)) + +config.set('TAISEI_BUILDCONF_HAVE_BSD_QSORT_R', cc.links( + ''' + #define _DEFAULT_SOURCE + #include + #include + int cmp(void *ctx, const void *a, const void *b) { + (*(int*)ctx)++; + return *(int*)a - *(int*)b; + } + int sort_test(size_t size, int array[size]) { + int i = 0; + qsort_r(array, size, sizeof(*array), &i, cmp); + return i; + } + int main(int argc, char **argv) { + volatile int a[] = { 1, 3, 3, 7, 9, 0, 0, 1 }; + return sort_test(sizeof(a)/sizeof(*a), (int*)a); + } + ''', + name : 'BSD qsort_r()', + args : ['-Wall', '-Werror'] +)) + +config.set('TAISEI_BUILDCONF_HAVE_MICROSOFT_QSORT_S', cc.links( + ''' + #include + #include + int cmp(void *ctx, const void *a, const void *b) { + (*(int*)ctx)++; + return *(int*)a - *(int*)b; + } + int sort_test(size_t size, int array[size]) { + int i = 0; + qsort_s(array, size, sizeof(*array), cmp, &i); + return i; + } + int main(int argc, char **argv) { + volatile int a[] = { 1, 3, 3, 7, 9, 0, 0, 1 }; + return sort_test(sizeof(a)/sizeof(*a), (int*)a); + } + ''', + name : 'Microsoft qsort_s()', + args : ['-Wall', '-Werror'] +)) + prefer_relpath_systems = [ 'windows', ] diff --git a/src/util/meson.build b/src/util/meson.build index 6fe8aa53..38f512d0 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -15,6 +15,7 @@ util_src = files( 'miscmath.c', 'pngcruft.c', 'rectpack.c', + 'sort_r.c', 'strbuf.c', 'stringops.c', ) diff --git a/src/util/sort_r.c b/src/util/sort_r.c new file mode 100644 index 00000000..da7ccd0f --- /dev/null +++ b/src/util/sort_r.c @@ -0,0 +1,306 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2024, Lukas Weber . + * Copyright (c) 2012-2024, Andrei Alexeyev . + */ + +// +// Adapted from https://github.com/noporpoise/sort_r +// + +#include "sort_r.h" + +/* Isaac Turner 29 April 2014 Public Domain */ + +#include /* qsort_r(), qsort_s() */ +#include /* needed for memcpy() */ + +/* + +sort_r function to be exported. + +Parameters: + base is the array to be sorted + nel is the number of elements in the array + width is the size in bytes of each element of the array + compar is the comparison function + arg is a pointer to be passed to the comparison function + +void sort_r(void *base, size_t nel, size_t width, + int (*compar)(const void *_a, const void *_b, void *_arg), + void *arg); + +*/ + +#if defined TAISEI_BUILDCONF_HAVE_GNU_QSORT_R +# define _SORT_R_LINUX +#elif defined TAISEI_BUILDCONF_HAVE_BSD_QSORT_R +# define _SORT_R_BSD +#elif defined TAISEI_BUILDCONF_HAVE_MICROSOFT_QSORT_S +# define _SORT_R_WINDOWS +#else + /* Using our own recursive quicksort sort_r_simple() */ +#endif + +#if (defined NESTED_QSORT && NESTED_QSORT == 0) +# undef NESTED_QSORT +#endif + +#define SORT_R_SWAP(a,b,tmp) ((tmp) = (a), (a) = (b), (b) = (tmp)) + +/* swap a and b */ +/* a and b must not be equal! */ +static void sort_r_swap(char *restrict a, char *restrict b, size_t w) +{ + char tmp, *end = a+w; + for(; a < end; a++, b++) { SORT_R_SWAP(*a, *b, tmp); } +} + +/* swap a, b iff a>b */ +/* a and b must not be equal! */ +/* restrict is same as restrict but better support on old machines */ +static int sort_r_cmpswap( + char *restrict a, + char *restrict b, size_t w, + int (*compar)(const void *_a, const void *_b, void *_arg), + void *arg) +{ + if(compar(a, b, arg) > 0) { + sort_r_swap(a, b, w); + 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 void sort_r_swap_blocks(char *ptr, size_t na, size_t nb) +{ + if(na > 0 && nb > 0) { + if(na > nb) { sort_r_swap(ptr, ptr+na, nb); } + else { sort_r_swap(ptr, ptr+nb, na); } + } +} + +/* Implement recursive quicksort ourselves */ +/* Note: quicksort is not stable, equivalent values may be swapped */ +void sort_r_simple( + void *base, size_t nel, size_t w, + int (*compar)(const void *_a, const void *_b, void *_arg), + void *arg) +{ + char *b = (char *)base, *end = b + nel*w; + + /* for(size_t i=0; i b && sort_r_cmpswap(pj-w,pj,w,compar,arg); pj -= w) {} + } + } + else + { + /* nel > 6; Quicksort */ + + int cmp; + char *pl, *ple, *pr, *pre, *pivot; + char *last = b+w*(nel-1), *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 *l[3]; + l[0] = b + w; + l[1] = b+w*(nel/2); + l[2] = last - w; + + /* printf("pivots: %i, %i, %i\n", *(int*)l[0], *(int*)l[1], *(int*)l[2]); */ + + if(compar(l[0],l[1],arg) > 0) { SORT_R_SWAP(l[0], l[1], tmp); } + if(compar(l[1],l[2],arg) > 0) { + SORT_R_SWAP(l[1], l[2], tmp); + if(compar(l[0],l[1],arg) > 0) { SORT_R_SWAP(l[0], l[1], tmp); } + } + + /* swap mid value (l[1]), and last element to put pivot as last element */ + if(l[1] != last) { sort_r_swap(l[1], last, w); } + + /* + pl is the next item on the left to be compared to the pivot + pr is the last item on the right that was compared to the pivot + ple is the left position to put the next item that equals the pivot + ple is the last right position where we put an item that equals the pivot + + v- end (beyond the array) + EEEEEELLLLLLLLuuuuuuuuGGGGGGGEEEEEEEE. + ^- b ^- 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; + ple = pl = b; + pre = pr = 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(pl < pr) { + /* 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(; pl < pr; pl += w) { + cmp = compar(pl, pivot, arg); + if(cmp > 0) { break; } + else if(cmp == 0) { + if(ple < pl) { sort_r_swap(ple, pl, w); } + ple += w; + } + } + /* break if last batch of left hand items were equal to pivot */ + if(pl >= pr) { 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(; pl < pr; ) { + pr -= w; /* Move right pointer onto an unprocessed item */ + cmp = compar(pr, pivot, arg); + if(cmp == 0) { + pre -= w; + if(pr < pre) { sort_r_swap(pr, pre, w); } + } + else if(cmp < 0) { + if(pl < pr) { sort_r_swap(pl, pr, w); } + pl += w; + break; + } + } + } + + pl = pr; /* pr may have gone below pl */ + + /* + 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(b, ple-b, pl-ple); + sort_r_swap_blocks(pr, pre-pr, end-pre); + + /*for(size_t i=0; icompar)(a, b, ss->arg); + } + + #endif + + #if defined _SORT_R_LINUX + + typedef int(* __compar_d_fn_t)(const void *, const void *, void *); + extern void (qsort_r)(void *base, size_t nel, size_t width, + __compar_d_fn_t __compar, void *arg) + __attribute__((nonnull (1, 4))); + + #endif + + /* implementation */ + void sort_r( + void *base, size_t nel, size_t width, + int (*compar)(const void *_a, const void *_b, void *_arg), + void *arg) + { + #if defined _SORT_R_LINUX + + qsort_r(base, nel, width, compar, arg); + + #elif defined _SORT_R_BSD + + struct sort_r_data tmp; + tmp.arg = arg; + tmp.compar = compar; + qsort_r(base, nel, width, &tmp, sort_r_arg_swap); + + #elif defined _SORT_R_WINDOWS + + struct sort_r_data tmp; + tmp.arg = arg; + tmp.compar = compar; + qsort_s(base, nel, width, sort_r_arg_swap, &tmp); + + #else + + /* Fall back to our own quicksort implementation */ + sort_r_simple(base, nel, width, compar, arg); + + #endif + } + +#endif /* !NESTED_QSORT */ diff --git a/src/util/sort_r.h b/src/util/sort_r.h new file mode 100644 index 00000000..ffe52538 --- /dev/null +++ b/src/util/sort_r.h @@ -0,0 +1,22 @@ +/* + * This software is licensed under the terms of the MIT License. + * See COPYING for further information. + * --- + * Copyright (c) 2011-2024, Lukas Weber . + * Copyright (c) 2012-2024, Andrei Alexeyev . + */ + +#pragma once +#include "taisei.h" + +void sort_r_simple( + void *base, size_t nel, size_t w, + int (*compar)(const void *_a, const void *_b, void *_arg), + void *arg +); + +void sort_r( + void *base, size_t nel, size_t width, + int (*compar)(const void *_a, const void *_b, void *_arg), + void *arg +);