diff --git a/tests/mm_seq_cst.c b/tests/mm_seq_cst.c new file mode 100644 index 0000000..0d0260e --- /dev/null +++ b/tests/mm_seq_cst.c @@ -0,0 +1,115 @@ + +#include +#include +#include + +#if 1 + enum memory_order store_memory_order = memory_order_acquire; + enum memory_order load_memory_order = memory_order_release; +#else + enum memory_order store_memory_order = memory_order_seq_cst; + enum memory_order load_memory_order = memory_order_seq_cst; +#endif + +// Scheduler on linux has too coarse granularity +// to allow a thread to a very tiny bit of work +// before switching to another thread. If we +// introduce an arbitrary delay, the threads will +// be able to be run in a random order better. +// Without it, the threads seem to be run as they are +// created, i.e. the scheduling is too convenient and +// hides a potential bug that we're testing for here +#define delay_that_works 1000000 +#define arbitrary_delay(n) for(volatile int i = 0; i < n; ++i) + +// This is all the explicit shared state between threads. +struct Shared_State typedef Shared_State; +struct Shared_State { + _Atomic(int) x; + _Atomic(int) y; + _Atomic(int) cnt; +}; +static Shared_State g_shared_state = {0}; + +// Sets the atomic flag x +int tx(void *ctx) { + arbitrary_delay(delay_that_works); + Shared_State *ss = ctx; + char msg[] = "Thread tx running\n"; + fwrite(msg, 1, sizeof msg, stdout); + atomic_store_explicit(&ss->x, 1, store_memory_order); + return 0; +} + +// Set the atomic flag y +int ty(void *ctx) { + arbitrary_delay(delay_that_works); + Shared_State *ss = ctx; + char msg[] = "Thread ty running\n"; + fwrite(msg, 1, sizeof msg, stdout); + atomic_store_explicit(&ss->y, 1, store_memory_order); + return 0; +} + +// Spinlock until flag x is set, if y is also set, add 1 to cnt +int t1(void *ctx) { + arbitrary_delay(delay_that_works); + Shared_State *ss = ctx; + char msg[] = "Thread t1 running\n"; + fwrite(msg, 1, sizeof msg, stdout); + while(atomic_load_explicit(&ss->x, load_memory_order) == 0) + ; + if(atomic_load_explicit(&ss->y, load_memory_order) == 1) { + atomic_fetch_add_explicit(&ss->cnt, 1, memory_order_relaxed); + } + return 0; +} + +// Spinlock until flag y is set, if x is also set, add 1 to cnt +int t2(void *ctx) { + arbitrary_delay(delay_that_works); + Shared_State *ss = ctx; + char msg[] = "Thread t2 running\n"; + fwrite(msg, 1, sizeof msg, stdout); + while(atomic_load_explicit(&ss->y, load_memory_order) == 0) + ; + if(atomic_load_explicit(&ss->x, load_memory_order) == 1) { + atomic_fetch_add_explicit(&ss->cnt, 1, memory_order_relaxed); + } + return 0; +} + +int main() { + // TODO: rewrite this to run in a loop after I + // fix stack leaking on thread destruction + thrd_start_t thread_funcs[4] = {tx,ty,t1,t2}; + thrd_t threads[4]; + // Spawn the threads + for(int i = 0; i < 4; ++i) { + int status = thrd_create(&threads[i], thread_funcs[i], &g_shared_state); + if(status != thrd_success) { + char msg[] = "Thread unable to start\n"; + fwrite(msg, 1, sizeof msg, stdout); + } + } + // Wait for threads to complete + for(int i = 0; i < 4; ++i) { + int _exit_code; + thrd_join(threads[i], &_exit_code); + } + // Check to see the cnt variable + int result = atomic_load_explicit(&g_shared_state.cnt, memory_order_relaxed); + if(result == 0) { + char msg[] = "Result was 0\n"; + fwrite(msg, 1, sizeof msg, stdout); + } + else if(result == 1) { + char msg[] = "Result was 1\n"; + fwrite(msg, 1, sizeof msg, stdout); + } + else { + char msg[] = "Result was 2\n"; + fwrite(msg, 1, sizeof msg, stdout); + } + return 0; +}