From 1c74afb01a070134446fb73d77f4f720627afd5a Mon Sep 17 00:00:00 2001 From: Luke Howard Date: Tue, 7 Apr 2020 13:49:27 +1000 Subject: [PATCH] roken: add mergesort_r() Add mergesort_r() as a stable sort function that can be used by other components of Heimdal. Note that there is no standardized prototype for this function, however it appears that both FreeBSD and glibc would adopt the glibc convention (where the private data argument appears last). See: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=214248 --- cf/roken-frag.m4 | 2 + lib/roken/NTMakefile | 1 + lib/roken/mergesort.c | 50 ++++++ lib/roken/mergesort_r.c | 339 +++++++++++++++++++++++++++++++++++ lib/roken/roken.h.in | 14 ++ lib/roken/version-script.map | 2 + 6 files changed, 408 insertions(+) create mode 100644 lib/roken/mergesort.c create mode 100644 lib/roken/mergesort_r.c diff --git a/cf/roken-frag.m4 b/cf/roken-frag.m4 index e8db74d1d..0557acfa2 100644 --- a/cf/roken-frag.m4 +++ b/cf/roken-frag.m4 @@ -367,6 +367,8 @@ AC_BROKEN([ \ lstat \ memmove \ memset_s \ + mergesort \ + mergesort_r \ mkstemp \ putenv \ rcmd \ diff --git a/lib/roken/NTMakefile b/lib/roken/NTMakefile index 157cccd6b..ce8005ad3 100644 --- a/lib/roken/NTMakefile +++ b/lib/roken/NTMakefile @@ -76,6 +76,7 @@ libroken_la_OBJS = \ $(OBJ)\lstat.obj \ $(OBJ)\memmem.obj \ $(OBJ)\memset_s.obj \ + $(OBJ)\mergesort_r.obj \ $(OBJ)\mkdir.obj \ $(OBJ)\mini_inetd.obj \ $(OBJ)\mkdtemp.obj \ diff --git a/lib/roken/mergesort.c b/lib/roken/mergesort.c new file mode 100644 index 000000000..ba51889b5 --- /dev/null +++ b/lib/roken/mergesort.c @@ -0,0 +1,50 @@ +/*********************************************************************** + * Copyright (c) 2020, AuriStor, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + **********************************************************************/ + +#include +#include "roken.h" + +static int +mergesort_compar_thunk(const void *a, const void *b, void *thunk) +{ + int (*compar)(const void *, const void *) = thunk; + + return compar(a, b); +} + +int ROKEN_LIB_FUNCTION +mergesort(void *base, size_t nel, size_t width, + int (*compar)(const void *, const void *)) +{ + return rk_mergesort_r(base, nel, width, + mergesort_compar_thunk, compar); +} + diff --git a/lib/roken/mergesort_r.c b/lib/roken/mergesort_r.c new file mode 100644 index 000000000..2d551a607 --- /dev/null +++ b/lib/roken/mergesort_r.c @@ -0,0 +1,339 @@ +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Peter McIlroy. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include "roken.h" + +/* + * Hybrid exponential search/linear search merge sort with hybrid + * natural/pairwise first pass. Requires about .3% more comparisons + * for random data than LSMS with pairwise first pass alone. + * It works for objects as small as two bytes. + */ + +#define NATURAL +#define THRESHOLD 16 /* Best choice for natural merge cut-off. */ + +/* #define NATURAL to get hybrid natural merge. + * (The default is pairwise merging.) + */ + +#include + +#include +#include +#include + +typedef int (*cmp_t)(const void *, const void *, void *); + +static void setup(u_char *, u_char *, size_t, size_t, cmp_t, void *); +static void insertionsort(u_char *, size_t, size_t, cmp_t, void *); + +#define ISIZE sizeof(int) +#define PSIZE sizeof(u_char *) +#define ICOPY_LIST(src, dst, last) \ + do \ + *(int*)dst = *(int*)src, src += ISIZE, dst += ISIZE; \ + while(src < last) +#define ICOPY_ELT(src, dst, i) \ + do \ + *(int*) dst = *(int*) src, src += ISIZE, dst += ISIZE; \ + while (i -= ISIZE) + +#define CCOPY_LIST(src, dst, last) \ + do \ + *dst++ = *src++; \ + while (src < last) +#define CCOPY_ELT(src, dst, i) \ + do \ + *dst++ = *src++; \ + while (i -= 1) + +/* + * Find the next possible pointer head. (Trickery for forcing an array + * to do double duty as a linked list when objects do not align with word + * boundaries. + */ +/* Assumption: PSIZE is a power of 2. */ +#define EVAL(p) (u_char **) \ + ((((u_char *)p + PSIZE - 1 - (u_char *) 0) & ~(PSIZE - 1))) + +/* + * Arguments are as for qsort_r, except thunk is moved to the last + * parameter for glibc compatibility. See: + * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=214248 + */ +int +mergesort_r(void *base, size_t nmemb, size_t size, cmp_t cmp, void *thunk) +{ + size_t i; + int sense; + int big, iflag; + u_char *f1, *f2, *t, *b, *tp2, *q, *l1, *l2; + u_char *list2, *list1, *p2, *p, *last, **p1; + + if (size < PSIZE / 2) { /* Pointers must fit into 2 * size. */ + errno = EINVAL; + return (-1); + } + + if (nmemb == 0) + return (0); + + /* + * XXX + * Stupid subtraction for the Cray. + */ + iflag = 0; + if (!(size % ISIZE) && !(((char *)base - (char *)0) % ISIZE)) + iflag = 1; + + if ((list2 = malloc(nmemb * size + PSIZE)) == NULL) + return (-1); + + list1 = base; + setup(list1, list2, nmemb, size, cmp, thunk); + last = list2 + nmemb * size; + i = big = 0; + while (*EVAL(list2) != last) { + l2 = list1; + p1 = EVAL(list1); + for (tp2 = p2 = list2; p2 != last; p1 = EVAL(l2)) { + p2 = *EVAL(p2); + f1 = l2; + f2 = l1 = list1 + (p2 - list2); + if (p2 != last) + p2 = *EVAL(p2); + l2 = list1 + (p2 - list2); + while (f1 < l1 && f2 < l2) { + if (cmp(f1, f2, thunk) <= 0) { + q = f2; + b = f1, t = l1; + sense = -1; + } else { + q = f1; + b = f2, t = l2; + sense = 0; + } + if (!big) { /* here i = 0 */ + while ((b += size) < t && cmp(q, b, thunk) >sense) + if (++i == 6) { + big = 1; + goto EXPONENTIAL; + } + } else { +EXPONENTIAL: for (i = size; ; i <<= 1) + if ((p = (b + i)) >= t) { + if ((p = t - size) > b && + cmp(q, p, thunk) <= sense) + t = p; + else + b = p; + break; + } else if (cmp(q, p, thunk) <= sense) { + t = p; + if (i == size) + big = 0; + goto FASTCASE; + } else + b = p; + while (t > b+size) { + i = (((t - b) / size) >> 1) * size; + if (cmp(q, p = b + i, thunk) <= sense) + t = p; + else + b = p; + } + goto COPY; +FASTCASE: while (i > size) + if (cmp(q, + p = b + (i >>= 1), thunk) <= sense) + t = p; + else + b = p; +COPY: b = t; + } + i = size; + if (q == f1) { + if (iflag) { + ICOPY_LIST(f2, tp2, b); + ICOPY_ELT(f1, tp2, i); + } else { + CCOPY_LIST(f2, tp2, b); + CCOPY_ELT(f1, tp2, i); + } + } else { + if (iflag) { + ICOPY_LIST(f1, tp2, b); + ICOPY_ELT(f2, tp2, i); + } else { + CCOPY_LIST(f1, tp2, b); + CCOPY_ELT(f2, tp2, i); + } + } + } + if (f2 < l2) { + if (iflag) + ICOPY_LIST(f2, tp2, l2); + else + CCOPY_LIST(f2, tp2, l2); + } else if (f1 < l1) { + if (iflag) + ICOPY_LIST(f1, tp2, l1); + else + CCOPY_LIST(f1, tp2, l1); + } + *p1 = l2; + } + tp2 = list1; /* swap list1, list2 */ + list1 = list2; + list2 = tp2; + last = list2 + nmemb*size; + } + if (base == list2) { + memmove(list2, list1, nmemb*size); + list2 = list1; + } + free(list2); + return (0); +} + +#define swap(a, b) { \ + s = b; \ + i = size; \ + do { \ + tmp = *a; *a++ = *s; *s++ = tmp; \ + } while (--i); \ + a -= size; \ + } +#define reverse(bot, top) { \ + s = top; \ + do { \ + i = size; \ + do { \ + tmp = *bot; *bot++ = *s; *s++ = tmp; \ + } while (--i); \ + s -= size2; \ + } while(bot < s); \ +} + +/* + * Optional hybrid natural/pairwise first pass. Eats up list1 in runs of + * increasing order, list2 in a corresponding linked list. Checks for runs + * when THRESHOLD/2 pairs compare with same sense. (Only used when NATURAL + * is defined. Otherwise simple pairwise merging is used.) + */ +void +setup(u_char *list1, u_char *list2, size_t n, size_t size, cmp_t cmp, void *thunk) +{ + int i, length, size2, tmp, sense; + u_char *f1, *f2, *s, *l2, *last, *p2; + + size2 = size*2; + if (n <= 5) { + insertionsort(list1, n, size, cmp, thunk); + *EVAL(list2) = (u_char*) list2 + n*size; + return; + } + /* + * Avoid running pointers out of bounds; limit n to evens + * for simplicity. + */ + i = 4 + (n & 1); + insertionsort(list1 + (n - i) * size, i, size, cmp, thunk); + last = list1 + size * (n - i); + *EVAL(list2 + (last - list1)) = list2 + n * size; + +#ifdef NATURAL + p2 = list2; + f1 = list1; + sense = (cmp(f1, f1 + size, thunk) > 0); + for (; f1 < last; sense = !sense) { + length = 2; + /* Find pairs with same sense. */ + for (f2 = f1 + size2; f2 < last; f2 += size2) { + if ((cmp(f2, f2+ size, thunk) > 0) != sense) + break; + length += 2; + } + if (length < THRESHOLD) { /* Pairwise merge */ + do { + p2 = *EVAL(p2) = f1 + size2 - list1 + list2; + if (sense > 0) + swap (f1, f1 + size); + } while ((f1 += size2) < f2); + } else { /* Natural merge */ + l2 = f2; + for (f2 = f1 + size2; f2 < l2; f2 += size2) { + if ((cmp(f2-size, f2, thunk) > 0) != sense) { + p2 = *EVAL(p2) = f2 - list1 + list2; + if (sense > 0) + reverse(f1, f2-size); + f1 = f2; + } + } + if (sense > 0) + reverse (f1, f2-size); + f1 = f2; + if (f2 < last || cmp(f2 - size, f2, thunk) > 0) + p2 = *EVAL(p2) = f2 - list1 + list2; + else + p2 = *EVAL(p2) = list2 + n*size; + } + } +#else /* pairwise merge only. */ + for (f1 = list1, p2 = list2; f1 < last; f1 += size2) { + p2 = *EVAL(p2) = p2 + size2; + if (cmp (f1, f1 + size) > 0) + swap(f1, f1 + size); + } +#endif /* NATURAL */ +} + +/* + * This is to avoid out-of-bounds addresses in sorting the + * last 4 elements. + */ +static void +insertionsort(u_char *a, size_t n, size_t size, cmp_t cmp, void *thunk) +{ + u_char *ai, *s, *t, *u, tmp; + int i; + + for (ai = a+size; --n >= 1; ai += size) + for (t = ai; t > a; t -= size) { + u = t - size; + if (cmp(u, t, thunk) <= 0) + break; + swap(u, t); + } +} diff --git a/lib/roken/roken.h.in b/lib/roken/roken.h.in index bf6bd3225..4fed1c469 100644 --- a/lib/roken/roken.h.in +++ b/lib/roken/roken.h.in @@ -1243,6 +1243,20 @@ void rk_qsort(void *, size_t, size_t, int (*)(const void *, const void *)); #endif +#if !defined(HAVE_MERGESORT) +#define mergesort rk_mergesort +int +mergesort(void *base, size_t nel, size_t width, + int (*compar)(const void *, const void *)); +#endif + +#if !defined(HAVE_MERGESORT_R) +#define mergesort_r rk_mergesort_r +int +mergesort_r(void *base, size_t nel, size_t width, + int (*compar)(const void *, const void *, void *), void *thunk); +#endif + #ifndef HAVE_MEMSET_S #define memset_s rk_memset_s ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL memset_s(void *s, size_t smax, diff --git a/lib/roken/version-script.map b/lib/roken/version-script.map index 35a2c1b4c..1a996b102 100644 --- a/lib/roken/version-script.map +++ b/lib/roken/version-script.map @@ -87,6 +87,8 @@ HEIMDAL_ROKEN_2.0 { rk_localtime_r; rk_memmem; rk_memset_s; + rk_mergesort; + rk_mergesort_r; rk_mkdir; rk_mkdtemp; rk_mkostemp;