From 31d2b678f7e25725b4b7c995ca2dd129f563ae06 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Wed, 10 Jul 2013 18:31:17 -0700 Subject: [PATCH] Added release/acquire memory barriers to the atomic API * Added a destructor to clean up TLS memory at thread shutdown * Refactored the TLS code to have platform independent code and a small platform dependent core with a fallback to generic code if platform dependent functions fail. * Fixed recursion issues with SDL_GetErrBuf() --- include/SDL_atomic.h | 27 +++++ include/SDL_thread.h | 7 +- src/thread/SDL_systhread.h | 6 + src/thread/SDL_thread.c | 202 ++++++++++++++++++++++++++++++-- src/thread/SDL_thread_c.h | 24 ++++ src/thread/beos/SDL_systls.c | 80 ++++--------- src/thread/generic/SDL_systls.c | 137 +--------------------- src/thread/pthread/SDL_systls.c | 84 ++++--------- src/thread/windows/SDL_systls.c | 82 ++++--------- test/testthread.c | 4 +- 10 files changed, 334 insertions(+), 319 deletions(-) diff --git a/include/SDL_atomic.h b/include/SDL_atomic.h index 78498ff9d..65c3b6dfe 100644 --- a/include/SDL_atomic.h +++ b/include/SDL_atomic.h @@ -45,6 +45,7 @@ * * There's also lots of good information here: * http://www.1024cores.net/home/lock-free-algorithms + * http://preshing.com/ * * These operations may or may not actually be implemented using * processor specific atomic operations. When possible they are @@ -135,6 +136,32 @@ void _ReadWriteBarrier(void); { SDL_SpinLock _tmp = 0; SDL_AtomicLock(&_tmp); SDL_AtomicUnlock(&_tmp); } #endif +/** + * Memory barriers are designed to prevent reads and writes from being + * reordered by the compiler and being seen out of order on multi-core CPUs. + * + * A typical pattern would be for thread A to write some data and a flag, + * and for thread B to read the flag and get the data. In this case you + * would insert a release barrier between writing the data and the flag, + * guaranteeing that the data write completes no later than the flag is + * written, and you would insert an acquire barrier between reading the + * flag and reading the data, to ensure that all the reads associated + * with the flag have completed. + * + * In this pattern you should always see a release barrier paired with + * an acquire barrier and you should gate the data reads/writes with a + * single flag variable. + * + * For more information on these semantics, take a look at the blog post: + * http://preshing.com/20120913/acquire-and-release-semantics + */ +/* FIXME: This is correct for x86 and x64 but not other CPUs + For PPC we need the lwsync instruction, and on ARM some variant of dmb + */ +#define SDL_MemoryBarrierRelease() SDL_CompilerBarrier() +#define SDL_MemoryBarrierAcquire() SDL_CompilerBarrier() + + /* Platform specific optimized versions of the atomic functions, * you can disable these by defining SDL_DISABLE_ATOMIC_INLINE */ diff --git a/include/SDL_thread.h b/include/SDL_thread.h index b1ebe22da..8bc3e7f8d 100644 --- a/include/SDL_thread.h +++ b/include/SDL_thread.h @@ -48,8 +48,8 @@ typedef struct SDL_Thread SDL_Thread; /* The SDL thread ID */ typedef unsigned long SDL_threadID; -/* Thread local storage ID */ -typedef int SDL_TLSID; +/* Thread local storage ID, 0 is the invalid ID */ +typedef unsigned SDL_TLSID; /* The SDL thread priority * @@ -219,13 +219,14 @@ extern DECLSPEC void * SDLCALL SDL_TLSGet(SDL_TLSID id); * * \param id The thread local storage ID * \param value The value to associate with the ID for the current thread + * \param destructor A function called when the thread exits, to free the value. * * \return 0 on success, -1 on error * * \sa SDL_TLSCreate() * \sa SDL_TLSGet() */ -extern DECLSPEC int SDLCALL SDL_TLSSet(SDL_TLSID id, const void *value); +extern DECLSPEC int SDLCALL SDL_TLSSet(SDL_TLSID id, const void *value, void (*destructor)(void*)); /* Ends C function definitions when using C++ */ diff --git a/src/thread/SDL_systhread.h b/src/thread/SDL_systhread.h index 694ea7f27..738ea27b0 100644 --- a/src/thread/SDL_systhread.h +++ b/src/thread/SDL_systhread.h @@ -50,6 +50,12 @@ extern int SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority); */ extern void SDL_SYS_WaitThread(SDL_Thread * thread); +/* Get the thread local storage for this thread */ +extern SDL_TLSData *SDL_SYS_GetTLSData(); + +/* Set the thread local storage for this thread */ +extern int SDL_SYS_SetTLSData(SDL_TLSData *data); + #endif /* _SDL_systhread_h */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/thread/SDL_thread.c b/src/thread/SDL_thread.c index 0cfd90f31..e0bac7e77 100644 --- a/src/thread/SDL_thread.c +++ b/src/thread/SDL_thread.c @@ -28,38 +28,219 @@ #include "../SDL_error_c.h" +SDL_TLSID +SDL_TLSCreate() +{ + static SDL_atomic_t SDL_tls_id; + return SDL_AtomicIncRef(&SDL_tls_id)+1; +} + +void * +SDL_TLSGet(SDL_TLSID id) +{ + SDL_TLSData *storage; + + storage = SDL_SYS_GetTLSData(); + if (!storage || id == 0 || id > storage->limit) { + return NULL; + } + return storage->array[id-1].data; +} + +int +SDL_TLSSet(SDL_TLSID id, const void *value, void (*destructor)(void *)) +{ + SDL_TLSData *storage; + + if (id == 0) { + return SDL_InvalidParamError("id"); + } + + storage = SDL_SYS_GetTLSData(); + if (!storage || id > storage->limit) { + int i, oldlimit, newlimit; + + oldlimit = storage ? storage->limit : 0; + newlimit = (id + TLS_ALLOC_CHUNKSIZE); + storage = (SDL_TLSData *)SDL_realloc(storage, sizeof(*storage)+(newlimit-1)*sizeof(storage->array[0])); + if (!storage) { + return SDL_OutOfMemory(); + } + storage->limit = newlimit; + for (i = oldlimit; i < newlimit; ++i) { + storage->array[i].data = NULL; + storage->array[i].destructor = NULL; + } + if (SDL_SYS_SetTLSData(storage) != 0) { + return -1; + } + } + + storage->array[id-1].data = SDL_const_cast(void*, value); + storage->array[id-1].destructor = destructor; + return 0; +} + +static void +SDL_TLSCleanup() +{ + SDL_TLSData *storage; + + storage = SDL_SYS_GetTLSData(); + if (storage) { + int i; + for (i = 0; i < storage->limit; ++i) { + if (storage->array[i].destructor) { + storage->array[i].destructor(storage->array[i].data); + } + } + SDL_SYS_SetTLSData(NULL); + SDL_free(storage); + } +} + + +/* This is a generic implementation of thread-local storage which doesn't + require additional OS support. + + It is not especially efficient and doesn't clean up thread-local storage + as threads exit. If there is a real OS that doesn't support thread-local + storage this implementation should be improved to be production quality. +*/ + +typedef struct SDL_TLSEntry { + SDL_threadID thread; + SDL_TLSData *storage; + struct SDL_TLSEntry *next; +} SDL_TLSEntry; + +static SDL_mutex *SDL_generic_TLS_mutex; +static SDL_TLSEntry *SDL_generic_TLS; + + +SDL_TLSData * +SDL_Generic_GetTLSData() +{ + SDL_threadID thread = SDL_ThreadID(); + SDL_TLSEntry *entry; + SDL_TLSData *storage = NULL; + + if (!SDL_generic_TLS_mutex) { + static SDL_SpinLock tls_lock; + SDL_AtomicLock(&tls_lock); + if (!SDL_generic_TLS_mutex) { + SDL_mutex *mutex = SDL_CreateMutex(); + SDL_MemoryBarrierRelease(); + SDL_generic_TLS_mutex = mutex; + if (!SDL_generic_TLS_mutex) { + SDL_AtomicUnlock(&tls_lock); + return NULL; + } + } + SDL_AtomicUnlock(&tls_lock); + } + + SDL_MemoryBarrierAcquire(); + SDL_LockMutex(SDL_generic_TLS_mutex); + for (entry = SDL_generic_TLS; entry; entry = entry->next) { + if (entry->thread == thread) { + storage = entry->storage; + break; + } + } + SDL_UnlockMutex(SDL_generic_TLS_mutex); + + return storage; +} + +int +SDL_Generic_SetTLSData(SDL_TLSData *storage) +{ + SDL_threadID thread = SDL_ThreadID(); + SDL_TLSEntry *prev, *entry; + + /* SDL_Generic_GetTLSData() is always called first, so we can assume SDL_generic_TLS_mutex */ + SDL_LockMutex(SDL_generic_TLS_mutex); + prev = NULL; + for (entry = SDL_generic_TLS; entry; entry = entry->next) { + if (entry->thread == thread) { + if (storage) { + entry->storage = storage; + } else { + if (prev) { + prev->next = entry->next; + } else { + SDL_generic_TLS = entry->next; + } + SDL_free(entry); + } + break; + } + prev = entry; + } + if (!entry) { + entry = (SDL_TLSEntry *)SDL_malloc(sizeof(*entry)); + if (entry) { + entry->thread = thread; + entry->storage = storage; + entry->next = SDL_generic_TLS; + SDL_generic_TLS = entry; + } + } + SDL_UnlockMutex(SDL_generic_TLS_mutex); + + if (!entry) { + return SDL_OutOfMemory(); + } + return 0; +} + /* Routine to get the thread-specific error variable */ SDL_error * SDL_GetErrBuf(void) { - static SDL_SpinLock spinlock; + static SDL_SpinLock tls_lock; static SDL_bool tls_being_created; static SDL_TLSID tls_errbuf; static SDL_error SDL_global_errbuf; + const SDL_error *ALLOCATION_IN_PROGRESS = (SDL_error *)-1; SDL_error *errbuf; + /* tls_being_created is there simply to prevent recursion if SDL_TLSCreate() fails. + It also means it's possible for another thread to also use SDL_global_errbuf, + but that's very unlikely and hopefully won't cause issues. + */ if (!tls_errbuf && !tls_being_created) { - SDL_AtomicLock(&spinlock); + SDL_AtomicLock(&tls_lock); if (!tls_errbuf) { - /* SDL_TLSCreate() could fail and call SDL_SetError() */ + SDL_TLSID slot; tls_being_created = SDL_TRUE; - tls_errbuf = SDL_TLSCreate(); + slot = SDL_TLSCreate(); tls_being_created = SDL_FALSE; + SDL_MemoryBarrierRelease(); + tls_errbuf = slot; } - SDL_AtomicUnlock(&spinlock); + SDL_AtomicUnlock(&tls_lock); } if (!tls_errbuf) { return &SDL_global_errbuf; } - errbuf = SDL_TLSGet(tls_errbuf); + SDL_MemoryBarrierAcquire(); + errbuf = (SDL_error *)SDL_TLSGet(tls_errbuf); + if (errbuf == ALLOCATION_IN_PROGRESS) { + return &SDL_global_errbuf; + } if (!errbuf) { + /* Mark that we're in the middle of allocating our buffer */ + SDL_TLSSet(tls_errbuf, ALLOCATION_IN_PROGRESS, NULL); errbuf = (SDL_error *)SDL_malloc(sizeof(*errbuf)); if (!errbuf) { + SDL_TLSSet(tls_errbuf, NULL, NULL); return &SDL_global_errbuf; } SDL_zerop(errbuf); - SDL_TLSSet(tls_errbuf, errbuf); + SDL_TLSSet(tls_errbuf, errbuf, SDL_free); } return errbuf; } @@ -82,9 +263,7 @@ SDL_RunThread(void *data) void *userdata = args->data; int *statusloc = &args->info->status; - /* Perform any system-dependent setup - - this function cannot fail, and cannot use SDL_SetError() - */ + /* Perform any system-dependent setup - this function may not fail */ SDL_SYS_SetupThread(args->info->name); /* Get the thread id */ @@ -95,6 +274,9 @@ SDL_RunThread(void *data) /* Run the function */ *statusloc = userfunc(userdata); + + /* Clean up thread-local storage */ + SDL_TLSCleanup(); } #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD diff --git a/src/thread/SDL_thread_c.h b/src/thread/SDL_thread_c.h index 64fb77894..da3a165fb 100644 --- a/src/thread/SDL_thread_c.h +++ b/src/thread/SDL_thread_c.h @@ -56,6 +56,30 @@ struct SDL_Thread /* This is the function called to run a thread */ extern void SDL_RunThread(void *data); +/* This is the system-independent thread local storage structure */ +typedef struct { + int limit; + struct { + void *data; + void (*destructor)(void*); + } array[1]; +} SDL_TLSData; + +/* This is how many TLS entries we allocate at once */ +#define TLS_ALLOC_CHUNKSIZE 4 + +/* Get cross-platform, slow, thread local storage for this thread. + This is only intended as a fallback if getting real thread-local + storage fails or isn't supported on this platform. + */ +extern SDL_TLSData *SDL_Generic_GetTLSData(); + +/* Set cross-platform, slow, thread local storage for this thread. + This is only intended as a fallback if getting real thread-local + storage fails or isn't supported on this platform. + */ +extern int SDL_Generic_SetTLSData(SDL_TLSData *data); + #endif /* _SDL_thread_c_h */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/thread/beos/SDL_systls.c b/src/thread/beos/SDL_systls.c index f71a7e9b7..8e941289a 100644 --- a/src/thread/beos/SDL_systls.c +++ b/src/thread/beos/SDL_systls.c @@ -21,81 +21,47 @@ #include "SDL_config.h" #include "SDL_thread.h" +#include "../SDL_thread_c.h" #if SDL_THREAD_BEOS #include -#define TLS_ALLOC_CHUNKSIZE 8 - -typedef struct { - int limit; - void *data[1]; -} SDL_TLSData; - -static SDL_SpinLock tls_lock; static int32 thread_local_storage = B_NO_MEMORY; -static SDL_atomic_t tls_id; +static SDL_bool generic_local_storage = SDL_FALSE; - -SDL_TLSID -SDL_TLSCreate() +SDL_TLSData * +SDL_SYS_GetTLSData() { - if (thread_local_storage == B_NO_MEMORY) { - SDL_AtomicLock(&tls_lock); - if (thread_local_storage == B_NO_MEMORY) { - thread_local_storage = tls_allocate(); - if (thread_local_storage == B_NO_MEMORY) { - SDL_SetError("tls_allocate() failed"); - SDL_AtomicUnlock(&tls_lock); - return 0; + if (thread_local_storage == B_NO_MEMORY && !generic_local_storage) { + static SDL_SpinLock lock; + SDL_AtomicLock(&lock); + if (thread_local_storage == B_NO_MEMORY && !generic_local_storage) { + int32 storage = tls_allocate(); + if (storage != B_NO_MEMORY) { + SDL_MemoryBarrierRelease(); + thread_local_storage = storage; + } else { + generic_local_storage = SDL_TRUE; } } - SDL_AtomicUnlock(&tls_lock); + SDL_AtomicUnlock(&lock); } - return SDL_AtomicIncRef(&tls_id)+1; -} - -void * -SDL_TLSGet(SDL_TLSID id) -{ - SDL_TLSData *data; - - data = (SDL_TLSData *)tls_get(thread_local_storage); - if (!data || id <= 0 || id > data->limit) { - return NULL; + if (generic_local_storage) { + return SDL_Generic_GetTLSData(); } - return data->data[id-1]; + SDL_MemoryBarrierAcquire(); + return (SDL_TLSData *)tls_get(thread_local_storage); } int -SDL_TLSSet(SDL_TLSID id, const void *value) +SDL_SYS_SetTLSData(SDL_TLSData *data) { - SDL_TLSData *data; - - if (thread_local_storage == B_NO_MEMORY || id <= 0) { - return SDL_InvalidParamError(id); + if (generic_local_storage) { + return SDL_Generic_SetTLSData(data); } - - data = (SDL_TLSData *)tls_get(thread_local_storage); - if (!data || id > data->limit) { - int i, oldlimit, newlimit; - - oldlimit = data ? data->limit : 0; - newlimit = (id + TLS_ALLOC_CHUNKSIZE); - data = (SDL_TLSData *)SDL_realloc(data, sizeof(*data)+(newlimit-1)*sizeof(void*)); - if (!data) { - return SDL_OutOfMemory(); - } - data->limit = newlimit; - for (i = oldlimit; i < newlimit; ++i) { - data->data[i] = NULL; - } - tls_set(thread_local_storage, data); - } - - data->data[id-1] = SDL_const_cast(void*, value); + tls_set(thread_local_storage, data); return 0; } diff --git a/src/thread/generic/SDL_systls.c b/src/thread/generic/SDL_systls.c index febc63e45..bdb83dbf9 100644 --- a/src/thread/generic/SDL_systls.c +++ b/src/thread/generic/SDL_systls.c @@ -20,144 +20,19 @@ */ #include "SDL_config.h" -#include "SDL_thread.h" +#include "../SDL_thread_c.h" -/* This is a generic implementation of thread-local storage which doesn't - require additional OS support. - It is not especially efficient and doesn't clean up thread-local storage - as threads exit. If there is a real OS that doesn't support thread-local - storage this implementation should be improved to be production quality. -*/ - -#define TLS_ALLOC_CHUNKSIZE 8 - -typedef struct { - int limit; - void *data[1]; -} SDL_TLSData; - -typedef struct SDL_TLSEntry { - SDL_threadID thread; - SDL_TLSData *data; - struct SDL_TLSEntry *next; -} SDL_TLSEntry; - -static SDL_SpinLock tls_lock; -static SDL_mutex *tls_mutex; -static SDL_TLSEntry *thread_local_storage; -static SDL_atomic_t tls_id; - - -static SDL_TLSData *GetTLSData() +SDL_TLSData * +SDL_SYS_GetTLSData() { - SDL_threadID thread = SDL_ThreadID(); - SDL_TLSEntry *entry; - SDL_TLSData *data = NULL; - - if (!tls_mutex) { - SDL_AtomicLock(&tls_lock); - if (!tls_mutex) { - tls_mutex = SDL_CreateMutex(); - if (!tls_mutex) { - SDL_AtomicUnlock(&tls_lock); - return NULL; - } - } - SDL_AtomicUnlock(&tls_lock); - } - - SDL_LockMutex(tls_mutex); - for (entry = thread_local_storage; entry; entry = entry->next) { - if (entry->thread == thread) { - data = entry->data; - break; - } - } - SDL_UnlockMutex(tls_mutex); - - return data; -} - -static int SetTLSData(SDL_TLSData *data) -{ - SDL_threadID thread = SDL_ThreadID(); - SDL_TLSEntry *entry; - - /* GetTLSData() is always called first, so we can assume tls_mutex */ - SDL_LockMutex(tls_mutex); - for (entry = thread_local_storage; entry; entry = entry->next) { - if (entry->thread == thread) { - entry->data = data; - break; - } - } - if (!entry) { - entry = (SDL_TLSEntry *)SDL_malloc(sizeof(*entry)); - if (entry) { - entry->thread = thread; - entry->data = data; - entry->next = thread_local_storage; - thread_local_storage = entry; - } - } - SDL_UnlockMutex(tls_mutex); - - if (!entry) { - return SDL_OutOfMemory(); - } - return 0; -} - - -SDL_TLSID -SDL_TLSCreate() -{ - return SDL_AtomicIncRef(&tls_id)+1; -} - -void * -SDL_TLSGet(SDL_TLSID id) -{ - SDL_TLSData *data; - - data = GetTLSData(); - if (!data || id <= 0 || id > data->limit) { - return NULL; - } - return data->data[id-1]; + return SDL_Generic_GetTLSData(); } int -SDL_TLSSet(SDL_TLSID id, const void *value) +SDL_SYS_SetTLSData(SDL_TLSData *data) { - SDL_TLSData *data; - - if (id <= 0) { - return SDL_InvalidParamError(id); - } - - data = GetTLSData(); - if (!data || id > data->limit) { - int i, oldlimit, newlimit; - - oldlimit = data ? data->limit : 0; - newlimit = (id + TLS_ALLOC_CHUNKSIZE); - data = (SDL_TLSData *)SDL_realloc(data, sizeof(*data)+(newlimit-1)*sizeof(void*)); - if (!data) { - return SDL_OutOfMemory(); - } - data->limit = newlimit; - for (i = oldlimit; i < newlimit; ++i) { - data->data[i] = NULL; - } - if (SetTLSData(data) != 0) { - return -1; - } - } - - data->data[id-1] = SDL_const_cast(void*, value); - return 0; + return SDL_Generic_SetTLSData(data); } /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/thread/pthread/SDL_systls.c b/src/thread/pthread/SDL_systls.c index 5cbaa1b77..9ef6e51ac 100644 --- a/src/thread/pthread/SDL_systls.c +++ b/src/thread/pthread/SDL_systls.c @@ -18,83 +18,51 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ - #include "SDL_config.h" #include "SDL_thread.h" +#include "../SDL_thread_c.h" #include -#define TLS_ALLOC_CHUNKSIZE 8 - -typedef struct { - int limit; - void *data[1]; -} SDL_TLSData; +#define INVALID_PTHREAD_KEY ((pthread_key_t)-1) -static SDL_SpinLock tls_lock; -static pthread_key_t thread_local_storage; -static SDL_atomic_t tls_id; +static pthread_key_t thread_local_storage = INVALID_PTHREAD_KEY; +static SDL_bool generic_local_storage = SDL_FALSE; - -SDL_TLSID -SDL_TLSCreate() +SDL_TLSData * +SDL_SYS_GetTLSData() { - if (!thread_local_storage) { - SDL_AtomicLock(&tls_lock); - if (!thread_local_storage) { - if (pthread_key_create(&thread_local_storage, NULL) != 0) { - SDL_SetError("pthread_key_create() failed"); - SDL_AtomicUnlock(&tls_lock); - return 0; + if (thread_local_storage == INVALID_PTHREAD_KEY && !generic_local_storage) { + static SDL_SpinLock lock; + SDL_AtomicLock(&lock); + if (thread_local_storage == INVALID_PTHREAD_KEY && !generic_local_storage) { + pthread_key_t storage; + if (pthread_key_create(&storage, NULL) == 0) { + SDL_MemoryBarrierRelease(); + thread_local_storage = storage; + } else { + generic_local_storage = SDL_TRUE; } } - SDL_AtomicUnlock(&tls_lock); + SDL_AtomicUnlock(&lock); } - return SDL_AtomicIncRef(&tls_id)+1; -} - -void * -SDL_TLSGet(SDL_TLSID id) -{ - SDL_TLSData *data; - - data = (SDL_TLSData *)pthread_getspecific(thread_local_storage); - if (!data || id <= 0 || id > data->limit) { - return NULL; + if (generic_local_storage) { + return SDL_Generic_GetTLSData(); } - return data->data[id-1]; + SDL_MemoryBarrierAcquire(); + return (SDL_TLSData *)pthread_getspecific(thread_local_storage); } int -SDL_TLSSet(SDL_TLSID id, const void *value) +SDL_SYS_SetTLSData(SDL_TLSData *data) { - SDL_TLSData *data; - - if (!thread_local_storage || id <= 0) { - return SDL_InvalidParamError(id); + if (generic_local_storage) { + return SDL_Generic_SetTLSData(data); } - - data = (SDL_TLSData *)pthread_getspecific(thread_local_storage); - if (!data || id > data->limit) { - int i, oldlimit, newlimit; - - oldlimit = data ? data->limit : 0; - newlimit = (id + TLS_ALLOC_CHUNKSIZE); - data = (SDL_TLSData *)SDL_realloc(data, sizeof(*data)+(newlimit-1)*sizeof(void*)); - if (!data) { - return SDL_OutOfMemory(); - } - data->limit = newlimit; - for (i = oldlimit; i < newlimit; ++i) { - data->data[i] = NULL; - } - if (pthread_setspecific(thread_local_storage, data) != 0) { - return SDL_SetError("pthread_setspecific() failed"); - } + if (pthread_setspecific(thread_local_storage, data) != 0) { + return SDL_SetError("pthread_setspecific() failed"); } - - data->data[id-1] = SDL_const_cast(void*, value); return 0; } diff --git a/src/thread/windows/SDL_systls.c b/src/thread/windows/SDL_systls.c index 2b7b39488..0ece77dd0 100644 --- a/src/thread/windows/SDL_systls.c +++ b/src/thread/windows/SDL_systls.c @@ -21,83 +21,49 @@ #include "SDL_config.h" #include "SDL_thread.h" +#include "../SDL_thread_c.h" #if SDL_THREAD_WINDOWS #include "../../core/windows/SDL_windows.h" -#define TLS_ALLOC_CHUNKSIZE 8 - -typedef struct { - int limit; - void *data[1]; -} SDL_TLSData; - -static SDL_SpinLock tls_lock; static DWORD thread_local_storage = TLS_OUT_OF_INDEXES; -static SDL_atomic_t tls_id; +static SDL_bool generic_local_storage = SDL_FALSE; - -SDL_TLSID -SDL_TLSCreate() +SDL_TLSData * +SDL_SYS_GetTLSData() { - if (thread_local_storage == TLS_OUT_OF_INDEXES) { - SDL_AtomicLock(&tls_lock); - if (thread_local_storage == TLS_OUT_OF_INDEXES) { - thread_local_storage = TlsAlloc(); - if (thread_local_storage == TLS_OUT_OF_INDEXES) { - SDL_SetError("TlsAlloc() failed"); - SDL_AtomicUnlock(&tls_lock); - return 0; + if (thread_local_storage == TLS_OUT_OF_INDEXES && !generic_local_storage) { + static SDL_SpinLock lock; + SDL_AtomicLock(&lock); + if (thread_local_storage == TLS_OUT_OF_INDEXES && !generic_local_storage) { + DWORD storage = TlsAlloc(); + if (storage != TLS_OUT_OF_INDEXES) { + SDL_MemoryBarrierRelease(); + thread_local_storage = storage; + } else { + generic_local_storage = SDL_TRUE; } } - SDL_AtomicUnlock(&tls_lock); + SDL_AtomicUnlock(&lock); } - return SDL_AtomicIncRef(&tls_id)+1; -} - -void * -SDL_TLSGet(SDL_TLSID id) -{ - SDL_TLSData *data; - - data = (SDL_TLSData *)TlsGetValue(thread_local_storage); - if (!data || id <= 0 || id > data->limit) { - return NULL; + if (generic_local_storage) { + return SDL_Generic_GetTLSData(); } - return data->data[id-1]; + SDL_MemoryBarrierAcquire(); + return (SDL_TLSData *)TlsGetValue(thread_local_storage); } int -SDL_TLSSet(SDL_TLSID id, const void *value) +SDL_SYS_SetTLSData(SDL_TLSData *data) { - SDL_TLSData *data; - - if (thread_local_storage == TLS_OUT_OF_INDEXES || id <= 0) { - return SDL_InvalidParamError(id); + if (generic_local_storage) { + return SDL_Generic_SetTLSData(data); } - - data = (SDL_TLSData *)TlsGetValue(thread_local_storage); - if (!data || id > data->limit) { - int i, oldlimit, newlimit; - - oldlimit = data ? data->limit : 0; - newlimit = (id + TLS_ALLOC_CHUNKSIZE); - data = (SDL_TLSData *)SDL_realloc(data, sizeof(*data)+(newlimit-1)*sizeof(void*)); - if (!data) { - return SDL_OutOfMemory(); - } - data->limit = newlimit; - for (i = oldlimit; i < newlimit; ++i) { - data->data[i] = NULL; - } - if (!TlsSetValue(thread_local_storage, data)) { - return SDL_SetError("TlsSetValue() failed"); - } + if (!TlsSetValue(thread_local_storage, data)) { + return SDL_SetError("TlsSetValue() failed"); } - - data->data[id-1] = SDL_const_cast(void*, value); return 0; } diff --git a/test/testthread.c b/test/testthread.c index b8f18dc06..47c1b57f0 100644 --- a/test/testthread.c +++ b/test/testthread.c @@ -33,7 +33,7 @@ quit(int rc) int SDLCALL ThreadFunc(void *data) { - SDL_TLSSet(tls, "baby thread"); + SDL_TLSSet(tls, "baby thread", NULL); printf("Started thread %s: My thread id is %lu, thread data = %s\n", (char *) data, SDL_ThreadID(), (const char *)SDL_TLSGet(tls)); while (alive) { @@ -66,7 +66,7 @@ main(int argc, char *argv[]) tls = SDL_TLSCreate(); SDL_assert(tls); - SDL_TLSSet(tls, "main thread"); + SDL_TLSSet(tls, "main thread", NULL); printf("Main thread data initially: %s\n", (const char *)SDL_TLSGet(tls)); alive = 1;