From: Owen Shepherd Date: Wed, 22 Aug 2012 22:23:05 +0000 (+0100) Subject: win32: initial pass at thread support X-Git-Url: https://pd.if.org/git/?p=pdclib;a=commitdiff_plain;h=639bad513ab9399ed9a8c588a476a10dbe6c9478 win32: initial pass at thread support --- diff --git a/platform/win32/Config.jam b/platform/win32/Config.jam index 1cd357a..538f72b 100644 --- a/platform/win32/Config.jam +++ b/platform/win32/Config.jam @@ -23,6 +23,6 @@ if $(PDCLIB_TOOLCHAIN) = "gcc" { EXIT ; } -PDCLIB_OPTIONS = nothread notime dlmalloc ; +PDCLIB_OPTIONS = notime dlmalloc ; CRT0 = [ FDirName platform win32 crt0$(SUFOBJ) ] ; \ No newline at end of file diff --git a/platform/win32/functions/_PDCLIB/_PDCLIB_runTlsCallbacks.c b/platform/win32/functions/_PDCLIB/_PDCLIB_runTlsCallbacks.c new file mode 100644 index 0000000..7dcf7a6 --- /dev/null +++ b/platform/win32/functions/_PDCLIB/_PDCLIB_runTlsCallbacks.c @@ -0,0 +1,34 @@ +#include +#include + +#ifndef REGTEST +extern PIMAGE_TLS_CALLBACK __crt_xl_start__; +#ifdef __GNUC__ +__attribute__((section(".CRT$XLZZZ"))) +#else +__declspec(allocate(".CRT$XLZZZ")) +#endif +PIMAGE_TLS_CALLBACK __crt_xl_end__ = NULL; + +/* Runs all TLS callbacks registered in the executable + */ + +void NTAPI _PDCLIB_runTlsCallbacks(void * image, DWORD reason, PVOID pv); +void NTAPI _PDCLIB_runTlsCallbacks(void * image, DWORD reason, PVOID pv) +{ + PIMAGE_TLS_CALLBACK * pcb = &__crt_xl_start__; + + while(*pcb) (*(pcb++))(image, reason, pv); +} +#endif + +#ifdef TEST +#include <_PDCLIB_test.h> + +/* Tested in tss_get.c */ +int main( void ) +{ + return TEST_RESULTS; +} + +#endif \ No newline at end of file diff --git a/platform/win32/functions/_PDCLIB/_tls_used.c b/platform/win32/functions/_PDCLIB/_tls_used.c new file mode 100644 index 0000000..a7e5a69 --- /dev/null +++ b/platform/win32/functions/_PDCLIB/_tls_used.c @@ -0,0 +1,55 @@ +#ifndef REGTEST +#include +#include +#include + +/* Win32 TLS support + * + * Components which depend upon TLS must express a dependency on the symbol + * _tls_used. This will cause said symbol to be emitted. + * + * The linker (in the case of both Microsoft's linker and Binutils, at least) + * will point the TLS directory entry in the PE header to _tls_used. + * + * NOTE: On Windows versions < NT 6.0, the TLS support _only_ works for + * the main executable and any DLLs loaded as dependencies of it + */ + +extern char __tls_start__[], __tls_end__[]; +ULONG _tls_index = TLS_OUT_OF_INDEXES; + +extern void NTAPI _PDCLIB_runTlsCallbacks(void * image, DWORD reason, PVOID pv); +static PIMAGE_TLS_CALLBACK tlsCallbacks[] = { + &_PDCLIB_runTlsCallbacks, + NULL, +}; + +#ifdef __GNUC__ +__attribute__((__section__(".rdata$T"))) +#else +__declspec(allocate(".rdata$T")) +#endif +#ifdef _WIN64 +const IMAGE_TLS_DIRECTORY64 _tls_used = { +#else +const IMAGE_TLS_DIRECTORY _tls_used = { +#endif + (uintptr_t) &__tls_start__, + (uintptr_t) &__tls_end__, + (uintptr_t) &_tls_index, // TLS index + (uintptr_t) &tlsCallbacks[0], // TLS callback array + (ULONG) 0, // Size of zero fill + (ULONG) 0 // Characteristics +}; +#endif + +#ifdef TEST +#include <_PDCLIB_test.h> + +/* Tested in tss_get.c */ +int main( void ) +{ + return TEST_RESULTS; +} + +#endif \ No newline at end of file diff --git a/platform/win32/functions/threads/call_once.c b/platform/win32/functions/threads/call_once.c new file mode 100644 index 0000000..675e000 --- /dev/null +++ b/platform/win32/functions/threads/call_once.c @@ -0,0 +1,79 @@ +#ifndef REGTEST +#include +#include + +static volatile HANDLE onceHandle = NULL; + +static void initOnceHandle( _PDCLIB_once_flag *flag ) +{ + HANDLE tOnceHandle = CreateEvent(NULL, TRUE, TRUE, NULL); + HANDLE oldVal + = InterlockedCompareExchangePointer(&flag->_Handle, tOnceHandle, NULL); + if(oldVal != NULL) + CloseHandle(tOnceHandle); +} + +void _PDCLIB_call_once(_PDCLIB_once_flag *flag, void (*func)(void)) +{ + if(!flag->_Handle) initOnceHandle(flag); + + long oldVal = InterlockedCompareExchange(&flag->_State, 1, -1); + for(;;) { + if(oldVal == 0) { + // Initialized + return; + } else if(oldVal == -1) { + // We are doing the initialization + func(); + if(InterlockedDecrement(&flag->_State) == 0) + CloseHandle(flag->_Handle); + SetEvent(flag->_Handle); + return; + } else { + // Somebody else is initializing - we are waiting + long newOldVal = InterlockedCompareExchange(&flag->_State, oldVal, + oldVal+1); + if(newOldVal == oldVal) { + // We incremented the "waiters" counter + if(WaitForSingleObject(flag->_Handle, INFINITE) != WAIT_OBJECT_0) + abort(); + if(InterlockedDecrement(&flag->_State) == 0) + CloseHandle(flag->_Handle); + return; + } else { + oldVal = newOldVal; + continue; + } + } + } +} +#endif + +#ifdef TEST +#include <_PDCLIB_test.h> + +#ifndef REGTEST +static int count = 0; +static once_flag once = ONCE_FLAG_INIT; + +static void do_once(void) +{ + count++; +} +#endif + +int main( void ) +{ +#ifndef REGTEST + TESTCASE(count == 0); + call_once(&once, do_once); + TESTCASE(count == 1); + call_once(&once, do_once); + TESTCASE(count == 1); + do_once(); + TESTCASE(count == 2); +#endif + return TEST_RESULTS; +} + +#endif diff --git a/platform/win32/functions/threads/mtx_init.c b/platform/win32/functions/threads/mtx_init.c new file mode 100644 index 0000000..19db390 --- /dev/null +++ b/platform/win32/functions/threads/mtx_init.c @@ -0,0 +1,30 @@ +#ifndef REGTEST +#include +#include + +int mtx_init(mtx_t *mtx, int type) +{ + mtx->_WaitEvHandle = CreateEvent(NULL, + /* bManualReset*/ FALSE, + /* bInitialState*/ FALSE, + /* name*/ NULL); + if(mtx->_WaitEvHandle == NULL) + return thrd_error; + + mtx->_State = -1; + mtx->_ThreadId = 0; + mtx->_NestCount = 0;; + + return thrd_success; +} +#endif + +#ifdef TEST +#include <_PDCLIB_test.h> + +int main( void ) +{ + return TEST_RESULTS; +} + +#endif \ No newline at end of file diff --git a/platform/win32/functions/threads/mtx_lock.c b/platform/win32/functions/threads/mtx_lock.c new file mode 100644 index 0000000..70af8ae --- /dev/null +++ b/platform/win32/functions/threads/mtx_lock.c @@ -0,0 +1,40 @@ +#ifndef REGTEST +#include +#include + +int mtx_lock(mtx_t *mtx) +{ + DWORD myId = GetCurrentThreadId(); + + if(mtx->_ThreadId == myId) { + mtx->_NestCount++; + return thrd_success; + } + + DWORD res = InterlockedIncrement(&mtx->_State); + if(res == 0) { + mtx->_ThreadId = myId; + return thrd_success; + } + + // If that increment didn't leave the state == 0, then we have contention + // -> block on the wait event handle + DWORD rv = WaitForSingleObject(mtx->_WaitEvHandle, INFINITE); + if(rv != WAIT_OBJECT_0) + return thrd_error; + + // We now own the mutex - so set it up for our use + mtx->_ThreadId = myId; + return thrd_success; +} +#endif + +#ifdef TEST +#include <_PDCLIB_test.h> + +int main( void ) +{ + return TEST_RESULTS; +} + +#endif \ No newline at end of file diff --git a/platform/win32/functions/threads/mtx_unlock.c b/platform/win32/functions/threads/mtx_unlock.c new file mode 100644 index 0000000..27b7aef --- /dev/null +++ b/platform/win32/functions/threads/mtx_unlock.c @@ -0,0 +1,39 @@ +#ifndef REGTEST +#include +#include + +extern void _PDCLIB_w32errno( void ); +int mtx_unlock(mtx_t *mtx) +{ + if(mtx->_NestCount) { + mtx->_NestCount--; + return thrd_success; + } + + mtx->_ThreadId = 0; + + DWORD res = InterlockedDecrement(&mtx->_State); + if(res == (DWORD) -1) { + // We reset the state to -1; success! + return thrd_success; + } + + DWORD rv = SetEvent(mtx->_WaitEvHandle); + if(rv == 0) { + _PDCLIB_w32errno(); + return thrd_error; + } + + return thrd_success; +} +#endif + +#ifdef TEST +#include <_PDCLIB_test.h> + +int main( void ) +{ + return TEST_RESULTS; +} + +#endif \ No newline at end of file diff --git a/platform/win32/functions/threads/tss_create.c b/platform/win32/functions/threads/tss_create.c new file mode 100644 index 0000000..af6da3f --- /dev/null +++ b/platform/win32/functions/threads/tss_create.c @@ -0,0 +1,73 @@ +#ifndef REGTEST +#include +#include +#include + +/* Pull in TLS support */ +extern char _tls_used[]; + +struct _PDCLIB_tss * _PDCLIB_tss_first = NULL; + +int tss_create( tss_t *key, tss_dtor_t dtor ) +{ + *key = malloc( sizeof *key ); + if( !*key ) { + return thrd_nomem; + } + + (*key)->_Key = TlsAlloc(); + if((*key)->_Key == TLS_OUT_OF_INDEXES) { + return thrd_error; + } + (*key)->_Destructor = dtor; + (*key)->_Next = _PDCLIB_tss_first; + + // TODO: make this atomic (& validate no other TLS blocks have been + // simultaneously allocated) + _PDCLIB_tss_first = *key; + + return thrd_success; +} + +static void NTAPI runTlsDestructors( void * image, DWORD reason, PVOID pv ) +{ + if( reason == DLL_THREAD_DETACH ) { + for(unsigned i = 0; i < TSS_DTOR_ITERATIONS; i++) { + struct _PDCLIB_tss * tss = _PDCLIB_tss_first; + bool destructorsRan = false; + while( tss ) { + void * val = TlsGetValue( tss->_Key ); + if( val ) { + TlsSetValue( tss->_Key, NULL ); + if( tss->_Destructor ) { + tss->_Destructor( val ); + destructorsRan = true; + } + } + + tss = tss->_Next; + } + if(!destructorsRan) break; + } + } +} + +#ifdef __GNUC__ +__attribute__((__section__(".CRT$XLC"))) +#else +__declspec(allocate(".CRT$XLC")) +#endif +PIMAGE_TLS_CALLBACK _PDCLIB_runTlsDestructors = runTlsDestructors; + +#endif + +#ifdef TEST +#include <_PDCLIB_test.h> + +/* Tested in tss_get.c */ +int main( void ) +{ + return TEST_RESULTS; +} + +#endif \ No newline at end of file diff --git a/platform/win32/functions/threads/tss_delete.c b/platform/win32/functions/threads/tss_delete.c new file mode 100644 index 0000000..57a43fd --- /dev/null +++ b/platform/win32/functions/threads/tss_delete.c @@ -0,0 +1,39 @@ +#ifndef REGTEST +#include +#include +#include + +extern struct _PDCLIB_tss * _PDCLIB_tss_first; +void tss_delete( tss_t key ) +{ + struct _PDCLIB_tss * prev = NULL; + struct _PDCLIB_tss * cur = _PDCLIB_tss_first; + while(cur) { + if(cur == key) { + if(prev) { + prev->_Next = key->_Next; + } else { + _PDCLIB_tss_first = key->_Next; + } + + TlsFree(key->_Key); + free(key); + return; + } + } + + // Not actually a TSS key + abort(); +} +#endif + +#ifdef TEST +#include <_PDCLIB_test.h> + +/* Tested in tss_get.c */ +int main( void ) +{ + return TEST_RESULTS; +} + +#endif \ No newline at end of file diff --git a/platform/win32/functions/threads/tss_get.c b/platform/win32/functions/threads/tss_get.c new file mode 100644 index 0000000..b0a4441 --- /dev/null +++ b/platform/win32/functions/threads/tss_get.c @@ -0,0 +1,33 @@ +#ifndef REGTEST +#include +#include + +void *tss_get(tss_t key) +{ + return TlsGetValue(key->_Key); +} +#endif + +#ifdef TEST +#include <_PDCLIB_test.h> + +#ifndef REGTEST +static tss_t key; +static char v; +#endif + +// Todo: make a thread and test destruction! + +int main( void ) +{ +#ifndef REGTEST + TESTCASE(tss_create(&key, NULL) == thrd_success); + TESTCASE(tss_get(key) == NULL); + TESTCASE(tss_set(key, &v) == thrd_success); + TESTCASE(tss_get(key) == &v); + tss_delete(key); +#endif + return TEST_RESULTS; +} + +#endif \ No newline at end of file diff --git a/platform/win32/functions/threads/tss_set.c b/platform/win32/functions/threads/tss_set.c new file mode 100644 index 0000000..5ab53f1 --- /dev/null +++ b/platform/win32/functions/threads/tss_set.c @@ -0,0 +1,22 @@ +#ifndef REGTEST +#include +#include + +int tss_set(tss_t key, void *val) +{ + if(TlsSetValue(key->_Key, val)) + return thrd_success; + return thrd_error; +} +#endif + +#ifdef TEST +#include <_PDCLIB_test.h> + +/* Tested in tss_get.c */ +int main( void ) +{ + return TEST_RESULTS; +} + +#endif \ No newline at end of file diff --git a/platform/win32/internals/_PDCLIB_threadconfig.h b/platform/win32/internals/_PDCLIB_threadconfig.h new file mode 100644 index 0000000..d914ae0 --- /dev/null +++ b/platform/win32/internals/_PDCLIB_threadconfig.h @@ -0,0 +1,37 @@ +#ifndef _PDCLIB_THREADCONFIG_H +#define _PDCLIB_THREADCONFIG_H +#include <_PDCLIB_aux.h> +#include <_PDCLIB_int.h> + +_PDCLIB_BEGIN_EXTERN_C +#define _PDCLIB_TSS_DTOR_ITERATIONS 3 +#define _PDCLIB_ONCE_FLAG_INIT { -1, 0 } +#define _PDCLIB_ONCE_FLAG_IS_DONE( _f ) ((_f)->_State == 0) +typedef struct { + long _State; + void *_Handle; +} _PDCLIB_once_flag; + +void _PDCLIB_call_once(_PDCLIB_once_flag *flag, void (*func)(void)); + +//#define _PDCLIB_THRD_HAVE_MISC +//#define _PDCLIB_CND_T char +#define _PDCLIB_MTX_T struct _PDCLIB_mtx + +struct _PDCLIB_mtx { + void * _WaitEvHandle; + volatile signed long _State; + volatile unsigned int _ThreadId; + volatile unsigned int _NestCount; +}; + +#define _PDCLIB_TSS_T struct _PDCLIB_tss * + +struct _PDCLIB_tss { + void (*_Destructor)(void*); + struct _PDCLIB_tss * _Next; + unsigned int _Key; +}; + +_PDCLIB_END_EXTERN_C +#endif