diff --git a/CMakeLists.txt b/CMakeLists.txt index f4e675b18..1cce17fc0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1058,7 +1058,7 @@ if(SDL_LIBC) set(symbols_to_check abs acos acosf asin asinf atan atan2 atan2f atanf atof atoi - bcopy bsearch + bcopy calloc ceil ceilf copysign copysignf cos cosf _Exit exp expf fabs fabsf floor floorf fmod fmodf fopen64 free fseeko fseeko64 @@ -1067,7 +1067,6 @@ if(SDL_LIBC) log log10 log10f logf lround lroundf _ltoa malloc memcmp memcpy memmove memset modf modff pow powf putenv - qsort realloc rindex round roundf scalbn scalbnf setenv sin sinf sqr sqrt sqrtf sscanf strchr strcmp strlcat strlcpy strlen strncmp strnlen diff --git a/build-scripts/check_stdlib_usage.py b/build-scripts/check_stdlib_usage.py index 5e62ff187..8dea74e14 100755 --- a/build-scripts/check_stdlib_usage.py +++ b/build-scripts/check_stdlib_usage.py @@ -42,6 +42,7 @@ words = [ 'atanf', 'atof', 'atoi', + 'bsearch', 'calloc', 'ceil', 'ceilf', @@ -90,6 +91,8 @@ words = [ 'pow', 'powf', 'qsort', + 'qsort_r', + 'qsort_s', 'realloc', 'round', 'roundf', diff --git a/include/SDL3/SDL_stdinc.h b/include/SDL3/SDL_stdinc.h index e8da4026e..281aa56d1 100644 --- a/include/SDL3/SDL_stdinc.h +++ b/include/SDL3/SDL_stdinc.h @@ -498,6 +498,9 @@ extern DECLSPEC int SDLCALL SDL_setenv(const char *name, const char *value, int extern DECLSPEC void SDLCALL SDL_qsort(void *base, size_t nmemb, size_t size, int (SDLCALL *compare) (const void *, const void *)); extern DECLSPEC void * SDLCALL SDL_bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (SDLCALL *compare) (const void *, const void *)); +extern DECLSPEC void SDLCALL SDL_qsort_r(void *base, size_t nmemb, size_t size, int (SDLCALL *compare) (void *, const void *, const void *), void *userdata); +extern DECLSPEC void * SDLCALL SDL_bsearch_r(const void *key, const void *base, size_t nmemb, size_t size, int (SDLCALL *compare) (void *, const void *, const void *), void *userdata); + extern DECLSPEC int SDLCALL SDL_abs(int x); /* NOTE: these double-evaluate their arguments, so you should never have side effects in the parameters */ diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index be0ac3f46..e6f5d91c3 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -82,8 +82,6 @@ #cmakedefine HAVE_PUTENV 1 #cmakedefine HAVE_UNSETENV 1 #endif -#cmakedefine HAVE_QSORT 1 -#cmakedefine HAVE_BSEARCH 1 #cmakedefine HAVE_ABS 1 #cmakedefine HAVE_BCOPY 1 #cmakedefine HAVE_MEMSET 1 diff --git a/include/build_config/SDL_build_config_android.h b/include/build_config/SDL_build_config_android.h index f279a40c1..64f8076e0 100644 --- a/include/build_config/SDL_build_config_android.h +++ b/include/build_config/SDL_build_config_android.h @@ -63,8 +63,6 @@ #define HAVE_PUTENV 1 #define HAVE_SETENV 1 #define HAVE_UNSETENV 1 -#define HAVE_QSORT 1 -#define HAVE_BSEARCH 1 #define HAVE_ABS 1 #define HAVE_BCOPY 1 #define HAVE_MEMSET 1 diff --git a/include/build_config/SDL_build_config_emscripten.h b/include/build_config/SDL_build_config_emscripten.h index 9e4ad6aa2..89d5531f3 100644 --- a/include/build_config/SDL_build_config_emscripten.h +++ b/include/build_config/SDL_build_config_emscripten.h @@ -65,8 +65,6 @@ #define HAVE_SETENV 1 #define HAVE_PUTENV 1 #define HAVE_UNSETENV 1 -#define HAVE_QSORT 1 -#define HAVE_BSEARCH 1 #define HAVE_ABS 1 #define HAVE_BCOPY 1 #define HAVE_MEMSET 1 diff --git a/include/build_config/SDL_build_config_ios.h b/include/build_config/SDL_build_config_ios.h index ed3b54808..e130cc467 100644 --- a/include/build_config/SDL_build_config_ios.h +++ b/include/build_config/SDL_build_config_ios.h @@ -55,8 +55,6 @@ #define HAVE_PUTENV 1 #define HAVE_SETENV 1 #define HAVE_UNSETENV 1 -#define HAVE_QSORT 1 -#define HAVE_BSEARCH 1 #define HAVE_ABS 1 #define HAVE_BCOPY 1 #define HAVE_MEMSET 1 diff --git a/include/build_config/SDL_build_config_macos.h b/include/build_config/SDL_build_config_macos.h index 7766ddc09..39ee50d45 100644 --- a/include/build_config/SDL_build_config_macos.h +++ b/include/build_config/SDL_build_config_macos.h @@ -59,8 +59,6 @@ #define HAVE_SETENV 1 #define HAVE_PUTENV 1 #define HAVE_UNSETENV 1 -#define HAVE_QSORT 1 -#define HAVE_BSEARCH 1 #define HAVE_ABS 1 #define HAVE_BCOPY 1 #define HAVE_MEMSET 1 diff --git a/include/build_config/SDL_build_config_windows.h b/include/build_config/SDL_build_config_windows.h index 55f4f7c53..19873dbf1 100644 --- a/include/build_config/SDL_build_config_windows.h +++ b/include/build_config/SDL_build_config_windows.h @@ -135,8 +135,6 @@ typedef unsigned int uintptr_t; #define HAVE_CALLOC 1 #define HAVE_REALLOC 1 #define HAVE_FREE 1 -#define HAVE_QSORT 1 -#define HAVE_BSEARCH 1 #define HAVE_ABS 1 #define HAVE_MEMSET 1 #define HAVE_MEMCPY 1 diff --git a/include/build_config/SDL_build_config_wingdk.h b/include/build_config/SDL_build_config_wingdk.h index a636694a0..992052a81 100644 --- a/include/build_config/SDL_build_config_wingdk.h +++ b/include/build_config/SDL_build_config_wingdk.h @@ -76,8 +76,6 @@ #define HAVE_CALLOC 1 #define HAVE_REALLOC 1 #define HAVE_FREE 1 -#define HAVE_QSORT 1 -#define HAVE_BSEARCH 1 #define HAVE_ABS 1 #define HAVE_MEMSET 1 #define HAVE_MEMCPY 1 diff --git a/include/build_config/SDL_build_config_winrt.h b/include/build_config/SDL_build_config_winrt.h index 07c656ef3..b5e5725fb 100644 --- a/include/build_config/SDL_build_config_winrt.h +++ b/include/build_config/SDL_build_config_winrt.h @@ -76,8 +76,6 @@ #define HAVE_CALLOC 1 #define HAVE_REALLOC 1 #define HAVE_FREE 1 -#define HAVE_QSORT 1 -#define HAVE_BSEARCH 1 #define HAVE_ABS 1 #define HAVE_MEMSET 1 #define HAVE_MEMCPY 1 diff --git a/include/build_config/SDL_build_config_xbox.h b/include/build_config/SDL_build_config_xbox.h index ea050da7d..861503673 100644 --- a/include/build_config/SDL_build_config_xbox.h +++ b/include/build_config/SDL_build_config_xbox.h @@ -76,8 +76,6 @@ #define HAVE_CALLOC 1 #define HAVE_REALLOC 1 #define HAVE_FREE 1 -#define HAVE_QSORT 1 -#define HAVE_BSEARCH 1 #define HAVE_ABS 1 #define HAVE_MEMSET 1 #define HAVE_MEMCPY 1 diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index e91067cfd..e351d9f08 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -971,6 +971,8 @@ SDL3_0.0.0 { SDL_CloseCamera; SDL_GetCameraPermissionState; SDL_GetCameraDevicePosition; + SDL_qsort_r; + SDL_bsearch_r; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index e786e75aa..ad8030b6c 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -996,3 +996,5 @@ #define SDL_CloseCamera SDL_CloseCamera_REAL #define SDL_GetCameraPermissionState SDL_GetCameraPermissionState_REAL #define SDL_GetCameraDevicePosition SDL_GetCameraDevicePosition_REAL +#define SDL_qsort_r SDL_qsort_r_REAL +#define SDL_bsearch_r SDL_bsearch_r_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 98e2ffc2a..3971dab57 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1021,3 +1021,5 @@ SDL_DYNAPI_PROC(int,SDL_ReleaseCameraFrame,(SDL_Camera *a, SDL_Surface *b),(a,b) SDL_DYNAPI_PROC(void,SDL_CloseCamera,(SDL_Camera *a),(a),) SDL_DYNAPI_PROC(int,SDL_GetCameraPermissionState,(SDL_Camera *a),(a),return) SDL_DYNAPI_PROC(SDL_CameraPosition,SDL_GetCameraDevicePosition,(SDL_CameraDeviceID a),(a),return) +SDL_DYNAPI_PROC(void,SDL_qsort_r,(void *a, size_t b, size_t c, int (SDLCALL *d)(void *, const void *, const void *), void *e),(a,b,c,d,e),) +SDL_DYNAPI_PROC(void*,SDL_bsearch_r,(const void *a, const void *b, size_t c, size_t d, int (SDLCALL *e)(void *, const void *, const void *), void *f),(a,b,c,d,e,f),return) diff --git a/src/stdlib/SDL_qsort.c b/src/stdlib/SDL_qsort.c index 3683e99d2..771712d20 100644 --- a/src/stdlib/SDL_qsort.c +++ b/src/stdlib/SDL_qsort.c @@ -20,17 +20,10 @@ */ #include "SDL_internal.h" - -#ifdef HAVE_QSORT -void SDL_qsort(void *base, size_t nmemb, size_t size, int (*compare) (const void *, const void *)) -{ - if (!base) { - return; - } - qsort(base, nmemb, size, compare); -} - -#else +// SDL3 always uses its own internal qsort implementation, below, so +// it can guarantee stable sorts across platforms and not have to +// tapdance to support the various qsort_r interfaces, or bridge from +// the C runtime's non-SDLCALL compare functions. #ifdef assert #undef assert @@ -52,10 +45,6 @@ void SDL_qsort(void *base, size_t nmemb, size_t size, int (*compare) (const void #undef memmove #endif #define memmove SDL_memmove -#ifdef qsortG -#undef qsortG -#endif -#define qsortG SDL_qsort /* This code came from Gareth McCaughan, under the zlib license. @@ -67,6 +56,8 @@ Everything below this comment until the HAVE_QSORT #endif was from Gareth Thank you to Gareth for relicensing this code under the zlib license for our benefit! +Update for SDL3: we have modified this from a qsort function to qsort_r. + --ryan. */ @@ -152,7 +143,7 @@ benefit! #undef DEBUG_QSORT -static char _ID[]=""; +static char _ID[]=""; #endif /* END SDL CHANGE ... commented this out with an #if 0 block. --ryan. */ @@ -274,19 +265,19 @@ typedef struct { char * first; char * last; } stack_entry; /* and so is the pivoting logic (note: last is inclusive): */ #define Pivot(swapper,sz) \ - if ((size_t)(last-first)>PIVOT_THRESHOLD*sz) mid=pivot_big(first,mid,last,sz,compare);\ + if ((size_t)(last-first)>PIVOT_THRESHOLD*sz) mid=pivot_big(first,mid,last,sz,compare,userdata);\ else { \ - if (compare(first,mid)<0) { \ - if (compare(mid,last)>0) { \ + if (compare(userdata,first,mid)<0) { \ + if (compare(userdata,mid,last)>0) { \ swapper(mid,last); \ - if (compare(first,mid)>0) swapper(first,mid);\ + if (compare(userdata,first,mid)>0) swapper(first,mid);\ } \ } \ else { \ - if (compare(mid,last)>0) swapper(first,last)\ + if (compare(userdata,mid,last)>0) swapper(first,last)\ else { \ swapper(first,mid); \ - if (compare(mid,last)>0) swapper(mid,last);\ + if (compare(userdata,mid,last)>0) swapper(mid,last);\ } \ } \ first+=sz; last-=sz; \ @@ -299,8 +290,8 @@ typedef struct { char * first; char * last; } stack_entry; /* and so is the partitioning logic: */ #define Partition(swapper,sz) { \ do { \ - while (compare(first,pivot)<0) first+=sz; \ - while (compare(pivot,last)<0) last-=sz; \ + while (compare(userdata,first,pivot)<0) first+=sz; \ + while (compare(userdata,pivot,last)<0) last-=sz; \ if (firstlimit ? limit : nmemb)-1)*sz;\ while (last!=base) { \ - if (compare(first,last)>0) first=last; \ + if (compare(userdata,first,last)>0) first=last; \ last-=sz; } \ if (first!=base) swapper(first,(char*)base); @@ -334,7 +325,7 @@ typedef struct { char * first; char * last; } stack_entry; char *test; \ /* Find the right place for |first|. \ * My apologies for var reuse. */ \ - for (test=first-size;compare(test,first)>0;test-=size) ; \ + for (test=first-size;compare(userdata,test,first)>0;test-=size) ; \ test+=size; \ if (test!=first) { \ /* Shift everything in [test,first) \ @@ -362,7 +353,7 @@ typedef struct { char * first; char * last; } stack_entry; /* ---------------------------------------------------------------------- */ static char * pivot_big(char *first, char *mid, char *last, size_t size, - int compare(const void *, const void *)) { + int (SDLCALL * compare)(void *, const void *, const void *), void *userdata) { size_t d=(((last-first)/size)>>3)*size; #ifdef DEBUG_QSORT fprintf(stderr, "pivot_big: first=%p last=%p size=%lu n=%lu\n", first, (unsigned long)last, size, (unsigned long)((last-first+1)/size)); @@ -372,38 +363,38 @@ fprintf(stderr, "pivot_big: first=%p last=%p size=%lu n=%lu\n", first, (unsigned #ifdef DEBUG_QSORT fprintf(stderr,"< %d %d %d @ %p %p %p\n",*(int*)a,*(int*)b,*(int*)c, a,b,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)); + m1 = compare(userdata,a,b)<0 ? + (compare(userdata,b,c)<0 ? b : (compare(userdata,a,c)<0 ? c : a)) + : (compare(userdata,a,c)<0 ? a : (compare(userdata,b,c)<0 ? c : b)); } { char *a=mid-d, *b=mid, *c=mid+d; #ifdef DEBUG_QSORT fprintf(stderr,". %d %d %d @ %p %p %p\n",*(int*)a,*(int*)b,*(int*)c, a,b,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)); + m2 = compare(userdata,a,b)<0 ? + (compare(userdata,b,c)<0 ? b : (compare(userdata,a,c)<0 ? c : a)) + : (compare(userdata,a,c)<0 ? a : (compare(userdata,b,c)<0 ? c : b)); } { char *a=last-2*d, *b=last-d, *c=last; #ifdef DEBUG_QSORT fprintf(stderr,"> %d %d %d @ %p %p %p\n",*(int*)a,*(int*)b,*(int*)c, a,b,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)); + m3 = compare(userdata,a,b)<0 ? + (compare(userdata,b,c)<0 ? b : (compare(userdata,a,c)<0 ? c : a)) + : (compare(userdata,a,c)<0 ? a : (compare(userdata,b,c)<0 ? c : b)); } #ifdef DEBUG_QSORT fprintf(stderr,"-> %d %d %d @ %p %p %p\n",*(int*)m1,*(int*)m2,*(int*)m3, m1,m2,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)); + return compare(userdata,m1,m2)<0 ? + (compare(userdata,m2,m3)<0 ? m2 : (compare(userdata,m1,m3)<0 ? m3 : m1)) + : (compare(userdata,m1,m3)<0 ? m1 : (compare(userdata,m2,m3)<0 ? m3 : m2)); } /* ---------------------------------------------------------------------- */ -static void qsort_nonaligned(void *base, size_t nmemb, size_t size, - int (*compare)(const void *, const void *)) { +static void qsort_r_nonaligned(void *base, size_t nmemb, size_t size, + int (SDLCALL * compare)(void *, const void *, const void *), void *userdata) { stack_entry stack[STACK_SIZE]; int stacktop=0; @@ -433,8 +424,8 @@ static void qsort_nonaligned(void *base, size_t nmemb, size_t size, free(pivot); } -static void qsort_aligned(void *base, size_t nmemb, size_t size, - int (*compare)(const void *, const void *)) { +static void qsort_r_aligned(void *base, size_t nmemb, size_t size, + int (SDLCALL * compare)(void *,const void *, const void *), void *userdata) { stack_entry stack[STACK_SIZE]; int stacktop=0; @@ -464,8 +455,8 @@ static void qsort_aligned(void *base, size_t nmemb, size_t size, free(pivot); } -static void qsort_words(void *base, size_t nmemb, - int (*compare)(const void *, const void *)) { +static void qsort_r_words(void *base, size_t nmemb, + int (SDLCALL * compare)(void *,const void *, const void *), void *userdata) { stack_entry stack[STACK_SIZE]; int stacktop=0; @@ -507,7 +498,7 @@ fprintf(stderr, "after partitioning first=#%lu last=#%lu\n", (first-(char*)base) /* 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) { + for (;compare(userdata,pl,pivot)>0;pr=pl,--pl) { *pr=*pl; } if (pr!=(int*)first) *pr=*(int*)pivot; } @@ -516,28 +507,34 @@ fprintf(stderr, "after partitioning first=#%lu last=#%lu\n", (first-(char*)base) /* ---------------------------------------------------------------------- */ -extern void qsortG(void *base, size_t nmemb, size_t size, - int (*compare)(const void *, const void *)) { +extern void SDL_qsort_r(void *base, size_t nmemb, size_t size, + int (SDLCALL * compare)(void *, const void *, const void *), void *userdata) { if (nmemb<=1) return; if (((size_t)base|size)&(WORD_BYTES-1)) - qsort_nonaligned(base,nmemb,size,compare); + qsort_r_nonaligned(base,nmemb,size,compare,userdata); else if (size!=WORD_BYTES) - qsort_aligned(base,nmemb,size,compare); + qsort_r_aligned(base,nmemb,size,compare,userdata); else - qsort_words(base,nmemb,compare); + qsort_r_words(base,nmemb,compare,userdata); } -#endif /* HAVE_QSORT */ - -void *SDL_bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compare)(const void *, const void *)) +static int SDLCALL qsort_non_r_bridge(void *userdata, const void *a, const void *b) +{ + int (SDLCALL *compare)(const void *, const void *) = (int (SDLCALL *)(const void *, const void *)) userdata; + return compare(a, b); +} + +void SDL_qsort(void *base, size_t nmemb, size_t size, int (SDLCALL *compare) (const void *, const void *)) +{ + SDL_qsort_r(base, nmemb, size, qsort_non_r_bridge, compare); +} + +// Don't use the C runtime for such a simple function, since we want to allow SDLCALL callbacks and userdata. +// SDL's replacement: Taken from the Public Domain C Library (PDCLib): +// Permission is granted to use, modify, and / or redistribute at will. +void *SDL_bsearch_r(const void *key, const void *base, size_t nmemb, size_t size, int (SDLCALL *compare)(void *, const void *, const void *), void *userdata) { -#ifdef HAVE_BSEARCH - return bsearch(key, base, nmemb, size, compare); -#else -/* SDL's replacement: Taken from the Public Domain C Library (PDCLib): - Permission is granted to use, modify, and / or redistribute at will. -*/ const void *pivot; size_t corr; int rc; @@ -547,7 +544,7 @@ void *SDL_bsearch(const void *key, const void *base, size_t nmemb, size_t size, corr = nmemb % 2; nmemb /= 2; pivot = (const char *)base + (nmemb * size); - rc = compare(key, pivot); + rc = compare(userdata, key, pivot); if (rc > 0) { base = (const char *)pivot + size; @@ -559,5 +556,11 @@ void *SDL_bsearch(const void *key, const void *base, size_t nmemb, size_t size, } return NULL; -#endif /* HAVE_BSEARCH */ } + +void *SDL_bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (SDLCALL *compare)(const void *, const void *)) +{ + // qsort_non_r_bridge just happens to match calling conventions, so reuse it. + return SDL_bsearch_r(key, base, nmemb, size, qsort_non_r_bridge, compare); +} + diff --git a/test/testqsort.c b/test/testqsort.c index 08a518851..5fa5db7b8 100644 --- a/test/testqsort.c +++ b/test/testqsort.c @@ -14,6 +14,8 @@ #include #include +static int a_global_var = 77; + static int SDLCALL num_compare(const void *_a, const void *_b) { @@ -22,20 +24,36 @@ num_compare(const void *_a, const void *_b) return (a < b) ? -1 : ((a > b) ? 1 : 0); } +static int SDLCALL +num_compare_r(void *userdata, const void *a, const void *b) +{ + if (userdata != &a_global_var) { + SDL_Log("Uhoh, invalid userdata during qsort!"); + } + return num_compare(a, b); +} + static void test_sort(const char *desc, int *nums, const int arraylen) { + static int nums_copy[1024 * 100]; int i; int prev; + SDL_assert(SDL_arraysize(nums_copy) >= arraylen); + SDL_Log("test: %s arraylen=%d", desc, arraylen); + SDL_memcpy(nums_copy, nums, arraylen * sizeof (*nums)); + SDL_qsort(nums, arraylen, sizeof(nums[0]), num_compare); + SDL_qsort_r(nums_copy, arraylen, sizeof(nums[0]), num_compare_r, &a_global_var); prev = nums[0]; for (i = 1; i < arraylen; i++) { const int val = nums[i]; - if (val < prev) { + const int val2 = nums_copy[i]; + if ((val < prev) || (val != val2)) { SDL_Log("sort is broken!"); return; }