From 251f92ede2255d7f52667a0d5794136f5da2b8c6 Mon Sep 17 00:00:00 2001 From: Mitya Selivanov Date: Sun, 18 Dec 2022 00:12:32 +0100 Subject: Threads cleanup --- CMakeLists.txt | 8 +- source/kit/CMakeLists.txt | 6 +- source/kit/condition_variable.h | 35 +- source/kit/mersenne_twister_64.c | 8 - source/kit/mersenne_twister_64.h | 2 - source/kit/mutex.h | 44 ++- source/kit/secure_random.c | 23 +- source/kit/secure_random.h | 5 - source/kit/thread.h | 92 +++++ source/kit/thread.posix.c | 266 ++++++++++++++ source/kit/thread.win32.c | 380 ++++++++++++++++++++ source/kit/threads.h | 197 ----------- source/kit/threads.posix.c | 322 ----------------- source/kit/threads.win32.c | 433 ----------------------- source/kit/time.c | 43 +-- source/kit/time.h | 43 +-- source/test/unittests/atomic.test.c | 2 +- source/test/unittests/condition_variable.test.c | 1 + source/test/unittests/mersenne_twister_64.test.c | 4 +- source/test/unittests/mutex.test.c | 1 + source/test/unittests/secure_random.test.c | 4 - source/test/unittests/thread.test.c | 2 +- 22 files changed, 849 insertions(+), 1072 deletions(-) create mode 100644 source/kit/thread.h create mode 100644 source/kit/thread.posix.c create mode 100644 source/kit/thread.win32.c delete mode 100644 source/kit/threads.h delete mode 100644 source/kit/threads.posix.c delete mode 100644 source/kit/threads.win32.c diff --git a/CMakeLists.txt b/CMakeLists.txt index b5d1cc2..be0471c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,12 +98,12 @@ if(NOT KIT_HAVE_MALLOC) set(KIT_DISABLE_SYSTEM_MALLOC ON) endif() -if(KIT_HAVE_STRUCT_TIMESPEC) - target_compile_definitions(${KIT_LIBRARY} PUBLIC KIT_HAVE_STRUCT_TIMESPEC) +if(NOT KIT_HAVE_STRUCT_TIMESPEC) + target_compile_definitions(${KIT_LIBRARY} PUBLIC KIT_NEED_STRUCT_TIMESPEC) endif() -if(KIT_HAVE_TIMESPEC_GET) - target_compile_definitions(${KIT_LIBRARY} PUBLIC KIT_HAVE_TIMESPEC_GET) +if(NOT KIT_HAVE_TIMESPEC_GET) + target_compile_definitions(${KIT_LIBRARY} PUBLIC KIT_NEED_TIMESPEC_GET) endif() if(KIT_DISABLE_SYSTEM_MALLOC) diff --git a/source/kit/CMakeLists.txt b/source/kit/CMakeLists.txt index f9a30d2..e12077a 100644 --- a/source/kit/CMakeLists.txt +++ b/source/kit/CMakeLists.txt @@ -1,8 +1,8 @@ target_sources( ${KIT_LIBRARY} PRIVATE - input_buffer.c bigint.c status.c threads.win32.c time.c - secure_random.c atomic.win32.c threads.posix.c condition_variable.c + input_buffer.c bigint.c status.c time.c secure_random.c + thread.posix.c atomic.win32.c condition_variable.c thread.win32.c move_back.c input_stream.c lower_bound.c file.c string_ref.c async_function.c allocator.c array_ref.c dynamic_array.c mutex.c mersenne_twister_64.c @@ -13,7 +13,7 @@ target_sources( $ $ $ - $ + $ $ $ $ diff --git a/source/kit/condition_variable.h b/source/kit/condition_variable.h index dd3b891..0e4630d 100644 --- a/source/kit/condition_variable.h +++ b/source/kit/condition_variable.h @@ -1,6 +1,39 @@ #ifndef KIT_CONDITION_VARIABLE_H #define KIT_CONDITION_VARIABLE_H -#include "threads.h" +#ifndef KIT_DISABLE_SYSTEM_THREADS +# include "mutex.h" + +# ifdef __cplusplus +extern "C" { +# endif + +# if defined(_WIN32) && !defined(__CYGWIN__) +typedef struct { + void *Ptr; +} cnd_t; + +typedef struct { + volatile uintptr_t status; +} once_flag; +# else +typedef pthread_cond_t cnd_t; +typedef pthread_once_t once_flag; +# endif + +void call_once(once_flag *, void (*)(void)); +int cnd_broadcast(cnd_t *); +void cnd_destroy(cnd_t *); +int cnd_init(cnd_t *); +int cnd_signal(cnd_t *); +int cnd_timedwait(cnd_t *__restrict, mtx_t *__restrict mtx_, + const struct timespec *__restrict); +int cnd_wait(cnd_t *, mtx_t *mtx_); + +# ifdef __cplusplus +} +# endif + +#endif #endif diff --git a/source/kit/mersenne_twister_64.c b/source/kit/mersenne_twister_64.c index 28f8b3b..0570a1f 100644 --- a/source/kit/mersenne_twister_64.c +++ b/source/kit/mersenne_twister_64.c @@ -1,7 +1,5 @@ #include "mersenne_twister_64.h" -#include "secure_random.h" - #define MM 156 #define MATRIX_A 0xb5026f5aa96619e9ull #define UM 0xffffffff80000000ull @@ -60,9 +58,3 @@ uint64_t kit_mt64_generate(kit_mt64_state_t *const state) { return x; } - -uint64_t kit_mt64_seed() { - uint64_t seed; - kit_secure_random(sizeof seed, &seed); - return seed; -} diff --git a/source/kit/mersenne_twister_64.h b/source/kit/mersenne_twister_64.h index fb509c2..67a4d22 100644 --- a/source/kit/mersenne_twister_64.h +++ b/source/kit/mersenne_twister_64.h @@ -24,8 +24,6 @@ void kit_mt64_init(kit_mt64_state_t *state, uint64_t seed); uint64_t kit_mt64_generate(kit_mt64_state_t *state); -uint64_t kit_mt64_seed(); - #ifndef KIT_DISABLE_SHORT_NAMES # define mt64_state_t kit_mt64_state_t # define mt64_init_array kit_mt64_init_array diff --git a/source/kit/mutex.h b/source/kit/mutex.h index 0b7c548..e5d2e40 100644 --- a/source/kit/mutex.h +++ b/source/kit/mutex.h @@ -1,6 +1,48 @@ #ifndef KIT_MUTEX_H #define KIT_MUTEX_H -#include "threads.h" +#ifndef KIT_DISABLE_SYSTEM_THREADS +# include "time.h" + +# if !defined(_WIN32) || defined(__CYGWIN__) +# include +# endif + +# ifdef __cplusplus +extern "C" { +# endif + +# if defined(_WIN32) && !defined(__CYGWIN__) +typedef struct { + void *DebugInfo; + long LockCount; + long RecursionCount; + void *OwningThread; + void *LockSemaphore; + uintptr_t SpinCount; +} mtx_t; +# else +typedef pthread_mutex_t mtx_t; +# endif + +enum { + mtx_plain = 0, + mtx_recursive = 1, + mtx_timed = 2, +}; + +void mtx_destroy(mtx_t *mtx_); +int mtx_init(mtx_t *mtx_, int); +int mtx_lock(mtx_t *mtx_); +int mtx_timedlock(mtx_t *__restrict mtx_, + const struct timespec *__restrict); +int mtx_trylock(mtx_t *mtx_); +int mtx_unlock(mtx_t *mtx_); + +# ifdef __cplusplus +} +# endif + +#endif #endif diff --git a/source/kit/secure_random.c b/source/kit/secure_random.c index 8f48c9d..013fd52 100644 --- a/source/kit/secure_random.c +++ b/source/kit/secure_random.c @@ -1,8 +1,10 @@ #include "secure_random.h" +#include "condition_variable.h" #include "mersenne_twister_64.h" #include "mutex.h" #include "time.h" +#include #include #if defined(_WIN32) && !defined(__CYGWIN__) @@ -28,12 +30,19 @@ static uint64_t get_available_memory() { } #ifndef KIT_DISABLE_SYSTEM_THREADS -static mtx_t kit_secure_random_fallback_mutex; +static once_flag kit_secure_random_fallback_flag; +static mtx_t kit_secure_random_fallback_mutex; + +static void secure_random_fallback_init() { + mtx_init(&kit_secure_random_fallback_mutex, mtx_plain); +} #endif static void secure_random_fallback(ptrdiff_t const size, void *const data) { #ifndef KIT_DISABLE_SYSTEM_THREADS + call_once(&kit_secure_random_fallback_flag, + secure_random_fallback_init); mtx_lock(&kit_secure_random_fallback_mutex); #endif @@ -82,18 +91,6 @@ static void secure_random_fallback(ptrdiff_t const size, #endif } -void kit_secure_random_init() { -#ifndef KIT_DISABLE_SYSTEM_THREADS - mtx_init(&kit_secure_random_fallback_mutex, mtx_plain); -#endif -} - -void kit_secure_random_cleanup() { -#ifndef KIT_DISABLE_SYSTEM_THREADS - mtx_destroy(&kit_secure_random_fallback_mutex); -#endif -} - void kit_secure_random(ptrdiff_t const size, void *const data) { if (size <= 0) return; diff --git a/source/kit/secure_random.h b/source/kit/secure_random.h index 5b461f9..758478d 100644 --- a/source/kit/secure_random.h +++ b/source/kit/secure_random.h @@ -9,14 +9,9 @@ extern "C" { #endif -void kit_secure_random_init(); -void kit_secure_random_cleanup(); - void kit_secure_random(ptrdiff_t size, void *data); #ifndef KIT_DISABLE_SHORT_NAMES -# define secure_random_init kit_secure_random_init -# define secure_random_cleanup kit_secure_random_cleanup # define secure_random kit_secure_random #endif diff --git a/source/kit/thread.h b/source/kit/thread.h new file mode 100644 index 0000000..f64264e --- /dev/null +++ b/source/kit/thread.h @@ -0,0 +1,92 @@ +#ifndef KIT_THREAD_H +#define KIT_THREAD_H + +#ifndef KIT_DISABLE_SYSTEM_THREADS +# include "time.h" +# include + +# if defined(__cplusplus) +# define _Noreturn [[noreturn]] +# elif defined(_MSC_VER) +# define _Noreturn __declspec(noreturn) +# endif + +# if !defined(_WIN32) || defined(__CYGWIN__) +# include +# endif + +# ifndef _Thread_local +# if defined(__cplusplus) +/* C++11 doesn't need `_Thread_local` keyword or macro */ +# elif !defined(__STDC_NO_THREADS__) +/* threads are optional in C11, _Thread_local present in this + * condition */ +# elif defined(_MSC_VER) +# define _Thread_local __declspec(thread) +# elif defined(__GNUC__) +# define _Thread_local __thread +# else +/* Leave _Thread_local undefined so that use of _Thread_local would + * not promote to a non-thread-local global variable + */ +# endif +# endif + +# if !defined(__cplusplus) +/* + * C11 thread_local() macro + * C++11 and above already have thread_local keyword + */ +# ifndef thread_local +# if _MSC_VER +# define thread_local __declspec(thread) +# else +# define thread_local _Thread_local +# endif +# endif +# endif + +# ifdef __cplusplus +extern "C" { +# endif + +typedef void (*tss_dtor_t)(void *); +typedef int (*thrd_start_t)(void *); + +# if defined(_WIN32) && !defined(__CYGWIN__) +typedef struct { + void *handle; +} thrd_t; +typedef unsigned long tss_t; +# else +typedef pthread_t thrd_t; +typedef pthread_key_t tss_t; +# endif + +enum { + thrd_success = 0, + thrd_timedout, + thrd_error, + thrd_busy, + thrd_nomem, + thrd_wrong_stack_size +}; + +int thrd_create(thrd_t *, thrd_start_t, void *); +int thrd_create_with_stack(thrd_t *, thrd_start_t, void *, + ptrdiff_t stack_size); +thrd_t thrd_current(void); +int thrd_detach(thrd_t); +int thrd_equal(thrd_t, thrd_t); +_Noreturn void thrd_exit(int); +int thrd_join(thrd_t, int *); +int thrd_sleep(const struct timespec *, struct timespec *); +void thrd_yield(void); + +# ifdef __cplusplus +} +# endif + +#endif + +#endif diff --git a/source/kit/thread.posix.c b/source/kit/thread.posix.c new file mode 100644 index 0000000..442d7f8 --- /dev/null +++ b/source/kit/thread.posix.c @@ -0,0 +1,266 @@ +#ifndef KIT_DISABLE_SYSTEM_THREADS +# if !defined(_WIN32) || defined(__CYGWIN__) + +# include +# include +# include +# include +# include /* intptr_t */ +# include +# include + +# include "allocator.h" +# include "condition_variable.h" +# include "mutex.h" +# include "thread.h" + +/* +Configuration macro: + + EMULATED_THREADS_USE_NATIVE_TIMEDLOCK + Use pthread_mutex_timedlock() for `mtx_timedlock()' + Otherwise use mtx_trylock() + *busy loop* emulation. +*/ +# if !defined(__CYGWIN__) && !defined(__APPLE__) && \ + !defined(__NetBSD__) +# define EMULATED_THREADS_USE_NATIVE_TIMEDLOCK +# endif + +/* +Implementation limits: + - Conditionally emulation for "mutex with timeout" + (see EMULATED_THREADS_USE_NATIVE_TIMEDLOCK macro) +*/ +typedef struct { + thrd_start_t func; + void *arg; + kit_allocator_t alloc; +} impl_thrd_param_t; + +static void *impl_thrd_routine(void *p) { + impl_thrd_param_t pack = *((impl_thrd_param_t *) p); + pack.alloc.deallocate(pack.alloc.state, p); + return (void *) (intptr_t) pack.func(pack.arg); +} + +void call_once(once_flag *flag, void (*func)(void)) { + pthread_once(flag, func); +} + +int cnd_broadcast(cnd_t *cond) { + assert(cond != NULL); + return (pthread_cond_broadcast(cond) == 0) ? thrd_success + : thrd_error; +} + +void cnd_destroy(cnd_t *cond) { + assert(cond); + pthread_cond_destroy(cond); +} + +int cnd_init(cnd_t *cond) { + assert(cond != NULL); + return (pthread_cond_init(cond, NULL) == 0) ? thrd_success + : thrd_error; +} + +int cnd_signal(cnd_t *cond) { + assert(cond != NULL); + return (pthread_cond_signal(cond) == 0) ? thrd_success : thrd_error; +} + +int cnd_timedwait(cnd_t *cond, mtx_t *mtx, + const struct timespec *abs_time) { + int rt; + + assert(mtx != NULL); + assert(cond != NULL); + assert(abs_time != NULL); + + rt = pthread_cond_timedwait(cond, mtx, abs_time); + if (rt == ETIMEDOUT) + return thrd_timedout; + return (rt == 0) ? thrd_success : thrd_error; +} + +int cnd_wait(cnd_t *cond, mtx_t *mtx) { + assert(mtx != NULL); + assert(cond != NULL); + return (pthread_cond_wait(cond, mtx) == 0) ? thrd_success + : thrd_error; +} + +void mtx_destroy(mtx_t *mtx) { + assert(mtx != NULL); + pthread_mutex_destroy(mtx); +} + +/* + * XXX: Workaround when building with -O0 and without pthreads link. + * + * In such cases constant folding and dead code elimination won't be + * available, thus the compiler will always add the pthread_mutexattr* + * functions into the binary. As we try to link, we'll fail as the + * symbols are unresolved. + * + * Ideally we'll enable the optimisations locally, yet that does not + * seem to work. + * + * So the alternative workaround is to annotate the symbols as weak. + * Thus the linker will be happy and things don't clash when building + * with -O1 or greater. + */ +# if defined(HAVE_FUNC_ATTRIBUTE_WEAK) && !defined(__CYGWIN__) +__attribute__((weak)) int pthread_mutexattr_init( + pthread_mutexattr_t *attr); + +__attribute__((weak)) int pthread_mutexattr_settype( + pthread_mutexattr_t *attr, int type); + +__attribute__((weak)) int pthread_mutexattr_destroy( + pthread_mutexattr_t *attr); +# endif + +int mtx_init(mtx_t *mtx, int type) { + pthread_mutexattr_t attr; + assert(mtx != NULL); + if (type != mtx_plain && type != mtx_timed && + type != (mtx_plain | mtx_recursive) && + type != (mtx_timed | mtx_recursive)) + return thrd_error; + + if ((type & mtx_recursive) == 0) { + pthread_mutex_init(mtx, NULL); + return thrd_success; + } + + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(mtx, &attr); + pthread_mutexattr_destroy(&attr); + return thrd_success; +} + +int mtx_lock(mtx_t *mtx) { + assert(mtx != NULL); + return (pthread_mutex_lock(mtx) == 0) ? thrd_success : thrd_error; +} + +int mtx_timedlock(mtx_t *mtx, const struct timespec *ts) { + assert(mtx != NULL); + assert(ts != NULL); + + { +# ifdef EMULATED_THREADS_USE_NATIVE_TIMEDLOCK + int rt; + rt = pthread_mutex_timedlock(mtx, ts); + if (rt == 0) + return thrd_success; + return (rt == ETIMEDOUT) ? thrd_timedout : thrd_error; +# else + time_t expire = time(NULL); + expire += ts->tv_sec; + while (mtx_trylock(mtx) != thrd_success) { + time_t now = time(NULL); + if (expire < now) + return thrd_timedout; + // busy loop! + thrd_yield(); + } + return thrd_success; +# endif + } +} + +int mtx_trylock(mtx_t *mtx) { + assert(mtx != NULL); + return (pthread_mutex_trylock(mtx) == 0) ? thrd_success : thrd_busy; +} + +int mtx_unlock(mtx_t *mtx) { + assert(mtx != NULL); + return (pthread_mutex_unlock(mtx) == 0) ? thrd_success : thrd_error; +} + +int thrd_create_with_stack(thrd_t *thr, thrd_start_t func, void *arg, + ptrdiff_t const require_stack_size) { + impl_thrd_param_t *pack; + assert(thr != NULL); + assert(require_stack_size == 0 || + require_stack_size >= PTHREAD_STACK_MIN); + pthread_attr_t attr; + pthread_attr_t *attr_p = NULL; + if (require_stack_size > 0) { + ptrdiff_t const page_size = (ptrdiff_t) sysconf(_SC_PAGESIZE); + ptrdiff_t const delta = require_stack_size % page_size; + ptrdiff_t const stack_size = delta == 0 ? require_stack_size + : require_stack_size + + page_size - delta; + if (pthread_attr_init(&attr) != 0) + return thrd_nomem; + if (pthread_attr_setstacksize(&attr, (size_t) stack_size) != 0) + return thrd_wrong_stack_size; + attr_p = &attr; + } + kit_allocator_t alloc = kit_alloc_default(); + pack = (impl_thrd_param_t *) alloc.allocate( + alloc.state, sizeof(impl_thrd_param_t)); + if (!pack) { + if (attr_p) + pthread_attr_destroy(attr_p); + return thrd_nomem; + } + pack->func = func; + pack->arg = arg; + pack->alloc = alloc; + if (pthread_create(thr, attr_p, impl_thrd_routine, pack) != 0) { + alloc.deallocate(alloc.state, pack); + if (attr_p) + pthread_attr_destroy(attr_p); + return thrd_error; + } + if (attr_p) + pthread_attr_destroy(attr_p); + return thrd_success; +} + +int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) { + return thrd_create_with_stack(thr, func, arg, 0); +} +thrd_t thrd_current(void) { + return pthread_self(); +} + +int thrd_detach(thrd_t thr) { + return (pthread_detach(thr) == 0) ? thrd_success : thrd_error; +} + +int thrd_equal(thrd_t thr0, thrd_t thr1) { + return pthread_equal(thr0, thr1); +} + +_Noreturn void thrd_exit(int res) { + pthread_exit((void *) (intptr_t) res); +} + +int thrd_join(thrd_t thr, int *res) { + void *code; + if (pthread_join(thr, &code) != 0) + return thrd_error; + if (res) + *res = (int) (intptr_t) code; + return thrd_success; +} + +int thrd_sleep(const struct timespec *time_point, + struct timespec *remaining) { + assert(time_point != NULL); + return nanosleep(time_point, remaining); +} + +void thrd_yield(void) { + sched_yield(); +} + +# endif +#endif diff --git a/source/kit/thread.win32.c b/source/kit/thread.win32.c new file mode 100644 index 0000000..7e1dda8 --- /dev/null +++ b/source/kit/thread.win32.c @@ -0,0 +1,380 @@ +#ifndef KIT_DISABLE_SYSTEM_THREADS +# if defined(_WIN32) && !defined(__CYGWIN__) + +# include +# include +# include +# include +# include +# include + +# include "allocator.h" +# include "condition_variable.h" +# include "mutex.h" +# include "threads.h" + +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN 1 +# endif +# include + +/* +Configuration macro: + + EMULATED_THREADS_USE_NATIVE_CALL_ONCE + Use native WindowsAPI one-time initialization function. + (requires WinVista or later) + Otherwise emulate by mtx_trylock() + *busy loop* for WinXP. + + EMULATED_THREADS_TSS_DTOR_SLOTNUM + Max registerable TSS dtor number. +*/ + +# if _WIN32_WINNT >= 0x0600 +/* Prefer native WindowsAPI on newer environment. */ +# if !defined(__MINGW32__) +# define EMULATED_THREADS_USE_NATIVE_CALL_ONCE +# endif +# endif +# define EMULATED_THREADS_TSS_DTOR_SLOTNUM \ + 64 /* see TLS_MINIMUM_AVAILABLE */ + +/* check configuration */ +# if defined(EMULATED_THREADS_USE_NATIVE_CALL_ONCE) && \ + (_WIN32_WINNT < 0x0600) +# error EMULATED_THREADS_USE_NATIVE_CALL_ONCE requires _WIN32_WINNT>=0x0600 +# endif + +static_assert(sizeof(cnd_t) == sizeof(CONDITION_VARIABLE), + "The size of cnd_t must equal to CONDITION_VARIABLE"); +static_assert(sizeof(thrd_t) == sizeof(HANDLE), + "The size of thrd_t must equal to HANDLE"); +static_assert(sizeof(tss_t) == sizeof(DWORD), + "The size of tss_t must equal to DWORD"); +static_assert(sizeof(mtx_t) == sizeof(CRITICAL_SECTION), + "The size of mtx_t must equal to CRITICAL_SECTION"); +static_assert(sizeof(once_flag) == sizeof(INIT_ONCE), + "The size of once_flag must equal to INIT_ONCE"); + +/* +Implementation limits: + - Conditionally emulation for "Initialization functions" + (see EMULATED_THREADS_USE_NATIVE_CALL_ONCE macro) + - Emulated `mtx_timelock()' with mtx_trylock() + *busy loop* +*/ + +typedef struct { + thrd_start_t func; + void *arg; + thrd_t thrd; + kit_allocator_t alloc; +} impl_thrd_param_t; + +struct thrd_state { + thrd_t thrd; + bool handle_need_close; +}; + +static thread_local struct thrd_state impl_current_thread = { 0 }; + +static unsigned __stdcall impl_thrd_routine(void *p) { + impl_thrd_param_t *pack_p = (impl_thrd_param_t *) p; + impl_thrd_param_t pack; + int code; + impl_current_thread.thrd = pack_p->thrd; + impl_current_thread.handle_need_close = false; + memcpy(&pack, pack_p, sizeof(impl_thrd_param_t)); + pack.alloc.deallocate(pack.alloc.state, p); + code = pack.func(pack.arg); + return (unsigned) code; +} + +static time_t impl_timespec2msec(const struct timespec *ts) { + return (ts->tv_sec * 1000U) + (ts->tv_nsec / 1000000L); +} + +static DWORD impl_abs2relmsec(const struct timespec *abs_time) { + const time_t abs_ms = impl_timespec2msec(abs_time); + struct timespec now; + timespec_get(&now, TIME_UTC); + const time_t now_ms = impl_timespec2msec(&now); + const DWORD rel_ms = (abs_ms > now_ms) ? (DWORD) (abs_ms - now_ms) + : 0; + return rel_ms; +} + +# ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE +struct impl_call_once_param { + void (*func)(void); +}; + +static BOOL CALLBACK impl_call_once_callback(PINIT_ONCE InitOnce, + PVOID Parameter, + PVOID *Context) { + struct impl_call_once_param *param = (struct impl_call_once_param *) + Parameter; + (param->func)(); + ((void) InitOnce); + ((void) Context); /* suppress warning */ + return TRUE; +} +# endif /* ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE */ + +static struct impl_tss_dtor_entry { + tss_t key; + tss_dtor_t dtor; +} impl_tss_dtor_tbl[EMULATED_THREADS_TSS_DTOR_SLOTNUM]; + +static int impl_tss_dtor_register(tss_t key, tss_dtor_t dtor) { + int i; + for (i = 0; i < EMULATED_THREADS_TSS_DTOR_SLOTNUM; i++) { + if (!impl_tss_dtor_tbl[i].dtor) + break; + } + if (i == EMULATED_THREADS_TSS_DTOR_SLOTNUM) + return 1; + impl_tss_dtor_tbl[i].key = key; + impl_tss_dtor_tbl[i].dtor = dtor; + return 0; +} + +static void impl_tss_dtor_invoke(void) { + int i; + for (i = 0; i < EMULATED_THREADS_TSS_DTOR_SLOTNUM; i++) { + if (impl_tss_dtor_tbl[i].dtor) { + void *val = tss_get(impl_tss_dtor_tbl[i].key); + if (val) + (impl_tss_dtor_tbl[i].dtor)(val); + } + } +} + +void call_once(once_flag *flag, void (*func)(void)) { + assert(flag && func); +# ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE + { + struct impl_call_once_param param; + param.func = func; + InitOnceExecuteOnce((PINIT_ONCE) flag, impl_call_once_callback, + (PVOID) ¶m, NULL); + } +# else + if (InterlockedCompareExchangePointer( + (PVOID volatile *) &flag->status, (PVOID) 1, (PVOID) 0) == + 0) { + (func)(); + InterlockedExchangePointer((PVOID volatile *) &flag->status, + (PVOID) 2); + } else { + while (flag->status == 1) { + // busy loop! + thrd_yield(); + } + } +# endif +} + +int cnd_broadcast(cnd_t *cond) { + assert(cond != NULL); + WakeAllConditionVariable((PCONDITION_VARIABLE) cond); + return thrd_success; +} + +void cnd_destroy(cnd_t *cond) { + assert(cond != NULL); + /* do nothing */ + (void) cond; +} + +int cnd_init(cnd_t *cond) { + assert(cond != NULL); + InitializeConditionVariable((PCONDITION_VARIABLE) cond); + return thrd_success; +} + +int cnd_signal(cnd_t *cond) { + assert(cond != NULL); + WakeConditionVariable((PCONDITION_VARIABLE) cond); + return thrd_success; +} + +int cnd_timedwait(cnd_t *cond, mtx_t *mtx, + const struct timespec *abs_time) { + assert(cond != NULL); + assert(mtx != NULL); + assert(abs_time != NULL); + const DWORD timeout = impl_abs2relmsec(abs_time); + if (SleepConditionVariableCS((PCONDITION_VARIABLE) cond, + (PCRITICAL_SECTION) mtx, timeout)) + return thrd_success; + return (GetLastError() == ERROR_TIMEOUT) ? thrd_timedout + : thrd_error; +} + +int cnd_wait(cnd_t *cond, mtx_t *mtx) { + assert(cond != NULL); + assert(mtx != NULL); + SleepConditionVariableCS((PCONDITION_VARIABLE) cond, + (PCRITICAL_SECTION) mtx, INFINITE); + return thrd_success; +} + +void mtx_destroy(mtx_t *mtx) { + assert(mtx); + DeleteCriticalSection((PCRITICAL_SECTION) mtx); +} + +int mtx_init(mtx_t *mtx, int type) { + assert(mtx != NULL); + if (type != mtx_plain && type != mtx_timed && + type != (mtx_plain | mtx_recursive) && + type != (mtx_timed | mtx_recursive)) + return thrd_error; + InitializeCriticalSection((PCRITICAL_SECTION) mtx); + return thrd_success; +} + +int mtx_lock(mtx_t *mtx) { + assert(mtx != NULL); + EnterCriticalSection((PCRITICAL_SECTION) mtx); + return thrd_success; +} + +int mtx_timedlock(mtx_t *mtx, const struct timespec *ts) { + assert(mtx != NULL); + assert(ts != NULL); + while (mtx_trylock(mtx) != thrd_success) { + if (impl_abs2relmsec(ts) == 0) + return thrd_timedout; + /* busy loop! */ + thrd_yield(); + } + return thrd_success; +} + +int mtx_trylock(mtx_t *mtx) { + assert(mtx != NULL); + return TryEnterCriticalSection((PCRITICAL_SECTION) mtx) + ? thrd_success + : thrd_busy; +} + +int mtx_unlock(mtx_t *mtx) { + assert(mtx != NULL); + LeaveCriticalSection((PCRITICAL_SECTION) mtx); + return thrd_success; +} + +int thrd_create_with_stack(thrd_t *thr, thrd_start_t func, void *arg, + ptrdiff_t const stack_size) { + impl_thrd_param_t *pack; + uintptr_t handle; + assert(thr != NULL); + assert(stack_size >= 0 && stack_size < 0x100000000); + kit_allocator_t alloc = kit_alloc_default(); + pack = (impl_thrd_param_t *) alloc.allocate( + alloc.state, (sizeof(impl_thrd_param_t))); + if (!pack) + return thrd_nomem; + pack->func = func; + pack->arg = arg; + pack->alloc = alloc; + handle = _beginthreadex(NULL, (unsigned) stack_size, + impl_thrd_routine, pack, CREATE_SUSPENDED, + NULL); + if (handle == 0) { + alloc.deallocate(alloc.state, pack); + if (errno == EAGAIN || errno == EACCES) + return thrd_nomem; + return thrd_error; + } + thr->handle = (void *) handle; + pack->thrd = *thr; + ResumeThread((HANDLE) handle); + return thrd_success; +} + +int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) { + return thrd_create_with_stack(thr, func, arg, 0); +} + +thrd_t thrd_current(void) { + /* GetCurrentThread() returns a pseudo-handle, which we need + * to pass to DuplicateHandle(). Only the resulting handle can be + * used from other threads. + * + * Note that neither handle can be compared to the one by + * thread_create. Only the thread IDs - as returned by GetThreadId() + * and GetCurrentThreadId() can be compared directly. + * + * Other potential solutions would be: + * - define thrd_t as a thread Ids, but this would mean we'd need to + * OpenThread for many operations + * - use malloc'ed memory for thrd_t. This would imply using TLS for + * current thread. + * + * Neither is particularly nice. + * + * Life would be much easier if C11 threads had different + * abstractions for threads and thread IDs, just like C++11 threads + * does... + */ + struct thrd_state *state = &impl_current_thread; + if (state->thrd.handle == NULL) { + if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), + GetCurrentProcess(), &(state->thrd.handle), + 0, FALSE, DUPLICATE_SAME_ACCESS)) { + abort(); + } + state->handle_need_close = true; + } + return state->thrd; +} + +int thrd_detach(thrd_t thr) { + CloseHandle(thr.handle); + return thrd_success; +} + +int thrd_equal(thrd_t thr0, thrd_t thr1) { + return GetThreadId(thr0.handle) == GetThreadId(thr1.handle); +} + +_Noreturn void thrd_exit(int res) { + _endthreadex((unsigned) res); +} + +int thrd_join(thrd_t thr, int *res) { + DWORD w, code; + if (thr.handle == NULL) { + return thrd_error; + } + w = WaitForSingleObject(thr.handle, INFINITE); + if (w != WAIT_OBJECT_0) + return thrd_error; + if (res) { + if (!GetExitCodeThread(thr.handle, &code)) { + CloseHandle(thr.handle); + return thrd_error; + } + *res = (int) code; + } + CloseHandle(thr.handle); + return thrd_success; +} + +int thrd_sleep(const struct timespec *time_point, + struct timespec *remaining) { + (void) remaining; + assert(time_point); + assert(!remaining); /* not implemented */ + Sleep((DWORD) impl_timespec2msec(time_point)); + return 0; +} + +void thrd_yield(void) { + SwitchToThread(); +} + +# endif +#endif diff --git a/source/kit/threads.h b/source/kit/threads.h deleted file mode 100644 index 5cfb5d3..0000000 --- a/source/kit/threads.h +++ /dev/null @@ -1,197 +0,0 @@ -/* - * C11 emulation library - * - * (C) Copyright yohhoy 2012. - * Copyright 2022 Yonggang Luo - * Distributed under the Boost Software License, Version 1.0. - * - * Permission is hereby granted, free of charge, to any person or - * organization obtaining a copy of the software and accompanying - * documentation covered by this license (the "Software") to use, - * reproduce, display, distribute, execute, and transmit the Software, - * and to prepare [[derivative work]]s of the Software, and to permit - * third-parties to whom the Software is furnished to do so, all - * subject to the following: - * - * The copyright notices in the Software and this entire statement, - * including the above license grant, this restriction and the - * following disclaimer, must be included in all copies of the - * Software, in whole or in part, and all derivative works of the - * Software, unless such copies or derivative works are solely in the - * form of machine-executable object code generated by a source - * language processor. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND - * NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE - * DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER - * LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#ifndef KIT_THREADS_H -#define KIT_THREADS_H - -#ifndef KIT_DISABLE_SYSTEM_THREADS -# include "time.h" - -# include -# include -# include -# include - -# ifdef _MSC_VER -# define _Noreturn __declspec(noreturn) -# endif - -# if defined(_WIN32) && !defined(__CYGWIN__) -# include /* close */ -# include /* _exit */ -# else -# include -# include /* close, _exit */ -# endif - -/*---------------------------- macros ---------------------------*/ - -# ifndef _Thread_local -# if defined(__cplusplus) -/* C++11 doesn't need `_Thread_local` keyword or macro */ -# elif !defined(__STDC_NO_THREADS__) -/* threads are optional in C11, _Thread_local present in this - * condition */ -# elif defined(_MSC_VER) -# define _Thread_local __declspec(thread) -# elif defined(__GNUC__) -# define _Thread_local __thread -# else -/* Leave _Thread_local undefined so that use of _Thread_local would - * not promote to a non-thread-local global variable - */ -# endif -# endif - -# if !defined(__cplusplus) -/* - * C11 thread_local() macro - * C++11 and above already have thread_local keyword - */ -# ifndef thread_local -# if _MSC_VER -# define thread_local __declspec(thread) -# else -# define thread_local _Thread_local -# endif -# endif -# endif - -# ifdef __cplusplus -extern "C" { -# endif - -/*---------------------------- types ----------------------------*/ -typedef void (*tss_dtor_t)(void *); -typedef int (*thrd_start_t)(void *); - -# if defined(_WIN32) && !defined(__CYGWIN__) -typedef struct { - void *Ptr; -} cnd_t; -/* Define thrd_t as struct type intentionally for avoid use of thrd_t - * as pointer type */ -typedef struct { - void *handle; -} thrd_t; -typedef unsigned long tss_t; -typedef struct { - void *DebugInfo; - long LockCount; - long RecursionCount; - void *OwningThread; - void *LockSemaphore; - uintptr_t SpinCount; -} mtx_t; /* Mock of CRITICAL_SECTION */ -typedef struct { - volatile uintptr_t status; -} once_flag; -// FIXME: temporary non-standard hack to ease transition -# define KIT_MTX_INITIALIZER_NP_ \ - { (void *) -1, -1, 0, 0, 0, 0 } -# define ONCE_FLAG_INIT \ - { 0 } -# define TSS_DTOR_ITERATIONS 1 -# else -typedef pthread_cond_t cnd_t; -typedef pthread_t thrd_t; -typedef pthread_key_t tss_t; -typedef pthread_mutex_t mtx_t; -typedef pthread_once_t once_flag; -// FIXME: temporary non-standard hack to ease transition -# define KIT_MTX_INITIALIZER_NP_ PTHREAD_MUTEX_INITIALIZER -# define ONCE_FLAG_INIT PTHREAD_ONCE_INIT -# ifdef PTHREAD_DESTRUCTOR_ITERATIONS -# define TSS_DTOR_ITERATIONS PTHREAD_DESTRUCTOR_ITERATIONS -# else -# define TSS_DTOR_ITERATIONS \ - 1 // assume TSS dtor MAY be called at least once. -# endif -# endif - -/*-------------------- enumeration constants --------------------*/ -enum { - mtx_plain = 0, - mtx_recursive = 1, - mtx_timed = 2, -}; - -enum { - thrd_success = 0, // succeeded - thrd_timedout, // timed out - thrd_error, // failed - thrd_busy, // resource busy - thrd_nomem, // out of memory, - thrd_wrong_stack_size -}; - -/*-------------------------- functions --------------------------*/ - -void call_once(once_flag *, void (*)(void)); -int cnd_broadcast(cnd_t *); -void cnd_destroy(cnd_t *); -int cnd_init(cnd_t *); -int cnd_signal(cnd_t *); -int cnd_timedwait(cnd_t *__restrict, mtx_t *__restrict mtx_, - const struct timespec *__restrict); -int cnd_wait(cnd_t *, mtx_t *mtx_); -void mtx_destroy(mtx_t *mtx_); -int mtx_init(mtx_t *mtx_, int); -int mtx_lock(mtx_t *mtx_); -int mtx_timedlock(mtx_t *__restrict mtx_, - const struct timespec *__restrict); -int mtx_trylock(mtx_t *mtx_); -int mtx_unlock(mtx_t *mtx_); -int thrd_create(thrd_t *, thrd_start_t, void *); -int thrd_create_with_stack(thrd_t *, thrd_start_t, void *, - ptrdiff_t stack_size); -thrd_t thrd_current(void); -int thrd_detach(thrd_t); -int thrd_equal(thrd_t, thrd_t); -# if defined(__cplusplus) -[[ noreturn ]] -# else -_Noreturn -# endif -void thrd_exit(int); -int thrd_join(thrd_t, int *); -int thrd_sleep(const struct timespec *, struct timespec *); -void thrd_yield(void); - -# ifdef __cplusplus -} -# endif - -#endif /* KIT_DISABLE_SYSTEM_THREADS */ - -#endif /* KIT_THREADS_H */ diff --git a/source/kit/threads.posix.c b/source/kit/threads.posix.c deleted file mode 100644 index efedbaa..0000000 --- a/source/kit/threads.posix.c +++ /dev/null @@ -1,322 +0,0 @@ -/* - * C11 emulation library - * - * (C) Copyright yohhoy 2012. - * Distributed under the Boost Software License, Version 1.0. - * - * Permission is hereby granted, free of charge, to any person or - * organization obtaining a copy of the software and accompanying - * documentation covered by this license (the "Software") to use, - * reproduce, display, distribute, execute, and transmit the Software, - * and to prepare [[derivative work]]s of the Software, and to permit - * third-parties to whom the Software is furnished to do so, all - * subject to the following: - * - * The copyright notices in the Software and this entire statement, - * including the above license grant, this restriction and the - * following disclaimer, must be included in all copies of the - * Software, in whole or in part, and all derivative works of the - * Software, unless such copies or derivative works are solely in the - * form of machine-executable object code generated by a source - * language processor. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND - * NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE - * DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER - * LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ -#ifndef KIT_DISABLE_SYSTEM_THREADS -# if !defined(_WIN32) || defined(__CYGWIN__) - -# include -# include -# include -# include -# include /* for intptr_t */ -# include -# include - -# include "allocator.h" -# include "threads.h" - -/* -Configuration macro: - - EMULATED_THREADS_USE_NATIVE_TIMEDLOCK - Use pthread_mutex_timedlock() for `mtx_timedlock()' - Otherwise use mtx_trylock() + *busy loop* emulation. -*/ -# if !defined(__CYGWIN__) && !defined(__APPLE__) && \ - !defined(__NetBSD__) -# define EMULATED_THREADS_USE_NATIVE_TIMEDLOCK -# endif - -/*---------------------------- types ----------------------------*/ - -/* -Implementation limits: - - Conditionally emulation for "mutex with timeout" - (see EMULATED_THREADS_USE_NATIVE_TIMEDLOCK macro) -*/ -typedef struct { - thrd_start_t func; - void *arg; - kit_allocator_t alloc; -} impl_thrd_param_t; - -static void *impl_thrd_routine(void *p) { - impl_thrd_param_t pack = *((impl_thrd_param_t *) p); - pack.alloc.deallocate(pack.alloc.state, p); - return (void *) (intptr_t) pack.func(pack.arg); -} - -/*--------------- 7.25.2 Initialization functions ---------------*/ -// 7.25.2.1 -void call_once(once_flag *flag, void (*func)(void)) { - pthread_once(flag, func); -} - -/*------------- 7.25.3 Condition variable functions -------------*/ -// 7.25.3.1 -int cnd_broadcast(cnd_t *cond) { - assert(cond != NULL); - return (pthread_cond_broadcast(cond) == 0) ? thrd_success - : thrd_error; -} - -// 7.25.3.2 -void cnd_destroy(cnd_t *cond) { - assert(cond); - pthread_cond_destroy(cond); -} - -// 7.25.3.3 -int cnd_init(cnd_t *cond) { - assert(cond != NULL); - return (pthread_cond_init(cond, NULL) == 0) ? thrd_success - : thrd_error; -} - -// 7.25.3.4 -int cnd_signal(cnd_t *cond) { - assert(cond != NULL); - return (pthread_cond_signal(cond) == 0) ? thrd_success : thrd_error; -} - -// 7.25.3.5 -int cnd_timedwait(cnd_t *cond, mtx_t *mtx, - const struct timespec *abs_time) { - int rt; - - assert(mtx != NULL); - assert(cond != NULL); - assert(abs_time != NULL); - - rt = pthread_cond_timedwait(cond, mtx, abs_time); - if (rt == ETIMEDOUT) - return thrd_timedout; - return (rt == 0) ? thrd_success : thrd_error; -} - -// 7.25.3.6 -int cnd_wait(cnd_t *cond, mtx_t *mtx) { - assert(mtx != NULL); - assert(cond != NULL); - return (pthread_cond_wait(cond, mtx) == 0) ? thrd_success - : thrd_error; -} - -/*-------------------- 7.25.4 Mutex functions --------------------*/ -// 7.25.4.1 -void mtx_destroy(mtx_t *mtx) { - assert(mtx != NULL); - pthread_mutex_destroy(mtx); -} - -/* - * XXX: Workaround when building with -O0 and without pthreads link. - * - * In such cases constant folding and dead code elimination won't be - * available, thus the compiler will always add the pthread_mutexattr* - * functions into the binary. As we try to link, we'll fail as the - * symbols are unresolved. - * - * Ideally we'll enable the optimisations locally, yet that does not - * seem to work. - * - * So the alternative workaround is to annotate the symbols as weak. - * Thus the linker will be happy and things don't clash when building - * with -O1 or greater. - */ -# if defined(HAVE_FUNC_ATTRIBUTE_WEAK) && !defined(__CYGWIN__) -__attribute__((weak)) int pthread_mutexattr_init( - pthread_mutexattr_t *attr); - -__attribute__((weak)) int pthread_mutexattr_settype( - pthread_mutexattr_t *attr, int type); - -__attribute__((weak)) int pthread_mutexattr_destroy( - pthread_mutexattr_t *attr); -# endif - -// 7.25.4.2 -int mtx_init(mtx_t *mtx, int type) { - pthread_mutexattr_t attr; - assert(mtx != NULL); - if (type != mtx_plain && type != mtx_timed && - type != (mtx_plain | mtx_recursive) && - type != (mtx_timed | mtx_recursive)) - return thrd_error; - - if ((type & mtx_recursive) == 0) { - pthread_mutex_init(mtx, NULL); - return thrd_success; - } - - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); - pthread_mutex_init(mtx, &attr); - pthread_mutexattr_destroy(&attr); - return thrd_success; -} - -// 7.25.4.3 -int mtx_lock(mtx_t *mtx) { - assert(mtx != NULL); - return (pthread_mutex_lock(mtx) == 0) ? thrd_success : thrd_error; -} - -// 7.25.4.4 -int mtx_timedlock(mtx_t *mtx, const struct timespec *ts) { - assert(mtx != NULL); - assert(ts != NULL); - - { -# ifdef EMULATED_THREADS_USE_NATIVE_TIMEDLOCK - int rt; - rt = pthread_mutex_timedlock(mtx, ts); - if (rt == 0) - return thrd_success; - return (rt == ETIMEDOUT) ? thrd_timedout : thrd_error; -# else - time_t expire = time(NULL); - expire += ts->tv_sec; - while (mtx_trylock(mtx) != thrd_success) { - time_t now = time(NULL); - if (expire < now) - return thrd_timedout; - // busy loop! - thrd_yield(); - } - return thrd_success; -# endif - } -} - -// 7.25.4.5 -int mtx_trylock(mtx_t *mtx) { - assert(mtx != NULL); - return (pthread_mutex_trylock(mtx) == 0) ? thrd_success : thrd_busy; -} - -// 7.25.4.6 -int mtx_unlock(mtx_t *mtx) { - assert(mtx != NULL); - return (pthread_mutex_unlock(mtx) == 0) ? thrd_success : thrd_error; -} - -/*------------------- 7.25.5 Thread functions -------------------*/ -// 7.25.5.1 -int thrd_create_with_stack(thrd_t *thr, thrd_start_t func, void *arg, - ptrdiff_t const require_stack_size) { - impl_thrd_param_t *pack; - assert(thr != NULL); - assert(require_stack_size == 0 || - require_stack_size >= PTHREAD_STACK_MIN); - pthread_attr_t attr; - pthread_attr_t *attr_p = NULL; - if (require_stack_size > 0) { - ptrdiff_t const page_size = (ptrdiff_t) sysconf(_SC_PAGESIZE); - ptrdiff_t const delta = require_stack_size % page_size; - ptrdiff_t const stack_size = delta == 0 ? require_stack_size - : require_stack_size + - page_size - delta; - if (pthread_attr_init(&attr) != 0) - return thrd_nomem; - if (pthread_attr_setstacksize(&attr, (size_t) stack_size) != 0) - return thrd_wrong_stack_size; - attr_p = &attr; - } - kit_allocator_t alloc = kit_alloc_default(); - pack = (impl_thrd_param_t *) alloc.allocate( - alloc.state, sizeof(impl_thrd_param_t)); - if (!pack) { - if (attr_p) - pthread_attr_destroy(attr_p); - return thrd_nomem; - } - pack->func = func; - pack->arg = arg; - pack->alloc = alloc; - if (pthread_create(thr, attr_p, impl_thrd_routine, pack) != 0) { - alloc.deallocate(alloc.state, pack); - if (attr_p) - pthread_attr_destroy(attr_p); - return thrd_error; - } - if (attr_p) - pthread_attr_destroy(attr_p); - return thrd_success; -} - -int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) { - return thrd_create_with_stack(thr, func, arg, 0); -} -// 7.25.5.2 -thrd_t thrd_current(void) { - return pthread_self(); -} - -// 7.25.5.3 -int thrd_detach(thrd_t thr) { - return (pthread_detach(thr) == 0) ? thrd_success : thrd_error; -} - -// 7.25.5.4 -int thrd_equal(thrd_t thr0, thrd_t thr1) { - return pthread_equal(thr0, thr1); -} - -// 7.25.5.5 -_Noreturn void thrd_exit(int res) { - pthread_exit((void *) (intptr_t) res); -} - -// 7.25.5.6 -int thrd_join(thrd_t thr, int *res) { - void *code; - if (pthread_join(thr, &code) != 0) - return thrd_error; - if (res) - *res = (int) (intptr_t) code; - return thrd_success; -} - -// 7.25.5.7 -int thrd_sleep(const struct timespec *time_point, - struct timespec *remaining) { - assert(time_point != NULL); - return nanosleep(time_point, remaining); -} - -// 7.25.5.8 -void thrd_yield(void) { - sched_yield(); -} - -# endif -#endif diff --git a/source/kit/threads.win32.c b/source/kit/threads.win32.c deleted file mode 100644 index 534b01c..0000000 --- a/source/kit/threads.win32.c +++ /dev/null @@ -1,433 +0,0 @@ -/* - * C11 emulation library - * - * (C) Copyright yohhoy 2012. - * Distributed under the Boost Software License, Version 1.0. - * - * Permission is hereby granted, free of charge, to any person or - * organization obtaining a copy of the software and accompanying - * documentation covered by this license (the "Software") to use, - * reproduce, display, distribute, execute, and transmit the Software, - * and to prepare [[derivative work]]s of the Software, and to permit - * third-parties to whom the Software is furnished to do so, all - * subject to the following: - * - * The copyright notices in the Software and this entire statement, - * including the above license grant, this restriction and the - * following disclaimer, must be included in all copies of the - * Software, in whole or in part, and all derivative works of the - * Software, unless such copies or derivative works are solely in the - * form of machine-executable object code generated by a source - * language processor. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND - * NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE - * DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER - * LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ -#ifndef KIT_DISABLE_SYSTEM_THREADS -# if defined(_WIN32) && !defined(__CYGWIN__) - -# include -# include -# include -# include // MSVCRT -# include -# include - -# include "allocator.h" -# include "threads.h" - -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN 1 -# endif -# include - -/* -Configuration macro: - - EMULATED_THREADS_USE_NATIVE_CALL_ONCE - Use native WindowsAPI one-time initialization function. - (requires WinVista or later) - Otherwise emulate by mtx_trylock() + *busy loop* for WinXP. - - EMULATED_THREADS_TSS_DTOR_SLOTNUM - Max registerable TSS dtor number. -*/ - -# if _WIN32_WINNT >= 0x0600 -// Prefer native WindowsAPI on newer environment. -# if !defined(__MINGW32__) -# define EMULATED_THREADS_USE_NATIVE_CALL_ONCE -# endif -# endif -# define EMULATED_THREADS_TSS_DTOR_SLOTNUM \ - 64 // see TLS_MINIMUM_AVAILABLE - -// check configuration -# if defined(EMULATED_THREADS_USE_NATIVE_CALL_ONCE) && \ - (_WIN32_WINNT < 0x0600) -# error EMULATED_THREADS_USE_NATIVE_CALL_ONCE requires _WIN32_WINNT>=0x0600 -# endif - -static_assert(sizeof(cnd_t) == sizeof(CONDITION_VARIABLE), - "The size of cnd_t must equal to CONDITION_VARIABLE"); -static_assert(sizeof(thrd_t) == sizeof(HANDLE), - "The size of thrd_t must equal to HANDLE"); -static_assert(sizeof(tss_t) == sizeof(DWORD), - "The size of tss_t must equal to DWORD"); -static_assert(sizeof(mtx_t) == sizeof(CRITICAL_SECTION), - "The size of mtx_t must equal to CRITICAL_SECTION"); -static_assert(sizeof(once_flag) == sizeof(INIT_ONCE), - "The size of once_flag must equal to INIT_ONCE"); - -/* -Implementation limits: - - Conditionally emulation for "Initialization functions" - (see EMULATED_THREADS_USE_NATIVE_CALL_ONCE macro) - - Emulated `mtx_timelock()' with mtx_trylock() + *busy loop* -*/ - -typedef struct { - thrd_start_t func; - void *arg; - thrd_t thrd; - kit_allocator_t alloc; -} impl_thrd_param_t; - -struct thrd_state { - thrd_t thrd; - bool handle_need_close; -}; - -static thread_local struct thrd_state impl_current_thread = { 0 }; - -static unsigned __stdcall impl_thrd_routine(void *p) { - impl_thrd_param_t *pack_p = (impl_thrd_param_t *) p; - impl_thrd_param_t pack; - int code; - impl_current_thread.thrd = pack_p->thrd; - impl_current_thread.handle_need_close = false; - memcpy(&pack, pack_p, sizeof(impl_thrd_param_t)); - pack.alloc.deallocate(pack.alloc.state, p); - code = pack.func(pack.arg); - return (unsigned) code; -} - -static time_t impl_timespec2msec(const struct timespec *ts) { - return (ts->tv_sec * 1000U) + (ts->tv_nsec / 1000000L); -} - -static DWORD impl_abs2relmsec(const struct timespec *abs_time) { - const time_t abs_ms = impl_timespec2msec(abs_time); - struct timespec now; - timespec_get(&now, TIME_UTC); - const time_t now_ms = impl_timespec2msec(&now); - const DWORD rel_ms = (abs_ms > now_ms) ? (DWORD) (abs_ms - now_ms) - : 0; - return rel_ms; -} - -# ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE -struct impl_call_once_param { - void (*func)(void); -}; -static BOOL CALLBACK impl_call_once_callback(PINIT_ONCE InitOnce, - PVOID Parameter, - PVOID *Context) { - struct impl_call_once_param *param = (struct impl_call_once_param *) - Parameter; - (param->func)(); - ((void) InitOnce); - ((void) Context); // suppress warning - return TRUE; -} -# endif // ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE - -static struct impl_tss_dtor_entry { - tss_t key; - tss_dtor_t dtor; -} impl_tss_dtor_tbl[EMULATED_THREADS_TSS_DTOR_SLOTNUM]; - -static int impl_tss_dtor_register(tss_t key, tss_dtor_t dtor) { - int i; - for (i = 0; i < EMULATED_THREADS_TSS_DTOR_SLOTNUM; i++) { - if (!impl_tss_dtor_tbl[i].dtor) - break; - } - if (i == EMULATED_THREADS_TSS_DTOR_SLOTNUM) - return 1; - impl_tss_dtor_tbl[i].key = key; - impl_tss_dtor_tbl[i].dtor = dtor; - return 0; -} - -static void impl_tss_dtor_invoke(void) { - int i; - for (i = 0; i < EMULATED_THREADS_TSS_DTOR_SLOTNUM; i++) { - if (impl_tss_dtor_tbl[i].dtor) { - void *val = tss_get(impl_tss_dtor_tbl[i].key); - if (val) - (impl_tss_dtor_tbl[i].dtor)(val); - } - } -} - -/*--------------- 7.25.2 Initialization functions ---------------*/ -// 7.25.2.1 -void call_once(once_flag *flag, void (*func)(void)) { - assert(flag && func); -# ifdef EMULATED_THREADS_USE_NATIVE_CALL_ONCE - { - struct impl_call_once_param param; - param.func = func; - InitOnceExecuteOnce((PINIT_ONCE) flag, impl_call_once_callback, - (PVOID) ¶m, NULL); - } -# else - if (InterlockedCompareExchangePointer( - (PVOID volatile *) &flag->status, (PVOID) 1, (PVOID) 0) == - 0) { - (func)(); - InterlockedExchangePointer((PVOID volatile *) &flag->status, - (PVOID) 2); - } else { - while (flag->status == 1) { - // busy loop! - thrd_yield(); - } - } -# endif -} - -/*------------- 7.25.3 Condition variable functions -------------*/ -// 7.25.3.1 -int cnd_broadcast(cnd_t *cond) { - assert(cond != NULL); - WakeAllConditionVariable((PCONDITION_VARIABLE) cond); - return thrd_success; -} - -// 7.25.3.2 -void cnd_destroy(cnd_t *cond) { - (void) cond; - assert(cond != NULL); - // do nothing -} - -// 7.25.3.3 -int cnd_init(cnd_t *cond) { - assert(cond != NULL); - InitializeConditionVariable((PCONDITION_VARIABLE) cond); - return thrd_success; -} - -// 7.25.3.4 -int cnd_signal(cnd_t *cond) { - assert(cond != NULL); - WakeConditionVariable((PCONDITION_VARIABLE) cond); - return thrd_success; -} - -// 7.25.3.5 -int cnd_timedwait(cnd_t *cond, mtx_t *mtx, - const struct timespec *abs_time) { - assert(cond != NULL); - assert(mtx != NULL); - assert(abs_time != NULL); - const DWORD timeout = impl_abs2relmsec(abs_time); - if (SleepConditionVariableCS((PCONDITION_VARIABLE) cond, - (PCRITICAL_SECTION) mtx, timeout)) - return thrd_success; - return (GetLastError() == ERROR_TIMEOUT) ? thrd_timedout - : thrd_error; -} - -// 7.25.3.6 -int cnd_wait(cnd_t *cond, mtx_t *mtx) { - assert(cond != NULL); - assert(mtx != NULL); - SleepConditionVariableCS((PCONDITION_VARIABLE) cond, - (PCRITICAL_SECTION) mtx, INFINITE); - return thrd_success; -} - -/*-------------------- 7.25.4 Mutex functions --------------------*/ -// 7.25.4.1 -void mtx_destroy(mtx_t *mtx) { - assert(mtx); - DeleteCriticalSection((PCRITICAL_SECTION) mtx); -} - -// 7.25.4.2 -int mtx_init(mtx_t *mtx, int type) { - assert(mtx != NULL); - if (type != mtx_plain && type != mtx_timed && - type != (mtx_plain | mtx_recursive) && - type != (mtx_timed | mtx_recursive)) - return thrd_error; - InitializeCriticalSection((PCRITICAL_SECTION) mtx); - return thrd_success; -} - -// 7.25.4.3 -int mtx_lock(mtx_t *mtx) { - assert(mtx != NULL); - EnterCriticalSection((PCRITICAL_SECTION) mtx); - return thrd_success; -} - -// 7.25.4.4 -int mtx_timedlock(mtx_t *mtx, const struct timespec *ts) { - assert(mtx != NULL); - assert(ts != NULL); - while (mtx_trylock(mtx) != thrd_success) { - if (impl_abs2relmsec(ts) == 0) - return thrd_timedout; - // busy loop! - thrd_yield(); - } - return thrd_success; -} - -// 7.25.4.5 -int mtx_trylock(mtx_t *mtx) { - assert(mtx != NULL); - return TryEnterCriticalSection((PCRITICAL_SECTION) mtx) - ? thrd_success - : thrd_busy; -} - -// 7.25.4.6 -int mtx_unlock(mtx_t *mtx) { - assert(mtx != NULL); - LeaveCriticalSection((PCRITICAL_SECTION) mtx); - return thrd_success; -} - -/*------------------- 7.25.5 Thread functions -------------------*/ -// 7.25.5.1 -int thrd_create_with_stack(thrd_t *thr, thrd_start_t func, void *arg, - ptrdiff_t const stack_size) { - impl_thrd_param_t *pack; - uintptr_t handle; - assert(thr != NULL); - assert(stack_size >= 0 && stack_size < 0x100000000); - kit_allocator_t alloc = kit_alloc_default(); - pack = (impl_thrd_param_t *) alloc.allocate( - alloc.state, (sizeof(impl_thrd_param_t))); - if (!pack) - return thrd_nomem; - pack->func = func; - pack->arg = arg; - pack->alloc = alloc; - handle = _beginthreadex(NULL, (unsigned) stack_size, - impl_thrd_routine, pack, CREATE_SUSPENDED, - NULL); - if (handle == 0) { - alloc.deallocate(alloc.state, pack); - if (errno == EAGAIN || errno == EACCES) - return thrd_nomem; - return thrd_error; - } - thr->handle = (void *) handle; - pack->thrd = *thr; - ResumeThread((HANDLE) handle); - return thrd_success; -} - -int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) { - return thrd_create_with_stack(thr, func, arg, 0); -} - -// 7.25.5.2 -thrd_t thrd_current(void) { - /* GetCurrentThread() returns a pseudo-handle, which we need - * to pass to DuplicateHandle(). Only the resulting handle can be - * used from other threads. - * - * Note that neither handle can be compared to the one by - * thread_create. Only the thread IDs - as returned by GetThreadId() - * and GetCurrentThreadId() can be compared directly. - * - * Other potential solutions would be: - * - define thrd_t as a thread Ids, but this would mean we'd need to - * OpenThread for many operations - * - use malloc'ed memory for thrd_t. This would imply using TLS for - * current thread. - * - * Neither is particularly nice. - * - * Life would be much easier if C11 threads had different - * abstractions for threads and thread IDs, just like C++11 threads - * does... - */ - struct thrd_state *state = &impl_current_thread; - if (state->thrd.handle == NULL) { - if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), - GetCurrentProcess(), &(state->thrd.handle), - 0, FALSE, DUPLICATE_SAME_ACCESS)) { - abort(); - } - state->handle_need_close = true; - } - return state->thrd; -} - -// 7.25.5.3 -int thrd_detach(thrd_t thr) { - CloseHandle(thr.handle); - return thrd_success; -} - -// 7.25.5.4 -int thrd_equal(thrd_t thr0, thrd_t thr1) { - return GetThreadId(thr0.handle) == GetThreadId(thr1.handle); -} - -// 7.25.5.5 -_Noreturn void thrd_exit(int res) { - _endthreadex((unsigned) res); -} - -// 7.25.5.6 -int thrd_join(thrd_t thr, int *res) { - DWORD w, code; - if (thr.handle == NULL) { - return thrd_error; - } - w = WaitForSingleObject(thr.handle, INFINITE); - if (w != WAIT_OBJECT_0) - return thrd_error; - if (res) { - if (!GetExitCodeThread(thr.handle, &code)) { - CloseHandle(thr.handle); - return thrd_error; - } - *res = (int) code; - } - CloseHandle(thr.handle); - return thrd_success; -} - -// 7.25.5.7 -int thrd_sleep(const struct timespec *time_point, - struct timespec *remaining) { - (void) remaining; - assert(time_point); - assert(!remaining); /* not implemented */ - Sleep((DWORD) impl_timespec2msec(time_point)); - return 0; -} - -// 7.25.5.8 -void thrd_yield(void) { - SwitchToThread(); -} - -# endif -#endif /* KIT_DISABLE_SYSTEM_THREADS */ diff --git a/source/kit/time.c b/source/kit/time.c index bcb3819..108434f 100644 --- a/source/kit/time.c +++ b/source/kit/time.c @@ -1,39 +1,6 @@ -/* - * C11 implementation - * - * (C) Copyright yohhoy 2012. - * Copyright 2022 Yonggang Luo - * Distributed under the Boost Software License, Version 1.0. - * - * Permission is hereby granted, free of charge, to any person or - * organization obtaining a copy of the software and accompanying - * documentation covered by this license (the "Software") to use, - * reproduce, display, distribute, execute, and transmit the Software, - * and to prepare [[derivative work]]s of the Software, and to permit - * third-parties to whom the Software is furnished to do so, all - * subject to the following: - * - * The copyright notices in the Software and this entire statement, - * including the above license grant, this restriction and the - * following disclaimer, must be included in all copies of the - * Software, in whole or in part, and all derivative works of the - * Software, unless such copies or derivative works are solely in the - * form of machine-executable object code generated by a source - * language processor. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND - * NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE - * DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER - * LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - #include "time.h" -#ifndef KIT_HAVE_TIMESPEC_GET +#ifdef KIT_NEED_TIMESPEC_GET # if defined(_WIN32) && !defined(__CYGWIN__) @@ -43,11 +10,11 @@ # include int timespec_get(struct timespec *ts, int base) { -/* difference between 1970 and 1601 */ +/* difference between 1970 and 1601 */ # define _TIMESPEC_IMPL_UNIX_EPOCH_IN_TICKS 116444736000000000ull -/* 1 tick is 100 nanoseconds */ +/* 1 tick is 100 nanoseconds */ # define _TIMESPEC_IMPL_TICKS_PER_SECONDS 10000000ull - if (!ts) + if (ts == NULL) return 0; if (base == TIME_UTC) { FILETIME ft; @@ -71,7 +38,7 @@ int timespec_get(struct timespec *ts, int base) { # else int timespec_get(struct timespec *ts, int base) { - if (!ts) + if (ts == NULL) return 0; if (base == TIME_UTC) { clock_gettime(CLOCK_REALTIME, ts); diff --git a/source/kit/time.h b/source/kit/time.h index 0d995f4..9445912 100644 --- a/source/kit/time.h +++ b/source/kit/time.h @@ -1,17 +1,8 @@ -/* - * Copyright 2022 Yonggang Luo - * SPDX-License-Identifier: MIT - * - * C11 emulation library - */ - #ifndef KIT_TIME_H #define KIT_TIME_H #include -/*---------------------------- macros ---------------------------*/ - #ifndef TIME_UTC # define TIME_UTC 1 #endif @@ -20,43 +11,19 @@ extern "C" { #endif -/*---------------------------- types ----------------------------*/ - -/* - * On MINGW `struct timespec` present but `timespec_get` may not - * present; On MSVC `struct timespec` and `timespec_get` present at - * the same time; So detecting `HAVE_STRUCT_TIMESPEC` in meson script - * dynamically. - */ -#ifndef KIT_HAVE_STRUCT_TIMESPEC +#ifdef KIT_NEED_STRUCT_TIMESPEC struct timespec { - time_t tv_sec; // Seconds - >= 0 - long tv_nsec; // Nanoseconds - [0, 999999999] + time_t tv_sec; /* Seconds - >= 0 */ + long tv_nsec; /* Nanoseconds - [0, 999999999] */ }; #endif -/*-------------------------- functions --------------------------*/ - -#if !defined(KIT_HAVE_TIMESPEC_GET) -# define KIT_TIMESPEC_GET_NEED_DECL_ -#elif defined(__APPLE__) && defined(__cplusplus) && \ - (__cplusplus < 201703L) -/* On macOS, the guard for declaration of timespec_get is by - * (defined(__cplusplus) && __cplusplus >= 201703L), - * fix the declaration for C++14 and lower here - */ -# define KIT_TIMESPEC_GET_NEED_DECL_ -#endif - -#ifdef KIT_TIMESPEC_GET_NEED_DECL_ -/*-------------------- 7.25.7 Time functions --------------------*/ -// 7.25.6.1 +#ifdef KIT_NEED_TIMESPEC_GET int timespec_get(struct timespec *ts, int base); -# undef KIT_TIMESPEC_GET_NEED_DECL_ #endif #ifdef __cplusplus } #endif -#endif /* KIT_TIME_H */ +#endif diff --git a/source/test/unittests/atomic.test.c b/source/test/unittests/atomic.test.c index c909cb0..b8cf0ac 100644 --- a/source/test/unittests/atomic.test.c +++ b/source/test/unittests/atomic.test.c @@ -1,5 +1,5 @@ #include "../../kit/atomic.h" -#include "../../kit/threads.h" +#include "../../kit/thread.h" #define KIT_TEST_FILE atomic #include "../../kit_test/test.h" diff --git a/source/test/unittests/condition_variable.test.c b/source/test/unittests/condition_variable.test.c index c855b0c..c5b0468 100644 --- a/source/test/unittests/condition_variable.test.c +++ b/source/test/unittests/condition_variable.test.c @@ -1,4 +1,5 @@ #include "../../kit/condition_variable.h" +#include "../../kit/thread.h" #define KIT_TEST_FILE condition_variable #include "../../kit_test/test.h" diff --git a/source/test/unittests/mersenne_twister_64.test.c b/source/test/unittests/mersenne_twister_64.test.c index b986c55..80d1c2c 100644 --- a/source/test/unittests/mersenne_twister_64.test.c +++ b/source/test/unittests/mersenne_twister_64.test.c @@ -1,4 +1,5 @@ #include "../../kit/mersenne_twister_64.h" +#include "../../kit/secure_random.h" #define KIT_TEST_FILE mersenne_twister_64 #include "../../kit_test/test.h" @@ -6,7 +7,8 @@ enum { SIZE = 1000 }; TEST("mt64 same seeds") { - uint64_t seed = mt64_seed(); + uint64_t seed; + secure_random(sizeof seed, &seed); mt64_state_t foo, bar; mt64_init(&foo, seed); diff --git a/source/test/unittests/mutex.test.c b/source/test/unittests/mutex.test.c index a898557..e597e59 100644 --- a/source/test/unittests/mutex.test.c +++ b/source/test/unittests/mutex.test.c @@ -1,4 +1,5 @@ #include "../../kit/mutex.h" +#include "../../kit/thread.h" #define KIT_TEST_FILE mutex #include "../../kit_test/test.h" diff --git a/source/test/unittests/secure_random.test.c b/source/test/unittests/secure_random.test.c index eadae65..ec0a7b2 100644 --- a/source/test/unittests/secure_random.test.c +++ b/source/test/unittests/secure_random.test.c @@ -8,13 +8,9 @@ TEST("secure random") { int v[20]; memset(v, 0, sizeof v); - secure_random_init(); - secure_random(40, v); secure_random(40, v + 10); - secure_random_cleanup(); - int repeats = 0; for (int i = 1; i < sizeof v / sizeof *v; i++) diff --git a/source/test/unittests/thread.test.c b/source/test/unittests/thread.test.c index 786e992..01198c2 100644 --- a/source/test/unittests/thread.test.c +++ b/source/test/unittests/thread.test.c @@ -1,4 +1,4 @@ -#include "../../kit/threads.h" +#include "../../kit/thread.h" #define KIT_TEST_FILE thread #include "../../kit_test/test.h" -- cgit v1.2.3