From 014956ac1dda5f0baf45eb8c2a79e219308e9ed2 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Mon, 15 Feb 2016 03:16:46 -0500 Subject: [PATCH] Replaced SDL_qsort with public domain code from PDCLib: http://pdclib.e43.eu/ --- debian/copyright | 11 - src/stdlib/SDL_qsort.c | 602 ++++++++++++----------------------------- 2 files changed, 167 insertions(+), 446 deletions(-) diff --git a/debian/copyright b/debian/copyright index 8ce26d1c5..99c3d4496 100644 --- a/debian/copyright +++ b/debian/copyright @@ -31,10 +31,6 @@ Copyright: 1995 Erik Corry 1995 Brown University License: BrownUn_UnCalifornia_ErikCorry -Files: src/stdlib/SDL_qsort.c -Copyright: 1998 Gareth McCaughan -License: Gareth_McCaughan - Files: src/test/SDL_test_md5.c Copyright: 1997-2016 Sam Lantinga 1990 RSA Data Security, Inc. @@ -270,13 +266,6 @@ License: BrownUn_UnCalifornia_ErikCorry * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. */ -License: Gareth_McCaughan - You may use it in anything you like; you may make money - out of it; you may distribute it in object form or as - part of an executable without including source code; - you don't have to credit me. (But it would be nice if - you did.) - License: Johnson_M._Hart Permission is granted for any and all use providing that this copyright is properly acknowledged. diff --git a/src/stdlib/SDL_qsort.c b/src/stdlib/SDL_qsort.c index 0d1978424..b0a662f7e 100644 --- a/src/stdlib/SDL_qsort.c +++ b/src/stdlib/SDL_qsort.c @@ -1,58 +1,5 @@ -/* qsort.c - * (c) 1998 Gareth McCaughan - * - * This is a drop-in replacement for the C library's |qsort()| routine. - * - * Features: - * - Median-of-three pivoting (and more) - * - Truncation and final polishing by a single insertion sort - * - Early truncation when no swaps needed in pivoting step - * - Explicit recursion, guaranteed not to overflow - * - A few little wrinkles stolen from the GNU |qsort()|. - * - separate code for non-aligned / aligned / word-size objects - * - * This code may be reproduced freely provided - * - this file is retained unaltered apart from minor - * changes for portability and efficiency - * - no changes are made to this comment - * - any changes that *are* made are clearly flagged - * - the _ID string below is altered by inserting, after - * the date, the string " altered" followed at your option - * by other material. (Exceptions: you may change the name - * of the exported routine without changing the ID string. - * You may change the values of the macros TRUNC_* and - * PIVOT_THRESHOLD without changing the ID string, provided - * they remain constants with TRUNC_nonaligned, TRUNC_aligned - * and TRUNC_words/WORD_BYTES between 8 and 24, and - * PIVOT_THRESHOLD between 32 and 200.) - * - * You may use it in anything you like; you may make money - * out of it; you may distribute it in object form or as - * part of an executable without including source code; - * you don't have to credit me. (But it would be nice if - * you did.) - * - * If you find problems with this code, or find ways of - * making it significantly faster, please let me know! - * My e-mail address, valid as of early 1998 and certainly - * OK for at least the next 18 months, is - * gjm11@dpmms.cam.ac.uk - * Thanks! - * - * Gareth McCaughan Peterhouse Cambridge 1998 - */ - -#if defined(__clang_analyzer__) && !defined(SDL_DISABLE_ANALYZE_MACROS) -#define SDL_DISABLE_ANALYZE_MACROS 1 -#endif - #include "../SDL_internal.h" -/* -#include -#include -#include -*/ #include "SDL_stdinc.h" #include "SDL_assert.h" @@ -64,418 +11,203 @@ SDL_qsort(void *base, size_t nmemb, size_t size, int (*compare) (const void *, c } #else -#ifdef assert -#undef assert +#ifdef REGTEST +#undef REGTEST #endif -#define assert(X) SDL_assert(X) -#ifdef malloc -#undef malloc + +#ifdef TEST +#undef TEST #endif -#define malloc SDL_malloc -#ifdef free -#undef free + +#ifndef _PDCLIB_memswp +#define _PDCLIB_memswp( i, j, size ) char tmp; do { tmp = *i; *i++ = *j; *j++ = tmp; } while ( --size ); #endif -#define free SDL_free -#ifdef memcpy -#undef memcpy + +#ifndef _PDCLIB_size_t +#define _PDCLIB_size_t size_t #endif -#define memcpy SDL_memcpy -#ifdef memmove -#undef memmove -#endif -#define memmove SDL_memmove -#ifdef qsort -#undef qsort -#endif -#define qsort SDL_qsort -static const char _ID[] = ""; +#define qsort SDL_qsort -/* How many bytes are there per word? (Must be a power of 2, - * and must in fact equal sizeof(int).) - */ -#define WORD_BYTES sizeof(int) +#define inline SDL_INLINE -/* How big does our stack need to be? Answer: one entry per - * bit in a |size_t|. - */ -#define STACK_SIZE (8*sizeof(size_t)) +/* +This code came from PDCLib, under the public domain. Specifically this: +https://bitbucket.org/pdclib/pdclib/raw/a82b02d0c7d4ed633b97f2a7639d9a10b1c92ec8/functions/stdlib/qsort.c +The _PDCLIB_memswp macro was from +https://bitbucket.org/pdclib/pdclib/src/a82b02d0c7d4ed633b97f2a7639d9a10b1c92ec8/platform/posix/internals/_PDCLIB_config.h?at=default&fileviewer=file-view-default#_PDCLIB_config.h-28 -/* Different situations have slightly different requirements, - * and we make life epsilon easier by using different truncation - * points for the three different cases. - * So far, I have tuned TRUNC_words and guessed that the same - * value might work well for the other two cases. Of course - * what works well on my machine might work badly on yours. - */ -#define TRUNC_nonaligned 12 -#define TRUNC_aligned 12 -#define TRUNC_words 12*WORD_BYTES /* nb different meaning */ +Everything below this comment until the HAVE_QSORT #endif was from PDCLib. +--ryan. +*/ -/* We use a simple pivoting algorithm for shortish sub-arrays - * and a more complicated one for larger ones. The threshold - * is PIVOT_THRESHOLD. - */ -#define PIVOT_THRESHOLD 40 +/* $Id$ */ -typedef struct +/* qsort( void *, size_t, size_t, int(*)( const void *, const void * ) ) + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#include + +#ifndef REGTEST + +/* This implementation is taken from Paul Edward's PDPCLIB. + + Original code is credited to Raymond Gardner, Englewood CO. + Minor mods are credited to Paul Edwards. + Some reformatting and simplification done by Martin Baute. + All code is still Public Domain. +*/ + +/* Wrapper for _PDCLIB_memswp protects against multiple argument evaluation. */ +static inline void memswp( char * i, char * j, size_t size ) { - char *first; - char *last; -} stack_entry; -#define pushLeft {stack[stacktop].first=ffirst;stack[stacktop++].last=last;} -#define pushRight {stack[stacktop].first=first;stack[stacktop++].last=llast;} -#define doLeft {first=ffirst;llast=last;continue;} -#define doRight {ffirst=first;last=llast;continue;} -#define pop {if (--stacktop<0) break;\ - first=ffirst=stack[stacktop].first;\ - last=llast=stack[stacktop].last;\ - continue;} - -/* Some comments on the implementation. - * 1. When we finish partitioning the array into "low" - * and "high", we forget entirely about short subarrays, - * because they'll be done later by insertion sort. - * Doing lots of little insertion sorts might be a win - * on large datasets for locality-of-reference reasons, - * but it makes the code much nastier and increases - * bookkeeping overhead. - * 2. We always save the shorter and get to work on the - * longer. This guarantees that every time we push - * an item onto the stack its size is <= 1/2 of that - * of its parent; so the stack can't need more than - * log_2(max-array-size) entries. - * 3. We choose a pivot by looking at the first, last - * and middle elements. We arrange them into order - * because it's easy to do that in conjunction with - * choosing the pivot, and it makes things a little - * easier in the partitioning step. Anyway, the pivot - * is the middle of these three. It's still possible - * to construct datasets where the algorithm takes - * time of order n^2, but it simply never happens in - * practice. - * 3' Newsflash: On further investigation I find that - * it's easy to construct datasets where median-of-3 - * simply isn't good enough. So on large-ish subarrays - * we do a more sophisticated pivoting: we take three - * sets of 3 elements, find their medians, and then - * take the median of those. - * 4. We copy the pivot element to a separate place - * because that way we can always do our comparisons - * directly against a pointer to that separate place, - * and don't have to wonder "did we move the pivot - * element?". This makes the inner loop better. - * 5. It's possible to make the pivoting even more - * reliable by looking at more candidates when n - * is larger. (Taking this to its logical conclusion - * results in a variant of quicksort that doesn't - * have that n^2 worst case.) However, the overhead - * from the extra bookkeeping means that it's just - * not worth while. - * 6. This is pretty clean and portable code. Here are - * all the potential portability pitfalls and problems - * I know of: - * - In one place (the insertion sort) I construct - * a pointer that points just past the end of the - * supplied array, and assume that (a) it won't - * compare equal to any pointer within the array, - * and (b) it will compare equal to a pointer - * obtained by stepping off the end of the array. - * These might fail on some segmented architectures. - * - I assume that there are 8 bits in a |char| when - * computing the size of stack needed. This would - * fail on machines with 9-bit or 16-bit bytes. - * - I assume that if |((int)base&(sizeof(int)-1))==0| - * and |(size&(sizeof(int)-1))==0| then it's safe to - * get at array elements via |int*|s, and that if - * actually |size==sizeof(int)| as well then it's - * safe to treat the elements as |int|s. This might - * fail on systems that convert pointers to integers - * in non-standard ways. - * - I assume that |8*sizeof(size_t)<=INT_MAX|. This - * would be false on a machine with 8-bit |char|s, - * 16-bit |int|s and 4096-bit |size_t|s. :-) - */ - -/* The recursion logic is the same in each case: */ -#define Recurse(Trunc) \ - { size_t l=last-ffirst,r=llast-first; \ - if (l=Trunc) doRight \ - else pop \ - } \ - else if (l<=r) { pushLeft; doRight } \ - else if (r>=Trunc) { pushRight; doLeft }\ - else doLeft \ - } - -/* and so is the pivoting logic: */ -#define Pivot(swapper,sz) \ - if ((size_t)(last-first)>PIVOT_THRESHOLD*sz) mid=pivot_big(first,mid,last,sz,compare);\ - else { \ - if (compare(first,mid)<0) { \ - if (compare(mid,last)>0) { \ - swapper(mid,last); \ - if (compare(first,mid)>0) swapper(first,mid);\ - } \ - } \ - else { \ - if (compare(mid,last)>0) swapper(first,last)\ - else { \ - swapper(first,mid); \ - if (compare(mid,last)>0) swapper(mid,last);\ - } \ - } \ - first+=sz; last-=sz; \ - } - -#ifdef DEBUG_QSORT -#include -#endif - -/* and so is the partitioning logic: */ -#define Partition(swapper,sz) { \ - int swapped=0; \ - do { \ - while (compare(first,pivot)<0) first+=sz; \ - while (compare(pivot,last)<0) last-=sz; \ - if (firstlimit ? limit : nmemb-1)*sz;\ - while (last!=base) { \ - if (compare(first,last)>0) first=last; \ - last-=sz; } \ - if (first!=base) swapper(first,(char*)base); +/* For small sets, insertion sort is faster than quicksort. + T is the threshold below which insertion sort will be used. + Must be 3 or larger. +*/ +#define T 7 -/* and so is the insertion sort, in the first two cases: */ -#define Insertion(swapper) \ - last=((char*)base)+nmemb*size; \ - for (first=((char*)base)+size;first!=last;first+=size) { \ - char *test; \ - /* Find the right place for |first|. \ - * My apologies for var reuse. */ \ - for (test=first-size;compare(test,first)>0;test-=size) ; \ - test+=size; \ - if (test!=first) { \ - /* Shift everything in [test,first) \ - * up by one, and place |first| \ - * where |test| is. */ \ - memcpy(pivot,first,size); \ - memmove(test+size,test,first-test); \ - memcpy(test,pivot,size); \ - } \ - } +/* Macros for handling the QSort stack */ +#define PREPARE_STACK char * stack[STACKSIZE]; char * * stackptr = stack +#define PUSH( base, limit ) stackptr[0] = base; stackptr[1] = limit; stackptr += 2 +#define POP( base, limit ) stackptr -= 2; base = stackptr[0]; limit = stackptr[1] +/* TODO: Stack usage is log2( nmemb ) (minus what T shaves off the worst case). + Worst-case nmemb is platform dependent and should probably be + configured through _PDCLIB_config.h. +*/ +#define STACKSIZE 64 -#define SWAP_nonaligned(a,b) { \ - register char *aa=(a),*bb=(b); \ - register size_t sz=size; \ - do { register char t=*aa; *aa++=*bb; *bb++=t; } while (--sz); } - -#define SWAP_aligned(a,b) { \ - register int *aa=(int*)(a),*bb=(int*)(b); \ - register size_t sz=size; \ - do { register int t=*aa;*aa++=*bb; *bb++=t; } while (sz-=WORD_BYTES); } - -#define SWAP_words(a,b) { \ - register int t=*((int*)a); *((int*)a)=*((int*)b); *((int*)b)=t; } - -/* ---------------------------------------------------------------------- */ - -static char * -pivot_big(char *first, char *mid, char *last, size_t size, - int compare(const void *, const void *)) +void qsort( void * base, size_t nmemb, size_t size, int (*compar)( const void *, const void * ) ) { - size_t d = (((last - first) / size) >> 3) * size; - char *m1, *m2, *m3; + char * i; + char * j; + _PDCLIB_size_t thresh = T * size; + char * base_ = (char *)base; + char * limit = base_ + nmemb * size; + PREPARE_STACK; + + for ( ;; ) { - char *a = first, *b = first + d, *c = first + 2 * d; -#ifdef DEBUG_QSORT - fprintf(stderr, "< %d %d %d\n", *(int *) a, *(int *) b, *(int *) c); -#endif - m1 = compare(a, b) < 0 ? - (compare(b, c) < 0 ? b : (compare(a, c) < 0 ? c : a)) - : (compare(a, c) < 0 ? a : (compare(b, c) < 0 ? c : b)); - } - { - char *a = mid - d, *b = mid, *c = mid + d; -#ifdef DEBUG_QSORT - fprintf(stderr, ". %d %d %d\n", *(int *) a, *(int *) b, *(int *) c); -#endif - m2 = compare(a, b) < 0 ? - (compare(b, c) < 0 ? b : (compare(a, c) < 0 ? c : a)) - : (compare(a, c) < 0 ? a : (compare(b, c) < 0 ? c : b)); - } - { - char *a = last - 2 * d, *b = last - d, *c = last; -#ifdef DEBUG_QSORT - fprintf(stderr, "> %d %d %d\n", *(int *) a, *(int *) b, *(int *) c); -#endif - m3 = compare(a, b) < 0 ? - (compare(b, c) < 0 ? b : (compare(a, c) < 0 ? c : a)) - : (compare(a, c) < 0 ? a : (compare(b, c) < 0 ? c : b)); - } -#ifdef DEBUG_QSORT - fprintf(stderr, "-> %d %d %d\n", *(int *) m1, *(int *) m2, *(int *) m3); -#endif - return compare(m1, m2) < 0 ? - (compare(m2, m3) < 0 ? m2 : (compare(m1, m3) < 0 ? m3 : m1)) - : (compare(m1, m3) < 0 ? m1 : (compare(m2, m3) < 0 ? m3 : m2)); -} - -/* ---------------------------------------------------------------------- */ - -static void -qsort_nonaligned(void *base, size_t nmemb, size_t size, - int (*compare) (const void *, const void *)) -{ - - stack_entry stack[STACK_SIZE]; - int stacktop = 0; - char *first, *last; - char *pivot = malloc(size); - size_t trunc = TRUNC_nonaligned * size; - assert(pivot != 0); - - first = (char *) base; - last = first + (nmemb - 1) * size; - - if ((size_t) (last - first) > trunc) { - char *ffirst = first, *llast = last; - while (1) { - /* Select pivot */ + if ( (size_t)( limit - base_ ) > thresh ) /* QSort for more than T elements. */ + { + /* We work from second to last - first will be pivot element. */ + i = base_ + size; + j = limit - size; + /* We swap first with middle element, then sort that with second + and last element so that eventually first element is the median + of the three - avoiding pathological pivots. + TODO: Instead of middle element, chose one randomly. + */ + memswp( ( ( ( (size_t)( limit - base_ ) ) / size ) / 2 ) * size + base_, base_, size ); + if ( compar( i, j ) > 0 ) memswp( i, j, size ); + if ( compar( base_, j ) > 0 ) memswp( base_, j, size ); + if ( compar( i, base_ ) > 0 ) memswp( i, base_, size ); + /* Now we have the median for pivot element, entering main Quicksort. */ + for ( ;; ) { - char *mid = first + size * ((last - first) / size >> 1); - Pivot(SWAP_nonaligned, size); - memcpy(pivot, mid, size); + do + { + /* move i right until *i >= pivot */ + i += size; + } while ( compar( i, base_ ) < 0 ); + do + { + /* move j left until *j <= pivot */ + j -= size; + } while ( compar( j, base_ ) > 0 ); + if ( i > j ) + { + /* break loop if pointers crossed */ + break; + } + /* else swap elements, keep scanning */ + memswp( i, j, size ); } - /* Partition. */ - Partition(SWAP_nonaligned, size); - /* Prepare to recurse/iterate. */ - Recurse(trunc)} - } - PreInsertion(SWAP_nonaligned, TRUNC_nonaligned, size); - Insertion(SWAP_nonaligned); - free(pivot); -} - -static void -qsort_aligned(void *base, size_t nmemb, size_t size, - int (*compare) (const void *, const void *)) -{ - - stack_entry stack[STACK_SIZE]; - int stacktop = 0; - char *first, *last; - char *pivot = malloc(size); - size_t trunc = TRUNC_aligned * size; - assert(pivot != 0); - - first = (char *) base; - last = first + (nmemb - 1) * size; - - if ((size_t) (last - first) > trunc) { - char *ffirst = first, *llast = last; - while (1) { - /* Select pivot */ + /* move pivot into correct place */ + memswp( base_, j, size ); + /* larger subfile base / limit to stack, sort smaller */ + if ( j - base_ > limit - i ) { - char *mid = first + size * ((last - first) / size >> 1); - Pivot(SWAP_aligned, size); - memcpy(pivot, mid, size); + /* left is larger */ + PUSH( base_, j ); + base_ = i; } - /* Partition. */ - Partition(SWAP_aligned, size); - /* Prepare to recurse/iterate. */ - Recurse(trunc)} - } - PreInsertion(SWAP_aligned, TRUNC_aligned, size); - Insertion(SWAP_aligned); - free(pivot); -} - -static void -qsort_words(void *base, size_t nmemb, - int (*compare) (const void *, const void *)) -{ - - stack_entry stack[STACK_SIZE]; - int stacktop = 0; - char *first, *last; - char *pivot = malloc(WORD_BYTES); - assert(pivot != 0); - - first = (char *) base; - last = first + (nmemb - 1) * WORD_BYTES; - - if (last - first > TRUNC_words) { - char *ffirst = first, *llast = last; - while (1) { -#ifdef DEBUG_QSORT - fprintf(stderr, "Doing %d:%d: ", - (first - (char *) base) / WORD_BYTES, - (last - (char *) base) / WORD_BYTES); -#endif - /* Select pivot */ + else { - char *mid = - first + WORD_BYTES * ((last - first) / (2 * WORD_BYTES)); - Pivot(SWAP_words, WORD_BYTES); - *(int *) pivot = *(int *) mid; + /* right is larger */ + PUSH( i, limit ); + limit = j; + } + } + else /* insertion sort for less than T elements */ + { + for ( j = base_, i = j + size; i < limit; j = i, i += size ) + { + for ( ; compar( j, j + size ) > 0; j -= size ) + { + memswp( j, j + size, size ); + if ( j == base_ ) + { + break; + } + } + } + if ( stackptr != stack ) /* if any entries on stack */ + { + POP( base_, limit ); + } + else /* else stack empty, done */ + { + break; } -#ifdef DEBUG_QSORT - fprintf(stderr, "pivot=%d\n", *(int *) pivot); -#endif - /* Partition. */ - Partition(SWAP_words, WORD_BYTES); - /* Prepare to recurse/iterate. */ - Recurse(TRUNC_words)} - } - PreInsertion(SWAP_words, (TRUNC_words / WORD_BYTES), WORD_BYTES); - /* Now do insertion sort. */ - last = ((char *) base) + nmemb * WORD_BYTES; - for (first = ((char *) base) + WORD_BYTES; first != last; - first += WORD_BYTES) { - /* Find the right place for |first|. My apologies for var reuse */ - int *pl = (int *) (first - WORD_BYTES), *pr = (int *) first; - *(int *) pivot = *(int *) first; - for (; compare(pl, pivot) > 0; pr = pl, --pl) { - *pr = *pl; } - if (pr != (int *) first) - *pr = *(int *) pivot; } - free(pivot); } -/* ---------------------------------------------------------------------- */ +#endif -void -qsort(void *base, size_t nmemb, size_t size, - int (*compare) (const void *, const void *)) +#ifdef TEST +#include <_PDCLIB_test.h> +#include +#include + +static int compare( const void * left, const void * right ) { - - if (nmemb <= 1) - return; - if (((uintptr_t) base | size) & (WORD_BYTES - 1)) - qsort_nonaligned(base, nmemb, size, compare); - else if (size != WORD_BYTES) - qsort_aligned(base, nmemb, size, compare); - else - qsort_words(base, nmemb, compare); + return *( (unsigned char *)left ) - *( (unsigned char *)right ); } -#endif /* !SDL_qsort */ +int main( void ) +{ + char presort[] = { "shreicnyjqpvozxmbt" }; + char sorted1[] = { "bcehijmnopqrstvxyz" }; + char sorted2[] = { "bticjqnyozpvreshxm" }; + char s[19]; + strcpy( s, presort ); + qsort( s, 18, 1, compare ); + TESTCASE( strcmp( s, sorted1 ) == 0 ); + strcpy( s, presort ); + qsort( s, 9, 2, compare ); + TESTCASE( strcmp( s, sorted2 ) == 0 ); + strcpy( s, presort ); + qsort( s, 1, 1, compare ); + TESTCASE( strcmp( s, presort ) == 0 ); +#if defined(REGTEST) && (__BSD_VISIBLE || __APPLE__) + puts( "qsort.c: Skipping test #4 for BSD as it goes into endless loop here." ); +#else + qsort( s, 100, 0, compare ); + TESTCASE( strcmp( s, presort ) == 0 ); +#endif + return TEST_RESULTS; +} + +#endif + +#endif /* HAVE_QSORT */ /* vi: set ts=4 sw=4 expandtab: */