diff --git a/core/atomics.odin b/core/atomics.odin new file mode 100644 index 00000000000..ec7900f0d69 --- /dev/null +++ b/core/atomics.odin @@ -0,0 +1,100 @@ +// TODO(bill): Use assembly instead here to implement atomics +// Inline vs external file? + +#import win32 "sys/windows.odin" when ODIN_OS == "windows"; +_ := compile_assert(ODIN_ARCH == "amd64"); // TODO(bill): x86 version + + +yield_thread :: proc() { win32.mm_pause(); } +mfence :: proc() { win32.ReadWriteBarrier(); } +sfence :: proc() { win32.WriteBarrier(); } +lfence :: proc() { win32.ReadBarrier(); } + + +load :: proc(a: ^i32) -> i32 { + return a^; +} +store :: proc(a: ^i32, value: i32) { + a^ = value; +} +compare_exchange :: proc(a: ^i32, expected, desired: i32) -> i32 { + return win32.InterlockedCompareExchange(a, desired, expected); +} +exchanged :: proc(a: ^i32, desired: i32) -> i32 { + return win32.InterlockedExchange(a, desired); +} +fetch_add :: proc(a: ^i32, operand: i32) -> i32 { + return win32.InterlockedExchangeAdd(a, operand); + +} +fetch_and :: proc(a: ^i32, operand: i32) -> i32 { + return win32.InterlockedAnd(a, operand); +} +fetch_or :: proc(a: ^i32, operand: i32) -> i32 { + return win32.InterlockedOr(a, operand); +} +spin_lock :: proc(a: ^i32, time_out: int) -> bool { // NOTE(bill) time_out = -1 as default + old_value := compare_exchange(a, 1, 0); + counter := 0; + for old_value != 0 && (time_out < 0 || counter < time_out) { + counter++; + yield_thread(); + old_value = compare_exchange(a, 1, 0); + mfence(); + } + return old_value == 0; +} +spin_unlock :: proc(a: ^i32) { + store(a, 0); + mfence(); +} +try_acquire_lock :: proc(a: ^i32) -> bool { + yield_thread(); + old_value := compare_exchange(a, 1, 0); + mfence(); + return old_value == 0; +} + + +load :: proc(a: ^i64) -> i64 { + return a^; +} +store :: proc(a: ^i64, value: i64) { + a^ = value; +} +compare_exchange :: proc(a: ^i64, expected, desired: i64) -> i64 { + return win32.InterlockedCompareExchange64(a, desired, expected); +} +exchanged :: proc(a: ^i64, desired: i64) -> i64 { + return win32.InterlockedExchange64(a, desired); +} +fetch_add :: proc(a: ^i64, operand: i64) -> i64 { + return win32.InterlockedExchangeAdd64(a, operand); +} +fetch_and :: proc(a: ^i64, operand: i64) -> i64 { + return win32.InterlockedAnd64(a, operand); +} +fetch_or :: proc(a: ^i64, operand: i64) -> i64 { + return win32.InterlockedOr64(a, operand); +} +spin_lock :: proc(a: ^i64, time_out: int) -> bool { // NOTE(bill) time_out = -1 as default + old_value := compare_exchange(a, 1, 0); + counter := 0; + for old_value != 0 && (time_out < 0 || counter < time_out) { + counter++; + yield_thread(); + old_value = compare_exchange(a, 1, 0); + mfence(); + } + return old_value == 0; +} +spin_unlock :: proc(a: ^i64) { + store(a, 0); + mfence(); +} +try_acquire_lock :: proc(a: ^i64) -> bool { + yield_thread(); + old_value := compare_exchange(a, 1, 0); + mfence(); + return old_value == 0; +} diff --git a/core/sync_linux.odin b/core/sync_linux.odin new file mode 100644 index 00000000000..4b0e0da2ef3 --- /dev/null +++ b/core/sync_linux.odin @@ -0,0 +1,93 @@ +#import "atomics.odin"; +#import "os.odin"; + +Semaphore :: struct { + // _handle: win32.Handle, +} + +Mutex :: struct { + _semaphore: Semaphore, + _counter: i32, + _owner: i32, + _recursion: i32, +} + +current_thread_id :: proc() -> i32 { + return i32(os.current_thread_id()); +} + +semaphore_init :: proc(s: ^Semaphore) { + // s._handle = win32.CreateSemaphoreA(nil, 0, 1<<31-1, nil); +} + +semaphore_destroy :: proc(s: ^Semaphore) { + // win32.CloseHandle(s._handle); +} + +semaphore_post :: proc(s: ^Semaphore, count: int) { + // win32.ReleaseSemaphore(s._handle, cast(i32)count, nil); +} + +semaphore_release :: proc(s: ^Semaphore) #inline { + semaphore_post(s, 1); +} + +semaphore_wait :: proc(s: ^Semaphore) { + // win32.WaitForSingleObject(s._handle, win32.INFINITE); +} + + +mutex_init :: proc(m: ^Mutex) { + atomics.store(&m._counter, 0); + atomics.store(&m._owner, current_thread_id()); + semaphore_init(&m._semaphore); + m._recursion = 0; +} +mutex_destroy :: proc(m: ^Mutex) { + semaphore_destroy(&m._semaphore); +} +mutex_lock :: proc(m: ^Mutex) { + thread_id := current_thread_id(); + if atomics.fetch_add(&m._counter, 1) > 0 { + if thread_id != atomics.load(&m._owner) { + semaphore_wait(&m._semaphore); + } + } + atomics.store(&m._owner, thread_id); + m._recursion++; +} +mutex_try_lock :: proc(m: ^Mutex) -> bool { + thread_id := current_thread_id(); + if atomics.load(&m._owner) == thread_id { + atomics.fetch_add(&m._counter, 1); + } else { + expected: i32 = 0; + if atomics.load(&m._counter) != 0 { + return false; + } + if atomics.compare_exchange(&m._counter, expected, 1) == 0 { + return false; + } + atomics.store(&m._owner, thread_id); + } + m._recursion++; + return true; +} +mutex_unlock :: proc(m: ^Mutex) { + recursion: i32; + thread_id := current_thread_id(); + assert(thread_id == atomics.load(&m._owner)); + + m._recursion--; + recursion = m._recursion; + if recursion == 0 { + atomics.store(&m._owner, thread_id); + } + + if atomics.fetch_add(&m._counter, -1) > 1 { + if recursion == 0 { + semaphore_release(&m._semaphore); + } + } +} + diff --git a/core/sync_windows.odin b/core/sync_windows.odin new file mode 100644 index 00000000000..ad64746f534 --- /dev/null +++ b/core/sync_windows.odin @@ -0,0 +1,91 @@ +#import win32 "sys/windows.odin" when ODIN_OS == "windows"; +#import "atomics.odin"; + +Semaphore :: struct { + _handle: win32.Handle, +} + +Mutex :: struct { + _semaphore: Semaphore, + _counter: i32, + _owner: i32, + _recursion: i32, +} + +current_thread_id :: proc() -> i32 { + return i32(win32.GetCurrentThreadId()); +} + +semaphore_init :: proc(s: ^Semaphore) { + s._handle = win32.CreateSemaphoreA(nil, 0, 1<<31-1, nil); +} + +semaphore_destroy :: proc(s: ^Semaphore) { + win32.CloseHandle(s._handle); +} + +semaphore_post :: proc(s: ^Semaphore, count: int) { + win32.ReleaseSemaphore(s._handle, i32(count), nil); +} + +semaphore_release :: proc(s: ^Semaphore) #inline { semaphore_post(s, 1); } + +semaphore_wait :: proc(s: ^Semaphore) { + win32.WaitForSingleObject(s._handle, win32.INFINITE); +} + + +mutex_init :: proc(m: ^Mutex) { + atomics.store(&m._counter, 0); + atomics.store(&m._owner, current_thread_id()); + semaphore_init(&m._semaphore); + m._recursion = 0; +} +mutex_destroy :: proc(m: ^Mutex) { + semaphore_destroy(&m._semaphore); +} +mutex_lock :: proc(m: ^Mutex) { + thread_id := current_thread_id(); + if atomics.fetch_add(&m._counter, 1) > 0 { + if thread_id != atomics.load(&m._owner) { + semaphore_wait(&m._semaphore); + } + } + atomics.store(&m._owner, thread_id); + m._recursion++; +} +mutex_try_lock :: proc(m: ^Mutex) -> bool { + thread_id := current_thread_id(); + if atomics.load(&m._owner) == thread_id { + atomics.fetch_add(&m._counter, 1); + } else { + expected: i32 = 0; + if atomics.load(&m._counter) != 0 { + return false; + } + if atomics.compare_exchange(&m._counter, expected, 1) == 0 { + return false; + } + atomics.store(&m._owner, thread_id); + } + m._recursion++; + return true; +} +mutex_unlock :: proc(m: ^Mutex) { + recursion: i32; + thread_id := current_thread_id(); + assert(thread_id == atomics.load(&m._owner)); + + m._recursion--; + recursion = m._recursion; + if recursion == 0 { + atomics.store(&m._owner, thread_id); + } + + if atomics.fetch_add(&m._counter, -1) > 1 { + if recursion == 0 { + semaphore_release(&m._semaphore); + } + } +} +