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()
This commit is contained in:
Sam Lantinga 2013-07-10 18:31:17 -07:00
parent 086ecc9949
commit 557bbf3fe6
10 changed files with 334 additions and 319 deletions

View file

@ -45,6 +45,7 @@
* *
* There's also lots of good information here: * There's also lots of good information here:
* http://www.1024cores.net/home/lock-free-algorithms * http://www.1024cores.net/home/lock-free-algorithms
* http://preshing.com/
* *
* These operations may or may not actually be implemented using * These operations may or may not actually be implemented using
* processor specific atomic operations. When possible they are * 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); } { SDL_SpinLock _tmp = 0; SDL_AtomicLock(&_tmp); SDL_AtomicUnlock(&_tmp); }
#endif #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, /* Platform specific optimized versions of the atomic functions,
* you can disable these by defining SDL_DISABLE_ATOMIC_INLINE * you can disable these by defining SDL_DISABLE_ATOMIC_INLINE
*/ */

View file

@ -48,8 +48,8 @@ typedef struct SDL_Thread SDL_Thread;
/* The SDL thread ID */ /* The SDL thread ID */
typedef unsigned long SDL_threadID; typedef unsigned long SDL_threadID;
/* Thread local storage ID */ /* Thread local storage ID, 0 is the invalid ID */
typedef int SDL_TLSID; typedef unsigned SDL_TLSID;
/* The SDL thread priority /* 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 id The thread local storage ID
* \param value The value to associate with the ID for the current thread * \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 * \return 0 on success, -1 on error
* *
* \sa SDL_TLSCreate() * \sa SDL_TLSCreate()
* \sa SDL_TLSGet() * \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++ */ /* Ends C function definitions when using C++ */

View file

@ -50,6 +50,12 @@ extern int SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority);
*/ */
extern void SDL_SYS_WaitThread(SDL_Thread * thread); 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 */ #endif /* _SDL_systhread_h */
/* vi: set ts=4 sw=4 expandtab: */ /* vi: set ts=4 sw=4 expandtab: */

View file

@ -28,38 +28,219 @@
#include "../SDL_error_c.h" #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 */ /* Routine to get the thread-specific error variable */
SDL_error * SDL_error *
SDL_GetErrBuf(void) SDL_GetErrBuf(void)
{ {
static SDL_SpinLock spinlock; static SDL_SpinLock tls_lock;
static SDL_bool tls_being_created; static SDL_bool tls_being_created;
static SDL_TLSID tls_errbuf; static SDL_TLSID tls_errbuf;
static SDL_error SDL_global_errbuf; static SDL_error SDL_global_errbuf;
const SDL_error *ALLOCATION_IN_PROGRESS = (SDL_error *)-1;
SDL_error *errbuf; 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) { if (!tls_errbuf && !tls_being_created) {
SDL_AtomicLock(&spinlock); SDL_AtomicLock(&tls_lock);
if (!tls_errbuf) { if (!tls_errbuf) {
/* SDL_TLSCreate() could fail and call SDL_SetError() */ SDL_TLSID slot;
tls_being_created = SDL_TRUE; tls_being_created = SDL_TRUE;
tls_errbuf = SDL_TLSCreate(); slot = SDL_TLSCreate();
tls_being_created = SDL_FALSE; tls_being_created = SDL_FALSE;
SDL_MemoryBarrierRelease();
tls_errbuf = slot;
} }
SDL_AtomicUnlock(&spinlock); SDL_AtomicUnlock(&tls_lock);
} }
if (!tls_errbuf) { if (!tls_errbuf) {
return &SDL_global_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) { 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)); errbuf = (SDL_error *)SDL_malloc(sizeof(*errbuf));
if (!errbuf) { if (!errbuf) {
SDL_TLSSet(tls_errbuf, NULL, NULL);
return &SDL_global_errbuf; return &SDL_global_errbuf;
} }
SDL_zerop(errbuf); SDL_zerop(errbuf);
SDL_TLSSet(tls_errbuf, errbuf); SDL_TLSSet(tls_errbuf, errbuf, SDL_free);
} }
return errbuf; return errbuf;
} }
@ -82,9 +263,7 @@ SDL_RunThread(void *data)
void *userdata = args->data; void *userdata = args->data;
int *statusloc = &args->info->status; int *statusloc = &args->info->status;
/* Perform any system-dependent setup /* Perform any system-dependent setup - this function may not fail */
- this function cannot fail, and cannot use SDL_SetError()
*/
SDL_SYS_SetupThread(args->info->name); SDL_SYS_SetupThread(args->info->name);
/* Get the thread id */ /* Get the thread id */
@ -95,6 +274,9 @@ SDL_RunThread(void *data)
/* Run the function */ /* Run the function */
*statusloc = userfunc(userdata); *statusloc = userfunc(userdata);
/* Clean up thread-local storage */
SDL_TLSCleanup();
} }
#ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD #ifdef SDL_PASSED_BEGINTHREAD_ENDTHREAD

View file

@ -56,6 +56,30 @@ struct SDL_Thread
/* This is the function called to run a thread */ /* This is the function called to run a thread */
extern void SDL_RunThread(void *data); 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 */ #endif /* _SDL_thread_c_h */
/* vi: set ts=4 sw=4 expandtab: */ /* vi: set ts=4 sw=4 expandtab: */

View file

@ -21,81 +21,47 @@
#include "SDL_config.h" #include "SDL_config.h"
#include "SDL_thread.h" #include "SDL_thread.h"
#include "../SDL_thread_c.h"
#if SDL_THREAD_BEOS #if SDL_THREAD_BEOS
#include <support/TLS.h> #include <support/TLS.h>
#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 int32 thread_local_storage = B_NO_MEMORY;
static SDL_atomic_t tls_id; static SDL_bool generic_local_storage = SDL_FALSE;
SDL_TLSData *
SDL_TLSID SDL_SYS_GetTLSData()
SDL_TLSCreate()
{ {
if (thread_local_storage == B_NO_MEMORY) { if (thread_local_storage == B_NO_MEMORY && !generic_local_storage) {
SDL_AtomicLock(&tls_lock); static SDL_SpinLock lock;
if (thread_local_storage == B_NO_MEMORY) { SDL_AtomicLock(&lock);
thread_local_storage = tls_allocate(); if (thread_local_storage == B_NO_MEMORY && !generic_local_storage) {
if (thread_local_storage == B_NO_MEMORY) { int32 storage = tls_allocate();
SDL_SetError("tls_allocate() failed"); if (storage != B_NO_MEMORY) {
SDL_AtomicUnlock(&tls_lock); SDL_MemoryBarrierRelease();
return 0; thread_local_storage = storage;
} else {
generic_local_storage = SDL_TRUE;
} }
} }
SDL_AtomicUnlock(&tls_lock); SDL_AtomicUnlock(&lock);
} }
return SDL_AtomicIncRef(&tls_id)+1; if (generic_local_storage) {
return SDL_Generic_GetTLSData();
} }
SDL_MemoryBarrierAcquire();
void * return (SDL_TLSData *)tls_get(thread_local_storage);
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;
}
return data->data[id-1];
} }
int int
SDL_TLSSet(SDL_TLSID id, const void *value) SDL_SYS_SetTLSData(SDL_TLSData *data)
{ {
SDL_TLSData *data; if (generic_local_storage) {
return SDL_Generic_SetTLSData(data);
if (thread_local_storage == B_NO_MEMORY || id <= 0) {
return SDL_InvalidParamError(id);
}
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); tls_set(thread_local_storage, data);
}
data->data[id-1] = SDL_const_cast(void*, value);
return 0; return 0;
} }

View file

@ -20,144 +20,19 @@
*/ */
#include "SDL_config.h" #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(); return SDL_Generic_GetTLSData();
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];
} }
int int
SDL_TLSSet(SDL_TLSID id, const void *value) SDL_SYS_SetTLSData(SDL_TLSData *data)
{ {
SDL_TLSData *data; return SDL_Generic_SetTLSData(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;
} }
/* vi: set ts=4 sw=4 expandtab: */ /* vi: set ts=4 sw=4 expandtab: */

View file

@ -18,83 +18,51 @@
misrepresented as being the original software. misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution. 3. This notice may not be removed or altered from any source distribution.
*/ */
#include "SDL_config.h" #include "SDL_config.h"
#include "SDL_thread.h" #include "SDL_thread.h"
#include "../SDL_thread_c.h"
#include <pthread.h> #include <pthread.h>
#define TLS_ALLOC_CHUNKSIZE 8 #define INVALID_PTHREAD_KEY ((pthread_key_t)-1)
typedef struct { static pthread_key_t thread_local_storage = INVALID_PTHREAD_KEY;
int limit; static SDL_bool generic_local_storage = SDL_FALSE;
void *data[1];
} SDL_TLSData;
static SDL_SpinLock tls_lock; SDL_TLSData *
static pthread_key_t thread_local_storage; SDL_SYS_GetTLSData()
static SDL_atomic_t tls_id;
SDL_TLSID
SDL_TLSCreate()
{ {
if (!thread_local_storage) { if (thread_local_storage == INVALID_PTHREAD_KEY && !generic_local_storage) {
SDL_AtomicLock(&tls_lock); static SDL_SpinLock lock;
if (!thread_local_storage) { SDL_AtomicLock(&lock);
if (pthread_key_create(&thread_local_storage, NULL) != 0) { if (thread_local_storage == INVALID_PTHREAD_KEY && !generic_local_storage) {
SDL_SetError("pthread_key_create() failed"); pthread_key_t storage;
SDL_AtomicUnlock(&tls_lock); if (pthread_key_create(&storage, NULL) == 0) {
return 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; if (generic_local_storage) {
return SDL_Generic_GetTLSData();
} }
SDL_MemoryBarrierAcquire();
void * return (SDL_TLSData *)pthread_getspecific(thread_local_storage);
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;
}
return data->data[id-1];
} }
int int
SDL_TLSSet(SDL_TLSID id, const void *value) SDL_SYS_SetTLSData(SDL_TLSData *data)
{ {
SDL_TLSData *data; if (generic_local_storage) {
return SDL_Generic_SetTLSData(data);
if (!thread_local_storage || id <= 0) {
return SDL_InvalidParamError(id);
}
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) { if (pthread_setspecific(thread_local_storage, data) != 0) {
return SDL_SetError("pthread_setspecific() failed"); return SDL_SetError("pthread_setspecific() failed");
} }
}
data->data[id-1] = SDL_const_cast(void*, value);
return 0; return 0;
} }

View file

@ -21,83 +21,49 @@
#include "SDL_config.h" #include "SDL_config.h"
#include "SDL_thread.h" #include "SDL_thread.h"
#include "../SDL_thread_c.h"
#if SDL_THREAD_WINDOWS #if SDL_THREAD_WINDOWS
#include "../../core/windows/SDL_windows.h" #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 DWORD thread_local_storage = TLS_OUT_OF_INDEXES;
static SDL_atomic_t tls_id; static SDL_bool generic_local_storage = SDL_FALSE;
SDL_TLSData *
SDL_TLSID SDL_SYS_GetTLSData()
SDL_TLSCreate()
{ {
if (thread_local_storage == TLS_OUT_OF_INDEXES) { if (thread_local_storage == TLS_OUT_OF_INDEXES && !generic_local_storage) {
SDL_AtomicLock(&tls_lock); static SDL_SpinLock lock;
if (thread_local_storage == TLS_OUT_OF_INDEXES) { SDL_AtomicLock(&lock);
thread_local_storage = TlsAlloc(); if (thread_local_storage == TLS_OUT_OF_INDEXES && !generic_local_storage) {
if (thread_local_storage == TLS_OUT_OF_INDEXES) { DWORD storage = TlsAlloc();
SDL_SetError("TlsAlloc() failed"); if (storage != TLS_OUT_OF_INDEXES) {
SDL_AtomicUnlock(&tls_lock); SDL_MemoryBarrierRelease();
return 0; thread_local_storage = storage;
} else {
generic_local_storage = SDL_TRUE;
} }
} }
SDL_AtomicUnlock(&tls_lock); SDL_AtomicUnlock(&lock);
} }
return SDL_AtomicIncRef(&tls_id)+1; if (generic_local_storage) {
return SDL_Generic_GetTLSData();
} }
SDL_MemoryBarrierAcquire();
void * return (SDL_TLSData *)TlsGetValue(thread_local_storage);
SDL_TLSGet(SDL_TLSID id)
{
SDL_TLSData *data;
data = (SDL_TLSData *)TlsGetValue(thread_local_storage);
if (!data || id <= 0 || id > data->limit) {
return NULL;
}
return data->data[id-1];
} }
int int
SDL_TLSSet(SDL_TLSID id, const void *value) SDL_SYS_SetTLSData(SDL_TLSData *data)
{ {
SDL_TLSData *data; if (generic_local_storage) {
return SDL_Generic_SetTLSData(data);
if (thread_local_storage == TLS_OUT_OF_INDEXES || id <= 0) {
return SDL_InvalidParamError(id);
}
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)) { if (!TlsSetValue(thread_local_storage, data)) {
return SDL_SetError("TlsSetValue() failed"); return SDL_SetError("TlsSetValue() failed");
} }
}
data->data[id-1] = SDL_const_cast(void*, value);
return 0; return 0;
} }

View file

@ -33,7 +33,7 @@ quit(int rc)
int SDLCALL int SDLCALL
ThreadFunc(void *data) 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", printf("Started thread %s: My thread id is %lu, thread data = %s\n",
(char *) data, SDL_ThreadID(), (const char *)SDL_TLSGet(tls)); (char *) data, SDL_ThreadID(), (const char *)SDL_TLSGet(tls));
while (alive) { while (alive) {
@ -66,7 +66,7 @@ main(int argc, char *argv[])
tls = SDL_TLSCreate(); tls = SDL_TLSCreate();
SDL_assert(tls); 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)); printf("Main thread data initially: %s\n", (const char *)SDL_TLSGet(tls));
alive = 1; alive = 1;