From da65de2d0107bafda6a87b164e9e4111f98e2a73 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Tue, 10 Jan 2023 18:54:42 +0100 Subject: [PATCH] Add compare_exchange. Rudimentary threads (subject to change) --- lib/std/core/mem.c3 | 25 +- lib/std/libc/libc.c3 | 17 +- lib/std/threads/os/thread_posix.c3 | 239 ++++++++++++++++++ lib/std/threads/os/thread_win32.c3 | 351 +++++++++++++++++++++++++++ lib/std/threads/thread.c3 | 73 ++++++ src/compiler/enums.h | 1 + src/compiler/llvm_codegen_builtins.c | 53 ++++ src/compiler/sema_builtins.c | 52 ++++ src/compiler/sema_name_resolution.c | 2 +- src/compiler/sema_types.c | 1 + src/compiler/symtab.c | 1 + src/version.h | 2 +- test/test_suite/stdlib/map.c3t | 2 +- test/test_suite2/stdlib/map.c3t | 2 +- 14 files changed, 814 insertions(+), 7 deletions(-) create mode 100644 lib/std/threads/os/thread_posix.c3 create mode 100644 lib/std/threads/os/thread_win32.c3 create mode 100644 lib/std/threads/thread.c3 diff --git a/lib/std/core/mem.c3 b/lib/std/core/mem.c3 index 51d0eaa56..972295212 100644 --- a/lib/std/core/mem.c3 +++ b/lib/std/core/mem.c3 @@ -3,16 +3,37 @@ // a copy of which can be found in the LICENSE_STDLIB file. module std::core::mem; -macro @volatile_load(&x) +macro @volatile_load(&x) @builtin { return $$volatile_load(&x); } -macro @volatile_store(&x, y) +macro @volatile_store(&x, y) @builtin { return $$volatile_store(&x, y); } +enum AtomicOrdering : int +{ + NOT_ATOMIC, // Not atomic + UNORDERED, // No lock + MONOTONIC, // Consistent ordering + AQUIRE, // Barrier locking load/store + RELEASE, // Barrier releasing load/store + AQUIRE_RELEASE, // Barrier fence to load/store + SEQ_CONSISTENT, // Aquire semantics, ordered with other seq_consistent +} + +macro compare_exchange(ptr, compare, value, AtomicOrdering $success, AtomicOrdering $failure, bool $volatile = true, bool $weak = false, usz $alignment = 0) +{ + return $$compare_exchange(ptr, compare, value, $volatile, $weak, $success.ordinal, $failure.ordinal, $alignment); +} + +macro compare_exchange_volatile(ptr, compare, value, AtomicOrdering $success, AtomicOrdering $failure) +{ + return compare_exchange(ptr, compare, value, $success, $failure, true); +} + /** * @require math::is_power_of_2(alignment) **/ diff --git a/lib/std/libc/libc.c3 b/lib/std/libc/libc.c3 index 87e48b0f4..03e0afeaf 100644 --- a/lib/std/libc/libc.c3 +++ b/lib/std/libc/libc.c3 @@ -218,6 +218,18 @@ struct Tm char *tm_zone; /* timezone abbreviation */ } +struct TimeSpec +{ + Time s; + ulong ns; +} + +const int TIME_UTC = 1; + + +extern fn int timespec_get(TimeSpec* ts, int base); +extern fn int nanosleep(TimeSpec* req, TimeSpec* remaining); + // Likely wrong, must be per platform. const CLOCKS_PER_SEC = 1000000; @@ -243,6 +255,7 @@ extern fn SignalFunction signal(int sig, SignalFunction function); module libc::errno; +const Errno OK = 0; const Errno EPERM = 1; // Operation not permitted const Errno ENOENT = 2; // No such file or directory const Errno ESRCH = 3; // No such process @@ -385,24 +398,26 @@ const Errno EISCONN = 106; /* Transport endpoint is already connected */ const Errno ENOTCONN = 107; /* Transport endpoint is not connected */ const Errno ESHUTDOWN = 108; /* Cannot send after transport endpoint shutdown */ const Errno ETOOMANYREFS = 109; /* Too many references: cannot splice */ -const Errno ETIMEDOUT = 110; /* Connection timed out */ const Errno ECONNREFUSED = 111; /* Connection refused */ const Errno EHOSTDOWN = 112; /* Host is down */ const Errno EHOSTUNREACH = 113; /* No route to host */ */ $if (env::OS_TYPE == OsType.MACOSX): +const Errno ETIMEDOUT = 60; // Connection timed out const Errno EINPROGRESS = 36; // Operation now in progress MacOS const Errno EALREADY = 37; // Operation already in progress MacOS const Errno EDQUOT = 69; // Quota exceeded, MacOS const Errno EWOULDBLOCK = 35; // Operation would block $elif (env::OS_TYPE == OsType.WIN32): +const Errno ETIMEDOUT = 138; // Connection timed out const Errno EALREADY = 103; // Operation already in progress const Errno EINPROGRESS = 112; // Operation now in progress Win32 const Errno EDQUOT = -122; // Quota exceeded, not in Win32 const Errno EWOULDBLOCK = 140; // Operation would block $else: +const Errno ETIMEDOUT = 110; // Connection timed out const Errno EALREADY = 114; // Operation already in progress const Errno EINPROGRESS = 115; // Operation now in progress const Errno EDQUOT = 122; // Quota exceeded diff --git a/lib/std/threads/os/thread_posix.c3 b/lib/std/threads/os/thread_posix.c3 new file mode 100644 index 000000000..12ae42317 --- /dev/null +++ b/lib/std/threads/os/thread_posix.c3 @@ -0,0 +1,239 @@ +module std::thread::os; +import libc; + +$if (thread::THREAD_MODEL == ThreadModel.POSIX): + +const PTHREAD_MUTEX_NORMAL = 0; +const PTHREAD_MUTEX_ERRORCHECK = 1; +const PTHREAD_MUTEX_RECURSIVE = 2; + +define NativeMutex = PthreadMutex; +define NativeConditionVariable = PthreadCond; +define NativeThread = Pthread; +define NativeOnceFlag = PthreadOnce; + +define Pthread = distinct void*; + +$if (env::OS_TYPE == OsType.LINUX): +define PthreadMutex = distinct ulong[5]; +define PthreadAttribute = distinct ulong[7]; +define PthreadMutexAttribute = distinct uint; +define PthreadCondAttribute = distinct uint; +define PthreadCond = distinct ulong[6]; +define PthreadOnce = distinct uint; +$else: +define PthreadMutex = distinct ulong[8]; +define PthreadMutexAttribute = distinct ulong[2]; +define PthreadAttribute = distinct ulong[8]; +define PthreadCond = distinct ulong[6]; +define PthreadCondAttribute = distinct ulong[8]; +define PthreadOnce = distinct ulong[2]; +$endif; + +define PosixThreadFn = fn void*(void*); + +extern fn int pthread_attr_destroy(PthreadAttribute*); +extern fn int pthread_attr_getdetachstate(PthreadAttribute*, int*); +extern fn int pthread_attr_init(PthreadAttribute*); +extern fn int pthread_mutex_init(PthreadMutex*, PthreadMutexAttribute*); +extern fn int pthread_mutex_destroy(PthreadMutex*); +extern fn int pthread_mutex_lock(PthreadMutex*); +extern fn int pthread_mutexattr_init(PthreadMutexAttribute*); +extern fn int pthread_mutexattr_destroy(PthreadMutexAttribute*); +extern fn int pthread_mutexattr_settype(PthreadMutexAttribute*, int); +extern fn int pthread_cond_init(PthreadCond*, PthreadCondAttribute*); +extern fn int pthread_cond_destroy(PthreadCond*); +extern fn int pthread_detach(Pthread); +extern fn int pthread_join(Pthread, void**); +extern fn int pthread_create(Pthread*, PthreadAttribute*, PosixThreadFn, void*); +extern fn Pthread pthread_self(); +extern fn Errno pthread_mutex_trylock(PthreadMutex*); +extern fn Errno pthread_mutex_unlock(PthreadMutex*); +extern fn Errno pthread_cond_signal(PthreadCond*); +extern fn Errno pthread_cond_broadcast(PthreadCond*); +extern fn Errno pthread_cond_wait(PthreadCond*, PthreadMutex*); +extern fn Errno pthread_cond_timedwait(PthreadCond*, PthreadMutex*, TimeSpec*); +extern fn void pthread_exit(void*); +extern fn void pthread_once(PthreadOnce*, OnceFn); +extern fn int pthread_equal(Pthread this, Pthread other); +extern fn int sched_yield(); + +fn void! NativeMutex.init(NativeMutex* mutex, MutexType type) +{ + PthreadMutexAttribute attr; + if (pthread_mutexattr_init(&attr)) return ThreadFault.INIT_FAILED!; + defer pthread_mutexattr_destroy(&attr); + if (type & thread::MUTEX_RECURSIVE) + { + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) return ThreadFault.INIT_FAILED!; + } + if (pthread_mutex_init(mutex, &attr)) return ThreadFault.INIT_FAILED!; +} + +fn void! NativeMutex.destroy(NativeMutex* mtx) +{ + if (pthread_mutex_destroy(mtx)) return ThreadFault.DESTROY_FAILED!; +} + +fn void! NativeMutex.lock(NativeMutex* mtx) +{ + if (pthread_mutex_lock(mtx)) return ThreadFault.LOCK_FAILED!; +} + +fn void! NativeMutex.lock_timeoutout(NativeMutex* mtx, ulong ms) +{ + /* Try to acquire the lock and, if we fail, sleep for 5ms. */ + Errno result; + while ((result = pthread_mutex_trylock(mtx)) == errno::EBUSY) + { + if (!ms) break; + ulong sleep = min(5, ms); + if (!libc::nanosleep(&& TimeSpec { .s = 0, .ns = sleep * 1000_000 }, null)) return ThreadFault.LOCK_FAILED!; + ms -= sleep; + } + switch (result) + { + case errno::OK: + return; + case errno::EBUSY: + case errno::ETIMEDOUT: + return ThreadFault.LOCK_TIMEOUT!; + default: + return ThreadFault.LOCK_FAILED!; + } +} + +fn bool NativeMutex.try_lock(NativeMutex* mtx) +{ + return !pthread_mutex_trylock(mtx); +} + +fn void! NativeMutex.unlock(NativeMutex* mtx) +{ + if (pthread_mutex_unlock(mtx)) return ThreadFault.UNLOCK_FAILED!; +} + +fn void! NativeConditionVariable.init(NativeConditionVariable* cond) +{ + if (pthread_cond_init(cond, null)) return ThreadFault.INIT_FAILED!; +} + +fn void! NativeConditionVariable.destroy(NativeConditionVariable* cond) +{ + if (pthread_cond_destroy(cond)) return ThreadFault.DESTROY_FAILED!; +} + +fn void! NativeConditionVariable.signal(NativeConditionVariable* cond) +{ + if (pthread_cond_signal(cond)) return ThreadFault.SIGNAL_FAILED!; +} + +fn void! NativeConditionVariable.broadcast(NativeConditionVariable* cond) +{ + if (pthread_cond_broadcast(cond)) return ThreadFault.SIGNAL_FAILED!; +} + +fn void! NativeConditionVariable.wait(NativeConditionVariable* cond, NativeMutex* mtx) +{ + if (pthread_cond_wait(cond, mtx)) return ThreadFault.WAIT_FAILED!; +} + +fn void! NativeConditionVariable.wait_timeout(NativeConditionVariable* cond, NativeMutex* mtx, ulong ms) +{ + TimeSpec now; + if (libc::timespec_get(&now, libc::TIME_UTC) != libc::TIME_UTC) return ThreadFault.WAIT_FAILED!; + now.s += ms / 1000; + now.ns += (ms % 1000) * 1000_000; + switch (pthread_cond_timedwait(cond, mtx, &now)) + { + case errno::ETIMEDOUT: + return ThreadFault.WAIT_TIMEOUT!; + case errno::OK: + return; + default: + return ThreadFault.WAIT_FAILED!; + } +} + + +private fn void* callback(void* arg) +{ + PosixThreadData *data = arg; + return (void*)(iptr)data.thread_fn(data.arg); +} + +fn void! NativeThread.create(NativeThread* thread, ThreadFn thread_fn, void* arg) +{ + PosixThreadData *thread_data = mem::alloc(PosixThreadData); + *thread_data = { .thread_fn = thread_fn, .arg = arg }; + if (pthread_create(thread, null, &callback, thread_data) != 0) + { + *thread = null; + free(thread_data); + return ThreadFault.INIT_FAILED!; + } +} + +fn void! NativeThread.detach(NativeThread thread) +{ + if (!pthread_detach(thread)) return ThreadFault.DETACH_FAILED!; +} + +fn void native_thread_exit(int result) +{ + pthread_exit((void*)(iptr)result); +} + +fn NativeThread native_thread_current() +{ + return pthread_self(); +} + +fn bool NativeThread.equals(NativeThread this, NativeThread other) +{ + return (bool)pthread_equal(this, other); +} + +fn int! NativeThread.join(NativeThread thread) +{ + void *pres; + if (pthread_join(thread, &pres)) return ThreadFault.JOIN_FAILED!; + return (int)(iptr)pres; +} + +fn void NativeOnceFlag.call_once(NativeOnceFlag* flag, OnceFn func) +{ + pthread_once(flag, func); +} + +fn void native_thread_yield() +{ + sched_yield(); +} + +private struct PosixThreadData +{ + ThreadFn thread_fn; + void* arg; +} + +fn void! native_sleep_nano(ulong nano) +{ + TimeSpec to = { .s = 0, .ns = nano }; + if (libc::nanosleep(&to, null)) return ThreadFault.INTERRUPTED!; +} + +fn void! native_sleep_ms(ulong ms) +{ + TimeSpec to = { .s = ms / 1000, .ns = (ms % 1000) * 1000_000 }; + if (libc::nanosleep(&to, null)) return ThreadFault.INTERRUPTED!; +} + +fn void! native_sleep(double s) +{ + ulong si = (ulong)s; + TimeSpec to = { .s = si, .ns = (ulong)((s - si) * 1000_000_000) }; + if (libc::nanosleep(&to, null)) return ThreadFault.INTERRUPTED!; +} + +$endif; \ No newline at end of file diff --git a/lib/std/threads/os/thread_win32.c3 b/lib/std/threads/os/thread_win32.c3 new file mode 100644 index 000000000..b9b24cb36 --- /dev/null +++ b/lib/std/threads/os/thread_win32.c3 @@ -0,0 +1,351 @@ +module std::thread::os; + +$if (thread::THREAD_MODEL == ThreadModel.WIN32): + +define NativeThread = Win32_HANDLE; +define Win32_CRITICAL_SECTION = distinct ulong[5]; +define Win32_HANDLE = distinct ulong; + +struct NativeMutex +{ + union + { + Win32_CRITICAL_SECTION critical_section; + Win32_HANDLE handle; + } + bool already_locked; + bool recursive; + bool timed; +} + +struct NativeOnceFlag +{ + int status; + Win32_CRITICAL_SECTION lock; +} + +struct NativeConditionVariable +{ + union + { + struct + { + Win32_HANDLE event_one; + Win32_HANDLE event_all; + } + Win32_HANDLE[2] events; + } + uint waiters_count; + Win32_CRITICAL_SECTION waiters_count_lock; +} + +extern fn void win32_InitializeCriticalSection(Win32_CRITICAL_SECTION* section) @extname("InitializeCriticalSection"); +extern fn void win32_DeleteCriticalSection(Win32_CRITICAL_SECTION* section) @extname("DeleteCriticalSection"); +extern fn Win32_HANDLE win32_CreateMutex(void*, bool, void*) @extname("CreateMutexA"); +extern fn bool win32_CloseHandle(Win32_HANDLE) @extname("CloseHandle"); +extern fn bool win32_ReleaseMutex(Win32_HANDLE) @extname("ReleaseMutex"); +extern fn void win32_EnterCriticalSection(Win32_CRITICAL_SECTION* section) @extname("EnterCriticalSection"); +extern fn void win32_LeaveCriticalSection(Win32_CRITICAL_SECTION* section) @extname("LeaveCriticalSection"); +extern fn bool win32_TryEnterCriticalSection(Win32_CRITICAL_SECTION* section) @extname("TryEnterCriticalSection"); +extern fn uint win32_WaitForSingleObject(Win32_HANDLE, uint milliseconds) @extname("WaitForSingleObject"); +extern fn void win32_Sleep(uint ms) @extname("Sleep"); +extern fn uint win32_WaitForMultipleObjects(uint count, Win32_HANDLE* handles, bool wait_all, uint ms) @extname("WaitForMultipleObjects"); +extern fn Win32_HANDLE win32_CreateEventA(void* attributes, bool manual_reset, bool initial_state, char* name) @extname("CreateEventA"); +extern fn bool win32_ResetEvent(Win32_HANDLE event) @extname("ResetEvent"); +extern fn bool win32_SetEvent(Win32_HANDLE handle) @extname("SetEvent"); +extern fn long win32_InterlockedCompareExchange(int* dest, int exchange, int comperand) @extname("InterlockedCompareExchange"); +extern fn uint win32_SleepEx(uint ms, bool alertable) @extname("SleepEx"); +extern fn Win32_HANDLE win32_CreateThread(void* attributes, usz stack, ThreadFn func, void* arg, uint flags, uint* thread_id) @extname("CreateThread"); +extern fn bool win32_GetExitCodeThread(Win32_HANDLE handle, uint* exit_code) @extname("GetExitCodeThread"); +extern fn uint win32_GetThreadId(Win32_HANDLE) @extname("GetThreadId"); +extern fn void win32_ExitThread(uint res) @noreturn @extname("ExitThread"); +extern fn Win32_HANDLE win32_GetCurrentThread() @extname("GetCurrentThread"); + +const uint WAIT_OBJECT_0 = 0; +const uint WAIT_ABANDONED = 128; +const uint WAIT_TIMEOUT = 258; +const uint WAIT_IO_COMPLETION = 192; +const uint WAIT_FAILED = (uint)-1; +const uint INFINITE = (uint)-1; + +fn void! NativeMutex.init(NativeMutex* mtx, MutexType type) +{ + mtx.already_locked = false; + mtx.recursive = (bool)(type & thread::MUTEX_RECURSIVE); + mtx.timed = (bool)(type & thread::MUTEX_TIMED); + if (!mtx.timed) + { + win32_InitializeCriticalSection(&(mtx.critical_section)); + return; + } + if (!(mtx.handle = win32_CreateMutex(null, false, null))) return ThreadFault.INIT_FAILED!; +} + +fn void! NativeMutex.destroy(NativeMutex* mtx) +{ + if (!mtx.timed) + { + win32_DeleteCriticalSection(&mtx.critical_section); + return; + } + if (!win32_CloseHandle(mtx.handle)) return ThreadFault.DESTROY_FAILED!; +} + +fn void! NativeMutex.lock(NativeMutex* mtx) +{ + if (!mtx.timed) + { + win32_EnterCriticalSection(&mtx.critical_section); + } + else + { + switch (win32_WaitForSingleObject(mtx.handle, INFINITE)) + { + case WAIT_OBJECT_0: + break; + case WAIT_ABANDONED: + default: + return ThreadFault.LOCK_FAILED!; + + } + } + if (!mtx.recursive) + { + while (mtx.already_locked) win32_Sleep(1); + } + mtx.already_locked = true; +} + + +/** + * @require mtx.timed "Only available for timed locks" + **/ +fn void! NativeMutex.lock_timeout(NativeMutex* mtx, uint ms) +{ + switch (win32_WaitForSingleObject(mtx.handle, ms)) + { + case WAIT_OBJECT_0: + break; + case WAIT_TIMEOUT: + return ThreadFault.LOCK_TIMEOUT!; + case WAIT_ABANDONED: + default: + return ThreadFault.LOCK_FAILED!; + } + if (!mtx.recursive) + { + while (mtx.already_locked) win32_Sleep(1); + mtx.already_locked = true; + } +} + +fn bool NativeMutex.try_lock(NativeMutex* mtx) +{ + bool success = mtx.timed + ? win32_WaitForSingleObject(mtx.handle, 0) == WAIT_OBJECT_0 + : win32_TryEnterCriticalSection(&mtx.critical_section); + + if (!success) return false; + if (!mtx.recursive) + { + if (mtx.already_locked) + { + assert(!mtx.timed); + win32_LeaveCriticalSection(&mtx.critical_section); + return false; + } + mtx.already_locked = true; + } + return true; +} + +fn void! NativeMutex.unlock(NativeMutex* mtx) +{ + mtx.already_locked = false; + if (!mtx.timed) + { + win32_LeaveCriticalSection(&mtx.critical_section); + return; + } + if (!win32_ReleaseMutex(mtx.handle)) return ThreadFault.UNLOCK_FAILED!; +} + +const int CONDITION_EVENT_ONE = 0; +const int CONDITION_EVENT_ALL = 1; + +fn void! NativeConditionVariable.init(NativeConditionVariable* cond) +{ + cond.waiters_count = 0; + win32_InitializeCriticalSection(&cond.waiters_count_lock); + cond.event_one = win32_CreateEventA(null, false, false, null); + if (!cond.event_one) + { + cond.event_all = (Win32_HANDLE)0; + return ThreadFault.INIT_FAILED!; + } + cond.event_all = win32_CreateEventA(null, true, false, null); + if (!cond.event_all) + { + win32_CloseHandle(cond.event_one); + cond.event_one = (Win32_HANDLE)0; + return ThreadFault.INIT_FAILED!; + } +} + +fn void! NativeConditionVariable.destroy(NativeConditionVariable* cond) @maydiscard +{ + if (cond.event_one) win32_CloseHandle(cond.event_one); + if (cond.event_all) win32_CloseHandle(cond.event_all); + win32_DeleteCriticalSection(&cond.waiters_count_lock); +} + +fn void! NativeConditionVariable.signal(NativeConditionVariable* cond) +{ + win32_EnterCriticalSection(&cond.waiters_count_lock); + bool have_waiters = cond.waiters_count > 0; + win32_LeaveCriticalSection(&cond.waiters_count_lock); + if (have_waiters && !win32_SetEvent(cond.event_one)) return ThreadFault.SIGNAL_FAILED!; +} + +fn void! NativeConditionVariable.broadcast(NativeConditionVariable* cond) +{ + win32_EnterCriticalSection(&cond.waiters_count_lock); + bool have_waiters = cond.waiters_count > 0; + win32_LeaveCriticalSection(&cond.waiters_count_lock); + if (have_waiters && !win32_SetEvent(cond.event_all)) return ThreadFault.SIGNAL_FAILED!; +} + +private fn void! timedwait(NativeConditionVariable* cond, NativeMutex* mtx, uint timeout) +{ + win32_EnterCriticalSection(&cond.waiters_count_lock); + cond.waiters_count++; + win32_LeaveCriticalSection(&cond.waiters_count_lock); + + mtx.unlock()?; + + uint result = win32_WaitForMultipleObjects(2, &cond.events, false, timeout); + switch (result) + { + case WAIT_TIMEOUT: + mtx.lock()?; + return ThreadFault.WAIT_TIMEOUT!; + case WAIT_FAILED: + mtx.lock()?; + return ThreadFault.WAIT_FAILED!; + default: + break; + } + + win32_EnterCriticalSection(&cond.waiters_count_lock); + cond.waiters_count--; + // If event all && no waiters + bool last_waiter = result == 1 && !cond.waiters_count; + win32_LeaveCriticalSection(&cond.waiters_count_lock); + + if (last_waiter) + { + if (!win32_ResetEvent(cond.event_all)) + { + mtx.lock()?; + return ThreadFault.WAIT_FAILED!; + } + } + + mtx.lock()?; +} + +fn void! NativeConditionVariable.wait(NativeConditionVariable* cond, NativeMutex* mtx) @inline +{ + return timedwait(cond, mtx, INFINITE) @inline; +} + +fn void! NativeConditionVariable.wait_timeout(NativeConditionVariable* cond, NativeMutex* mtx, uint time) @inline +{ + return timedwait(cond, mtx, time) @inline; +} + +fn void! NativeThread.create(NativeThread* thread, ThreadFn func, void* args) +{ + if (!(*thread = win32_CreateThread(null, 0, func, args, 0, null))) return ThreadFault.INIT_FAILED!; +} + +fn void! NativeThread.detach(NativeThread thread) @inline +{ + if (!win32_CloseHandle(thread)) return ThreadFault.DETACH_FAILED!; +} + + +fn void native_thread_exit(int result) @inline +{ + win32_ExitThread((uint)result); +} + +fn void native_thread_yield() +{ + win32_Sleep(0); +} + +fn void NativeOnceFlag.call_once(NativeOnceFlag* flag, OnceFn func) +{ + while (@volatile_load(flag.status) < 3) + { + switch (@volatile_load(flag.status)) + { + case 0: + if (mem::compare_exchange_volatile(&flag.status, 1, 0, AtomicOrdering.SEQ_CONSISTENT, AtomicOrdering.SEQ_CONSISTENT) == 0) + { + win32_InitializeCriticalSection(&flag.lock); + win32_EnterCriticalSection(&flag.lock); + @volatile_store(flag.status, 2); + func(); + @volatile_store(flag.status, 3); + win32_LeaveCriticalSection(&flag.lock); + return; + } + break; + case 1: + break; + case 2: + win32_EnterCriticalSection(&flag.lock); + win32_LeaveCriticalSection(&flag.lock); + break; + } + } +} + +fn void! NativeThread.join(NativeThread thread, int *res) +{ + if (win32_WaitForSingleObject(thread, INFINITE) == WAIT_FAILED) return ThreadFault.JOIN_FAILED!; + if (!win32_GetExitCodeThread(thread, (uint*)res)) return ThreadFault.JOIN_FAILED!; + defer win32_CloseHandle(thread); +} + +fn NativeThread native_thread_current() +{ + return win32_GetCurrentThread(); +} + +fn bool NativeThread.equals(NativeThread this, NativeThread other) +{ + return win32_GetThreadId(this) == win32_GetThreadId(other); +} + +/** + * @require ms < uint.max "Too long sleep" + **/ +fn void! native_sleep_ms(ulong ms) +{ + if (win32_SleepEx((uint)ms, true) == WAIT_IO_COMPLETION) return ThreadFault.INTERRUPTED!; +} + +fn void! native_sleep(double s) +{ + return native_sleep_ms((uint)s * 1000); +} + +fn void! native_sleep_nano(ulong ns) +{ + return native_sleep_ms(ns < 1000_000 ? 1 : ns / 1000_000); +} + +$endif; \ No newline at end of file diff --git a/lib/std/threads/thread.c3 b/lib/std/threads/thread.c3 new file mode 100644 index 000000000..8d4ae2cf2 --- /dev/null +++ b/lib/std/threads/thread.c3 @@ -0,0 +1,73 @@ +module std::thread; + +enum ThreadModel +{ + WIN32, + POSIX +} +const ThreadModel THREAD_MODEL = env::OS_TYPE == OsType.WIN32 ? ThreadModel.WIN32 : ThreadModel.POSIX; + +define MutexType = distinct int; + +const MutexType MUTEX_PLAIN = 0; +const MutexType MUTEX_TIMED = 1; +const MutexType MUTEX_RECURSIVE = 2; + +define Mutex = distinct NativeMutex; +define ConditionVariable = distinct NativeConditionVariable; +define Thread = distinct NativeThread; +define OnceFlag = distinct NativeOnceFlag; +define OnceFn = fn void(); + +define ThreadFn = fn int(void* arg); + +fault ThreadFault +{ + INIT_FAILED, + DESTROY_FAILED, + LOCK_FAILED, + LOCK_TIMEOUT, + UNLOCK_FAILED, + SIGNAL_FAILED, + WAIT_FAILED, + WAIT_TIMEOUT, + DETACH_FAILED, + JOIN_FAILED, + INTERRUPTED, +} + +macro void! Mutex.init(Mutex* mutex, MutexType type) = NativeMutex.init((NativeMutex*)mutex, type); +macro void! Mutex.destroy(Mutex* mutex) = NativeMutex.destroy((NativeMutex*)mutex); +macro void! Mutex.lock(Mutex* mutex) = NativeMutex.lock((NativeMutex*)mutex); +macro void! Mutex.lock_timeout(Mutex* mutex, ulong ms) = NativeMutex.lock((NativeMutex*)mutex, ms); +macro bool Mutex.try_lock(Mutex* mutex) = NativeMutex.try_lock((NativeMutex*)mutex); +macro bool Mutex.unlock(Mutex* mutex) = NativeMutex.unlock((NativeMutex*)mutex); + +macro void! ConditionVariable.init(ConditionVariable* cond) = NativeConditionVariable.init((NativeConditionVariable*)cond); +macro void! ConditionVariable.destroy(ConditionVariable* cond) = NativeConditionVariable.destroy((NativeConditionVariable*)cond); +macro void! ConditionVariable.signal(ConditionVariable* cond) = NativeConditionVariable.signal((NativeConditionVariable*)cond); +macro void! ConditionVariable.broadcast(ConditionVariable* cond) = NativeConditionVariable.broadcast((NativeConditionVariable*)cond); +macro void! ConditionVariable.wait(ConditionVariable* cond, Mutex* mutex) +{ + return NativeConditionVariable.wait((NativeConditionVariable*)cond, (NativeMutex*)mutex); +} +macro void! ConditionVariable.wait_timeout(ConditionVariable* cond, Mutex* mutex, ulong timeout) +{ + return NativeConditionVariable.wait_timeout((NativeConditionVariable*)cond, (NativeMutex*)mutex, timeout); +} + + +macro void! Thread.create(Thread* thread, ThreadFn thread_fn, void* arg) = NativeThread.create((NativeThread*)thread, thread_fn, arg); +macro void! Thread.detach(Thread thread) = NativeThread.detach((NativeThread)thread); +macro int! Thread.join(Thread thread) = NativeThread.join((NativeThread)thread); +macro bool Thread.equals(Thread thread, Thread other) = NativeThread.equals((NativeThread)this, (NativeThread)other); + +macro void OnceFlag.call_once(OnceFlag* flag, OnceFn func) = NativeOnceFlag.call_once((NativeOnceFlag*)flag, func); + +macro void yield() = os::native_thread_yield(); +macro Thread current() = os::native_thread_current(); +macro void exit(int result) = os::native_thread_exit(result); +macro void! sleep(double s) @maydiscard = os::native_sleep(s); +macro void! sleep_ms(ulong ms) @maydiscard = os::native_sleep_ms(ms); +macro void! sleep_ns(ulong ns) @maydiscard = os::native_sleep_nano(ns); + diff --git a/src/compiler/enums.h b/src/compiler/enums.h index f11448c27..08ea4e3d2 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -822,6 +822,7 @@ typedef enum BUILTIN_BITREVERSE, BUILTIN_BSWAP, BUILTIN_CEIL, + BUILTIN_COMPARE_EXCHANGE, BUILTIN_COPYSIGN, BUILTIN_COS, BUILTIN_CTLZ, diff --git a/src/compiler/llvm_codegen_builtins.c b/src/compiler/llvm_codegen_builtins.c index f462bd470..01f7380b6 100644 --- a/src/compiler/llvm_codegen_builtins.c +++ b/src/compiler/llvm_codegen_builtins.c @@ -54,6 +54,56 @@ INLINE void llvm_emit_shufflevector(GenContext *c, BEValue *result_value, Expr * return; } +static LLVMAtomicOrdering ordering_to_llvm(int value) +{ + switch (value) + { + case 0: return LLVMAtomicOrderingNotAtomic; + case 1: return LLVMAtomicOrderingUnordered; + case 2: return LLVMAtomicOrderingMonotonic; + case 3: return LLVMAtomicOrderingAcquire; + case 4: return LLVMAtomicOrderingRelease; + case 5: return LLVMAtomicOrderingAcquireRelease; + case 6: return LLVMAtomicOrderingSequentiallyConsistent; + default: UNREACHABLE; + } +} + +INLINE void llvm_emit_compare_exchange(GenContext *c, BEValue *result_value, Expr *expr) +{ + Expr **args = expr->call_expr.arguments; + LLVMValueRef normal_args[3]; + BEValue value; + for (int i = 0; i < 3; i++) + { + llvm_emit_expr(c, &value, args[i]); + llvm_value_rvalue(c, &value); + normal_args[i] = value.value; + } + Type *type = value.type; + bool is_volatile = args[3]->const_expr.b; + bool is_weak = args[4]->const_expr.b; + uint64_t success_ordering = args[5]->const_expr.ixx.i.low; + uint64_t failure_ordering = args[6]->const_expr.ixx.i.low; + uint64_t alignment = args[7]->const_expr.ixx.i.low; + LLVMValueRef result = LLVMBuildAtomicCmpXchg(c->builder, normal_args[0], normal_args[1], normal_args[2], + ordering_to_llvm(success_ordering), ordering_to_llvm(failure_ordering), false); + if (alignment && alignment >= type_abi_alignment(type)) + { + LLVMSetAlignment(result, alignment); + } + if (is_volatile) + { + LLVMSetVolatile(result, true); + } + if (is_weak) + { + LLVMSetWeak(result, true); + } + + llvm_value_set(result_value, llvm_emit_extract_value(c, result, 0), type); +} + INLINE void llvm_emit_unreachable(GenContext *c, BEValue *result_value, Expr *expr) { llvm_value_set(result_value, LLVMBuildUnreachable(c->builder), type_void); @@ -525,6 +575,9 @@ void llvm_emit_builtin_call(GenContext *c, BEValue *result_value, Expr *expr) case BUILTIN_SHUFFLEVECTOR: llvm_emit_shufflevector(c, result_value, expr); return; + case BUILTIN_COMPARE_EXCHANGE: + llvm_emit_compare_exchange(c, result_value, expr); + return; case BUILTIN_FRAMEADDRESS: { LLVMTypeRef type[2] = { llvm_get_type(c, type_voidptr), llvm_get_type(c, type_int) }; diff --git a/src/compiler/sema_builtins.c b/src/compiler/sema_builtins.c index 6dcc09e5f..de4073fa7 100644 --- a/src/compiler/sema_builtins.c +++ b/src/compiler/sema_builtins.c @@ -235,6 +235,16 @@ static inline bool sema_expr_analyse_shufflevector(SemaContext *context, Expr *e return true; } +bool is_valid_atomicity(Expr* expr) +{ + if (!expr_is_const_int(expr) || !int_fits(expr->const_expr.ixx, TYPE_U8) || expr->const_expr.ixx.i.low > 6) + { + SEMA_ERROR(expr, "Expected a constant integer value < 8."); + return false; + } + return true; +} + bool sema_expr_analyse_builtin_call(SemaContext *context, Expr *expr) { expr->call_expr.is_builtin = true; @@ -288,6 +298,46 @@ bool sema_expr_analyse_builtin_call(SemaContext *context, Expr *expr) case BUILTIN_UNREACHABLE: rtype = type_void; break; + case BUILTIN_COMPARE_EXCHANGE: + { + Type *comp_type = type_no_optional(args[0]->type->canonical); + if (!type_is_pointer(comp_type)) + { + SEMA_ERROR(args[0], "Expected a pointer here."); + return false; + } + Type *pointee = comp_type->pointer; + for (int i = 1; i < 3; i++) + { + if (pointee != type_no_optional(args[i]->type->canonical)) + { + SEMA_ERROR(args[i], "Expected an argument of type %s.", type_quoted_error_string(pointee)); + return false; + } + } + for (int i = 3; i < 5; i++) + { + if (args[i]->type->canonical != type_bool || !expr_is_const(args[i])) + { + SEMA_ERROR(args[i], "Expected a constant boolean value."); + return false; + } + } + for (int i = 5; i < 7; i++) + { + if (!is_valid_atomicity(args[i])) return false; + } + Expr *align = args[7]; + if (!expr_is_const_int(align) + || !int_fits(align->const_expr.ixx, TYPE_U64) + || (!is_power_of_two(align->const_expr.ixx.i.low) && align->const_expr.ixx.i.low)) + { + SEMA_ERROR(args[7], "Expected a constant power-of-two alignment or zero."); + return false; + } + rtype = args[1]->type; + break; + } case BUILTIN_SYSCLOCK: rtype = type_ulong; break; @@ -657,6 +707,8 @@ static inline unsigned builtin_expected_args(BuiltinFunction func) case BUILTIN_MEMSET: case BUILTIN_MEMSET_INLINE: return 5; + case BUILTIN_COMPARE_EXCHANGE: + return 8; case BUILTIN_SHUFFLEVECTOR: case BUILTIN_NONE: UNREACHABLE diff --git a/src/compiler/sema_name_resolution.c b/src/compiler/sema_name_resolution.c index f461a4a89..98a222561 100644 --- a/src/compiler/sema_name_resolution.c +++ b/src/compiler/sema_name_resolution.c @@ -548,7 +548,7 @@ INLINE Decl *sema_resolve_symbol_common(SemaContext *context, NameResolve *name_ decl = sema_resolve_path_symbol(context, name_resolve); if (!decl && !name_resolve->maybe_decl && !name_resolve->path_found) { - if (!name_resolve->suppress_error) return NULL; + if (name_resolve->suppress_error) return NULL; SEMA_ERROR(name_resolve->path, "Unknown module '%.*s', did you type it right?", name_resolve->path->len, name_resolve->path->module); return poisoned_decl; } diff --git a/src/compiler/sema_types.c b/src/compiler/sema_types.c index 26777af04..86d60e781 100644 --- a/src/compiler/sema_types.c +++ b/src/compiler/sema_types.c @@ -181,6 +181,7 @@ static bool sema_resolve_type_identifier(SemaContext *context, TypeInfo *type_in { Decl *decl = sema_resolve_symbol(context, type_info->unresolved.name, type_info->unresolved.path, type_info->span); + assert(decl); // Already handled if (!decl_ok(decl)) { diff --git a/src/compiler/symtab.c b/src/compiler/symtab.c index d08f697af..afcb8135f 100644 --- a/src/compiler/symtab.c +++ b/src/compiler/symtab.c @@ -188,6 +188,7 @@ void symtab_init(uint32_t capacity) builtin_list[BUILTIN_BITREVERSE] = KW_DEF("bitreverse"); builtin_list[BUILTIN_BSWAP] = KW_DEF("bswap"); builtin_list[BUILTIN_CEIL] = KW_DEF("ceil"); + builtin_list[BUILTIN_COMPARE_EXCHANGE] = KW_DEF(("compare_exchange")); builtin_list[BUILTIN_COPYSIGN] = KW_DEF("copysign"); builtin_list[BUILTIN_COS] = KW_DEF("cos"); builtin_list[BUILTIN_CTLZ] = KW_DEF("clz"); diff --git a/src/version.h b/src/version.h index 68d211713..31f8f24f7 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define COMPILER_VERSION "0.4.8" \ No newline at end of file +#define COMPILER_VERSION "0.4.9" \ No newline at end of file diff --git a/test/test_suite/stdlib/map.c3t b/test/test_suite/stdlib/map.c3t index d8065b054..2ec384bb5 100644 --- a/test/test_suite/stdlib/map.c3t +++ b/test/test_suite/stdlib/map.c3t @@ -508,7 +508,7 @@ after_check100: ; preds = %if.then panic_block: ; preds = %assign_optional %160 = load void (i8*, i64, i8*, i64, i8*, i64, i32)*, void (i8*, i64, i8*, i64, i8*, i64, i32)** @std_core_builtin_panic, align 8 - call void %160(i8* getelementptr inbounds ([28 x i8], [28 x i8]* @.panic_msg, i64 0, i64 0), i64 27, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.file, i64 0, i64 0), i64 6, i8* getelementptr inbounds ([5 x i8], [5 x i8]* @.func, i64 0, i64 0), i64 4, i32 257) + call void %160(i8* getelementptr inbounds ([28 x i8], [28 x i8]* @.panic_msg, i64 0, i64 0), i64 27, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @.file, i64 0, i64 0), i64 6, i8* getelementptr inbounds ([5 x i8], [5 x i8]* @.func, i64 0, i64 0), i64 4, unreachable noerr_block: ; preds = %after_check100 diff --git a/test/test_suite2/stdlib/map.c3t b/test/test_suite2/stdlib/map.c3t index 956177cb9..5fc5af981 100644 --- a/test/test_suite2/stdlib/map.c3t +++ b/test/test_suite2/stdlib/map.c3t @@ -364,7 +364,7 @@ after_check67: ; preds = %if.then panic_block: ; preds = %assign_optional %81 = load ptr, ptr @std_core_builtin_panic, align 8 - call void %81(ptr @.panic_msg, i64 27, ptr @.file, i64 6, ptr @.func, i64 4, i32 257) + call void %81(ptr @.panic_msg, i64 27, ptr @.file, i64 6, ptr @.func, i64 4, unreachable noerr_block: ; preds = %after_check67