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:
parent
086ecc9949
commit
557bbf3fe6
10 changed files with 334 additions and 319 deletions
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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++ */
|
||||||
|
|
|
@ -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: */
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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: */
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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: */
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue