From d9fab5e824b5caee48ed96cf76e0011f0cdf6742 Mon Sep 17 00:00:00 2001 From: Paco Pascal Date: Sat, 18 Nov 2023 20:56:22 -0500 Subject: Return value of _umtx_op on FreeBSD wasn't checked correctly --- src/threading.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/threading.cpp') diff --git a/src/threading.cpp b/src/threading.cpp index 3ddc05b0a..74aa3eb7e 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -660,7 +660,7 @@ gb_internal void futex_broadcast(Futex *addr) { gb_internal void futex_wait(Futex *addr, Footex val) { for (;;) { int ret = _umtx_op(addr, UMTX_OP_WAIT_UINT, val, 0, NULL); - if (ret == 0) { + if (ret == -1) { if (errno == ETIMEDOUT || errno == EINTR) { continue; } -- cgit v1.2.3 From 7b53dbeb8afe110f90347c171cd28c317ac762d0 Mon Sep 17 00:00:00 2001 From: Stan Irvin-Wilmot Date: Wed, 10 Jan 2024 15:53:00 +0000 Subject: fix loop condition on compare_exhange_strong result in semaphore_wait - it was backwards so would loop on success and bail on fail --- src/threading.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/threading.cpp') diff --git a/src/threading.cpp b/src/threading.cpp index 74aa3eb7e..c283da425 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -210,7 +210,7 @@ gb_internal void semaphore_wait(Semaphore *s) { original_count = s->count().load(std::memory_order_relaxed); } - if (!s->count().compare_exchange_strong(original_count, original_count-1, std::memory_order_acquire, std::memory_order_acquire)) { + if (s->count().compare_exchange_strong(original_count, original_count-1, std::memory_order_acquire, std::memory_order_acquire)) { return; } } -- cgit v1.2.3 From cbfb32c34c09fd13098f0127bc98c88b53587a97 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 13 Feb 2024 16:21:41 +0000 Subject: Fix race condition with regards to #soa arrays by using the fields mutex --- src/check_type.cpp | 10 +++++----- src/threading.cpp | 10 ++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) (limited to 'src/threading.cpp') diff --git a/src/check_type.cpp b/src/check_type.cpp index 03c7474fb..66f8b1185 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2553,6 +2553,8 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e GB_ASSERT(is_type_struct(elem)); Type *old_struct = base_type(elem); + RW_MUTEX_GUARD(&old_struct->Struct.fields_mutex); + field_count = old_struct->Struct.fields.count; soa_struct = alloc_type_struct(); @@ -2593,21 +2595,19 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e } if (soa_kind != StructSoa_Fixed) { - Entity *len_field = alloc_entity_field(scope, empty_token, t_int, false, cast(i32)field_count+0); + Entity *len_field = alloc_entity_field(scope, make_token_ident("__$len"), t_int, false, cast(i32)field_count+0); soa_struct->Struct.fields[field_count+0] = len_field; add_entity(ctx, scope, nullptr, len_field); add_entity_use(ctx, nullptr, len_field); if (soa_kind == StructSoa_Dynamic) { - Entity *cap_field = alloc_entity_field(scope, empty_token, t_int, false, cast(i32)field_count+1); + Entity *cap_field = alloc_entity_field(scope, make_token_ident("__$cap"), t_int, false, cast(i32)field_count+1); soa_struct->Struct.fields[field_count+1] = cap_field; add_entity(ctx, scope, nullptr, cap_field); add_entity_use(ctx, nullptr, cap_field); - Token token = {}; - token.string = str_lit("allocator"); init_mem_allocator(ctx->checker); - Entity *allocator_field = alloc_entity_field(scope, token, t_allocator, false, cast(i32)field_count+2); + Entity *allocator_field = alloc_entity_field(scope, make_token_ident("allocator"), t_allocator, false, cast(i32)field_count+2); soa_struct->Struct.fields[field_count+2] = allocator_field; add_entity(ctx, scope, nullptr, allocator_field); add_entity_use(ctx, nullptr, allocator_field); diff --git a/src/threading.cpp b/src/threading.cpp index c283da425..b8bc9b118 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -119,17 +119,25 @@ struct MutexGuard { explicit MutexGuard(RecursiveMutex *rm) noexcept : rm{rm} { mutex_lock(this->rm); } + explicit MutexGuard(RwMutex *rm) noexcept : rwm{rwm} { + rw_mutex_lock(this->rwm); + } explicit MutexGuard(BlockingMutex &bm) noexcept : bm{&bm} { mutex_lock(this->bm); } explicit MutexGuard(RecursiveMutex &rm) noexcept : rm{&rm} { mutex_lock(this->rm); } + explicit MutexGuard(RwMutex &rwm) noexcept : rwm{&rwm} { + rw_mutex_lock(this->rwm); + } ~MutexGuard() noexcept { if (this->bm) { mutex_unlock(this->bm); } else if (this->rm) { mutex_unlock(this->rm); + } else if (this->rwm) { + rw_mutex_unlock(this->rwm); } } @@ -137,10 +145,12 @@ struct MutexGuard { BlockingMutex *bm; RecursiveMutex *rm; + RwMutex *rwm; }; #define MUTEX_GUARD_BLOCK(m) if (MutexGuard GB_DEFER_3(_mutex_guard_){m}) #define MUTEX_GUARD(m) mutex_lock(m); defer (mutex_unlock(m)) +#define RW_MUTEX_GUARD(m) rw_mutex_lock(m); defer (rw_mutex_unlock(m)) struct RecursiveMutex { -- cgit v1.2.3 From d496dbf3a0ee05819ab6e802939b4219cfa9c7fe Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 13 Feb 2024 16:54:41 +0000 Subject: Fix race condition with #soa --- src/check_type.cpp | 6 ++---- src/threading.cpp | 16 ++++++++++++++++ src/types.cpp | 17 ++++++----------- 3 files changed, 24 insertions(+), 15 deletions(-) (limited to 'src/threading.cpp') diff --git a/src/check_type.cpp b/src/check_type.cpp index 66f8b1185..8a140d95e 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -632,9 +632,6 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * scope_reserve(ctx->scope, min_field_count); - rw_mutex_lock(&struct_type->Struct.fields_mutex); - defer (rw_mutex_unlock(&struct_type->Struct.fields_mutex)); - if (st->is_raw_union && min_field_count > 1) { struct_type->Struct.is_raw_union = true; context = str_lit("struct #raw_union"); @@ -662,6 +659,7 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * gb_unused(where_clause_ok); } check_struct_fields(ctx, node, &struct_type->Struct.fields, &struct_type->Struct.tags, st->fields, min_field_count, struct_type, context); + wait_signal_set(&struct_type->Struct.fields_wait_signal); } #define ST_ALIGN(_name) if (st->_name != nullptr) { \ @@ -2553,8 +2551,8 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e GB_ASSERT(is_type_struct(elem)); Type *old_struct = base_type(elem); - RW_MUTEX_GUARD(&old_struct->Struct.fields_mutex); + wait_signal_until_available(&old_struct->Struct.fields_wait_signal); field_count = old_struct->Struct.fields.count; soa_struct = alloc_type_struct(); diff --git a/src/threading.cpp b/src/threading.cpp index b8bc9b118..731394126 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -107,6 +107,22 @@ gb_internal void thread_set_name (Thread *t, char const *name); gb_internal void yield_thread(void); gb_internal void yield_process(void); +struct Wait_Signal { + Futex futex; +}; + +gb_internal void wait_signal_until_available(Wait_Signal *ws) { + if (ws->futex.load() == 0) { + futex_wait(&ws->futex, 1); + } +} + +gb_internal void wait_signal_set(Wait_Signal *ws) { + ws->futex.store(1); + futex_broadcast(&ws->futex); +} + + struct MutexGuard { MutexGuard() = delete; diff --git a/src/types.cpp b/src/types.cpp index 04fb06582..2f1994574 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -144,7 +144,7 @@ struct TypeStruct { Type * soa_elem; i32 soa_count; StructSoaKind soa_kind; - RwMutex fields_mutex; + Wait_Signal fields_wait_signal; BlockingMutex offset_mutex; // for settings offsets bool is_polymorphic; @@ -2969,9 +2969,8 @@ gb_internal Selection lookup_field_from_index(Type *type, i64 index) { isize max_count = 0; switch (type->kind) { case Type_Struct: - rw_mutex_shared_lock(&type->Struct.fields_mutex); + wait_signal_until_available(&type->Struct.fields_wait_signal); max_count = type->Struct.fields.count; - rw_mutex_shared_unlock(&type->Struct.fields_mutex); break; case Type_Tuple: max_count = type->Tuple.variables.count; break; } @@ -2982,8 +2981,7 @@ gb_internal Selection lookup_field_from_index(Type *type, i64 index) { switch (type->kind) { case Type_Struct: { - rw_mutex_shared_lock(&type->Struct.fields_mutex); - defer (rw_mutex_shared_unlock(&type->Struct.fields_mutex)); + wait_signal_until_available(&type->Struct.fields_wait_signal); for (isize i = 0; i < max_count; i++) { Entity *f = type->Struct.fields[i]; if (f->kind == Entity_Variable) { @@ -3048,9 +3046,8 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name } } if (type->kind == Type_Struct) { - rw_mutex_shared_lock(&type->Struct.fields_mutex); + wait_signal_until_available(&type->Struct.fields_wait_signal); isize field_count = type->Struct.fields.count; - rw_mutex_shared_unlock(&type->Struct.fields_mutex); if (field_count != 0) for_array(i, type->Struct.fields) { Entity *f = type->Struct.fields[i]; if (f->flags&EntityFlag_Using) { @@ -3079,9 +3076,8 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name } if (type->kind == Type_Struct) { - rw_mutex_shared_lock(&type->Struct.fields_mutex); + wait_signal_until_available(&type->Struct.fields_wait_signal); Scope *s = type->Struct.scope; - rw_mutex_shared_unlock(&type->Struct.fields_mutex); if (s != nullptr) { Entity *found = scope_lookup_current(s, field_name); if (found != nullptr && found->kind != Entity_Variable) { @@ -3129,9 +3125,8 @@ gb_internal Selection lookup_field_with_selection(Type *type_, String field_name } } - rw_mutex_shared_lock(&type->Struct.fields_mutex); + wait_signal_until_available(&type->Struct.fields_wait_signal); isize field_count = type->Struct.fields.count; - rw_mutex_shared_unlock(&type->Struct.fields_mutex); if (field_count != 0) for_array(i, type->Struct.fields) { Entity *f = type->Struct.fields[i]; if (f->kind != Entity_Variable || (f->flags & EntityFlag_Field) == 0) { -- cgit v1.2.3 From c5c2a4d09d98f0d3b6263e204785553e47b83395 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 13 Feb 2024 17:13:39 +0000 Subject: Fix typo --- src/threading.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/threading.cpp') diff --git a/src/threading.cpp b/src/threading.cpp index 731394126..725b58c89 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -135,7 +135,7 @@ struct MutexGuard { explicit MutexGuard(RecursiveMutex *rm) noexcept : rm{rm} { mutex_lock(this->rm); } - explicit MutexGuard(RwMutex *rm) noexcept : rwm{rwm} { + explicit MutexGuard(RwMutex *rwm) noexcept : rwm{rwm} { rw_mutex_lock(this->rwm); } explicit MutexGuard(BlockingMutex &bm) noexcept : bm{&bm} { -- cgit v1.2.3 From c178f7199d7070c5481c5f0f3077f8dcbfa90226 Mon Sep 17 00:00:00 2001 From: Slendi Date: Thu, 15 Feb 2024 15:51:28 +0200 Subject: Get Odin to compile on Haiku This patch makes Odin to compile on Haiku which is a good first step. Now, all that's needed to do is to figure out how to do futexes, which I am blaming for the program crashing. --- build_odin.sh | 5 + src/gb/gb.h | 49 ++- src/path.cpp | 922 +++++++++++++++++++++++++++--------------------------- src/threading.cpp | 47 ++- 4 files changed, 559 insertions(+), 464 deletions(-) (limited to 'src/threading.cpp') diff --git a/build_odin.sh b/build_odin.sh index 589aeb550..0d7750ffa 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -83,6 +83,11 @@ OpenBSD) LDFLAGS="$LDFLAGS -liconv" LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" ;; +Haiku) + CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags) -I/system/develop/headers/private/shared -I/system/develop/headers/private/kernel" + LDFLAGS="$LDFLAGS -liconv" + LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" + ;; *) error "Platform \"$OS_NAME\" unsupported" ;; diff --git a/src/gb/gb.h b/src/gb/gb.h index 93d250f21..702647121 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -83,6 +83,10 @@ extern "C" { #ifndef GB_SYSTEM_OPENBSD #define GB_SYSTEM_OPENBSD 1 #endif + #elif defined(__HAIKU__) || defined(__haiku__) + #ifndef GB_SYSTEM_HAIKU + #define GB_SYSTEM_HAIKU 1 + #endif #else #error This UNIX operating system is not supported #endif @@ -206,7 +210,7 @@ extern "C" { #endif #include // NOTE(bill): malloc on linux #include - #if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) + #if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__HAIKU__) #include #endif #include @@ -247,6 +251,13 @@ extern "C" { #include #define lseek64 lseek #endif + +#if defined(GB_SYSTEM_HAIKU) + #include + #include + #include + #define lseek64 lseek +#endif #if defined(GB_SYSTEM_UNIX) #include @@ -801,6 +812,13 @@ typedef struct gbAffinity { isize thread_count; isize threads_per_core; } gbAffinity; +#elif defined(GB_SYSTEM_HAIKU) +typedef struct gbAffinity { + b32 is_accurate; + isize core_count; + isize thread_count; + isize threads_per_core; +} gbAffinity; #else #error TODO(bill): Unknown system #endif @@ -2984,6 +3002,8 @@ gb_inline u32 gb_thread_current_id(void) { __asm__("mov %%fs:0x10,%0" : "=r"(thread_id)); #elif defined(GB_SYSTEM_LINUX) thread_id = gettid(); +#elif defined(GB_SYSTEM_HAIKU) + thread_id = find_thread(NULL); #else #error Unsupported architecture for gb_thread_current_id() #endif @@ -3184,7 +3204,9 @@ b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) { //info.affinity_tag = cast(integer_t)index; //result = thread_policy_set(thread, THREAD_AFFINITY_POLICY, cast(thread_policy_t)&info, THREAD_AFFINITY_POLICY_COUNT); +#if !defined(GB_SYSTEM_HAIKU) result = pthread_setaffinity_np(thread, sizeof(cpuset_t), &mn); +#endif return result == 0; } @@ -3236,6 +3258,29 @@ b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) { return true; } +isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { + GB_ASSERT(0 <= core && core < a->core_count); + return a->threads_per_core; +} +#elif defined(GB_SYSTEM_HAIKU) +#include + +void gb_affinity_init(gbAffinity *a) { + a->core_count = sysconf(_SC_NPROCESSORS_ONLN); + a->threads_per_core = 1; + a->is_accurate = a->core_count > 0; + a->core_count = a->is_accurate ? a->core_count : 1; + a->thread_count = a->core_count; +} + +void gb_affinity_destroy(gbAffinity *a) { + gb_unused(a); +} + +b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) { + return true; +} + isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { GB_ASSERT(0 <= core && core < a->core_count); return a->threads_per_core; @@ -5457,7 +5502,7 @@ gb_inline b32 gb_file_copy(char const *existing_filename, char const *new_filena } } - gb_free(buf); + gb_mfree(buf); close(new_fd); close(existing_fd); diff --git a/src/path.cpp b/src/path.cpp index de80c9def..742bba7f8 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -1,461 +1,461 @@ -/* - Path handling utilities. -*/ -#if !defined(GB_SYSTEM_WINDOWS) -#include -#endif - -gb_internal String remove_extension_from_path(String const &s) { - if (s.len != 0 && s.text[s.len-1] == '.') { - return s; - } - for (isize i = s.len-1; i >= 0; i--) { - if (s[i] == '.') { - return substring(s, 0, i); - } - } - return s; -} - -gb_internal String remove_directory_from_path(String const &s) { - isize len = 0; - for (isize i = s.len-1; i >= 0; i--) { - if (s[i] == '/' || - s[i] == '\\') { - break; - } - len += 1; - } - return substring(s, s.len-len, s.len); -} - - -// NOTE(Mark Naughton): getcwd as String -#if !defined(GB_SYSTEM_WINDOWS) -gb_internal String get_current_directory(void) { - char cwd[256]; - getcwd(cwd, 256); - - return make_string_c(cwd); -} - -#else -gb_internal String get_current_directory(void) { - gbAllocator a = heap_allocator(); - - wchar_t cwd[256]; - GetCurrentDirectoryW(256, cwd); - - String16 wstr = make_string16_c(cwd); - - return string16_to_string(a, wstr); -} -#endif - -gb_internal bool path_is_directory(String path); - -gb_internal String directory_from_path(String const &s) { - if (path_is_directory(s)) { - return s; - } - - isize i = s.len-1; - for (; i >= 0; i--) { - if (s[i] == '/' || - s[i] == '\\') { - break; - } - } - if (i >= 0) { - return substring(s, 0, i); - } - return substring(s, 0, 0); -} - -#if defined(GB_SYSTEM_WINDOWS) - gb_internal bool path_is_directory(String path) { - gbAllocator a = heap_allocator(); - String16 wstr = string_to_string16(a, path); - defer (gb_free(a, wstr.text)); - - i32 attribs = GetFileAttributesW(wstr.text); - if (attribs < 0) return false; - - return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0; - } - -#else - gb_internal bool path_is_directory(String path) { - gbAllocator a = heap_allocator(); - char *copy = cast(char *)copy_string(a, path).text; - defer (gb_free(a, copy)); - - struct stat s; - if (stat(copy, &s) == 0) { - return (s.st_mode & S_IFDIR) != 0; - } - return false; - } -#endif - - -gb_internal String path_to_full_path(gbAllocator a, String path) { - gbAllocator ha = heap_allocator(); - char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len); - defer (gb_free(ha, path_c)); - - char *fullpath = gb_path_get_full_name(a, path_c); - String res = string_trim_whitespace(make_string_c(fullpath)); -#if defined(GB_SYSTEM_WINDOWS) - for (isize i = 0; i < res.len; i++) { - if (res.text[i] == '\\') { - res.text[i] = '/'; - } - } -#endif - return copy_string(a, res); -} - -struct Path { - String basename; - String name; - String ext; -}; - -// NOTE(Jeroen): Naively turns a Path into a string. -gb_internal String path_to_string(gbAllocator a, Path path) { - if (path.basename.len + path.name.len + path.ext.len == 0) { - return make_string(nullptr, 0); - } - - isize len = path.basename.len + 1 + path.name.len + 1; - if (path.ext.len > 0) { - len += path.ext.len + 1; - } - - u8 *str = gb_alloc_array(a, u8, len); - - isize i = 0; - gb_memmove(str+i, path.basename.text, path.basename.len); i += path.basename.len; - - gb_memmove(str+i, "/", 1); i += 1; - - gb_memmove(str+i, path.name.text, path.name.len); i += path.name.len; - if (path.ext.len > 0) { - gb_memmove(str+i, ".", 1); i += 1; - gb_memmove(str+i, path.ext.text, path.ext.len); i += path.ext.len; - } - str[i] = 0; - - String res = make_string(str, i); - res = string_trim_whitespace(res); - return res; -} - -// NOTE(Jeroen): Naively turns a Path into a string, then normalizes it using `path_to_full_path`. -gb_internal String path_to_full_path(gbAllocator a, Path path) { - String temp = path_to_string(heap_allocator(), path); - defer (gb_free(heap_allocator(), temp.text)); - - return path_to_full_path(a, temp); -} - -// NOTE(Jeroen): Takes a path like "odin" or "W:\Odin", turns it into a full path, -// and then breaks it into its components to make a Path. -gb_internal Path path_from_string(gbAllocator a, String const &path) { - Path res = {}; - - if (path.len == 0) return res; - - String fullpath = path_to_full_path(a, path); - defer (gb_free(heap_allocator(), fullpath.text)); - - res.basename = directory_from_path(fullpath); - res.basename = copy_string(a, res.basename); - - if (path_is_directory(fullpath)) { - // It's a directory. We don't need to tinker with the name and extension. - // It could have a superfluous trailing `/`. Remove it if so. - if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') { - res.basename.len--; - } - return res; - } - - // Note(Dragos): Is the copy_string required if it's a substring? - isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len; - res.name = substring(fullpath, name_start, fullpath.len); - res.name = remove_extension_from_path(res.name); - res.name = copy_string(a, res.name); - - res.ext = path_extension(fullpath, false); // false says not to include the dot. - res.ext = copy_string(a, res.ext); - return res; -} - -// NOTE(Jeroen): Takes a path String and returns the last path element. -gb_internal String last_path_element(String const &path) { - isize count = 0; - u8 * start = (u8 *)(&path.text[path.len - 1]); - for (isize length = path.len; length > 0 && path.text[length - 1] != '/'; length--) { - count++; - start--; - } - if (count > 0) { - start++; // Advance past the `/` and return the substring. - String res = make_string(start, count); - return res; - } - // Must be a root path like `/` or `C:/`, return empty String. - return STR_LIT(""); -} - -gb_internal bool path_is_directory(Path path) { - String path_string = path_to_full_path(heap_allocator(), path); - defer (gb_free(heap_allocator(), path_string.text)); - - return path_is_directory(path_string); -} - -struct FileInfo { - String name; - String fullpath; - i64 size; - bool is_dir; -}; - -enum ReadDirectoryError { - ReadDirectory_None, - - ReadDirectory_InvalidPath, - ReadDirectory_NotExists, - ReadDirectory_Permission, - ReadDirectory_NotDir, - ReadDirectory_Empty, - ReadDirectory_Unknown, - - ReadDirectory_COUNT, -}; - -gb_internal i64 get_file_size(String path) { - char *c_str = alloc_cstring(heap_allocator(), path); - defer (gb_free(heap_allocator(), c_str)); - - gbFile f = {}; - gbFileError err = gb_file_open(&f, c_str); - defer (gb_file_close(&f)); - if (err != gbFileError_None) { - return -1; - } - return gb_file_size(&f); -} - - -#if defined(GB_SYSTEM_WINDOWS) -gb_internal ReadDirectoryError read_directory(String path, Array *fi) { - GB_ASSERT(fi != nullptr); - - - while (path.len > 0) { - Rune end = path[path.len-1]; - if (end == '/') { - path.len -= 1; - } else if (end == '\\') { - path.len -= 1; - } else { - break; - } - } - - if (path.len == 0) { - return ReadDirectory_InvalidPath; - } - { - char *c_str = alloc_cstring(temporary_allocator(), path); - gbFile f = {}; - gbFileError file_err = gb_file_open(&f, c_str); - defer (gb_file_close(&f)); - - switch (file_err) { - case gbFileError_Invalid: return ReadDirectory_InvalidPath; - case gbFileError_NotExists: return ReadDirectory_NotExists; - // case gbFileError_Permission: return ReadDirectory_Permission; - } - } - - if (!path_is_directory(path)) { - return ReadDirectory_NotDir; - } - - - gbAllocator a = heap_allocator(); - char *new_path = gb_alloc_array(a, char, path.len+3); - defer (gb_free(a, new_path)); - - gb_memmove(new_path, path.text, path.len); - gb_memmove(new_path+path.len, "/*", 2); - new_path[path.len+2] = 0; - - String np = make_string(cast(u8 *)new_path, path.len+2); - String16 wstr = string_to_string16(a, np); - defer (gb_free(a, wstr.text)); - - WIN32_FIND_DATAW file_data = {}; - HANDLE find_file = FindFirstFileW(wstr.text, &file_data); - if (find_file == INVALID_HANDLE_VALUE) { - return ReadDirectory_Unknown; - } - defer (FindClose(find_file)); - - array_init(fi, a, 0, 100); - - do { - wchar_t *filename_w = file_data.cFileName; - u64 size = cast(u64)file_data.nFileSizeLow; - size |= (cast(u64)file_data.nFileSizeHigh) << 32; - String name = string16_to_string(a, make_string16_c(filename_w)); - if (name == "." || name == "..") { - gb_free(a, name.text); - continue; - } - - String filepath = {}; - filepath.len = path.len+1+name.len; - filepath.text = gb_alloc_array(a, u8, filepath.len+1); - defer (gb_free(a, filepath.text)); - gb_memmove(filepath.text, path.text, path.len); - gb_memmove(filepath.text+path.len, "/", 1); - gb_memmove(filepath.text+path.len+1, name.text, name.len); - - FileInfo info = {}; - info.name = name; - info.fullpath = path_to_full_path(a, filepath); - info.size = cast(i64)size; - info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - array_add(fi, info); - } while (FindNextFileW(find_file, &file_data)); - - if (fi->count == 0) { - return ReadDirectory_Empty; - } - - return ReadDirectory_None; -} -#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) - -#include - -gb_internal ReadDirectoryError read_directory(String path, Array *fi) { - GB_ASSERT(fi != nullptr); - - gbAllocator a = heap_allocator(); - - char *c_path = alloc_cstring(a, path); - defer (gb_free(a, c_path)); - - DIR *dir = opendir(c_path); - if (!dir) { - switch (errno) { - case ENOENT: - return ReadDirectory_NotExists; - case EACCES: - return ReadDirectory_Permission; - case ENOTDIR: - return ReadDirectory_NotDir; - default: - // ENOMEM: out of memory - // EMFILE: per-process limit on open fds reached - // ENFILE: system-wide limit on total open files reached - return ReadDirectory_Unknown; - } - GB_PANIC("unreachable"); - } - - array_init(fi, a, 0, 100); - - for (;;) { - struct dirent *entry = readdir(dir); - if (entry == nullptr) { - break; - } - - String name = make_string_c(entry->d_name); - if (name == "." || name == "..") { - continue; - } - - String filepath = {}; - filepath.len = path.len+1+name.len; - filepath.text = gb_alloc_array(a, u8, filepath.len+1); - defer (gb_free(a, filepath.text)); - gb_memmove(filepath.text, path.text, path.len); - gb_memmove(filepath.text+path.len, "/", 1); - gb_memmove(filepath.text+path.len+1, name.text, name.len); - filepath.text[filepath.len] = 0; - - - struct stat dir_stat = {}; - - if (stat((char *)filepath.text, &dir_stat)) { - continue; - } - - if (S_ISDIR(dir_stat.st_mode)) { - continue; - } - - i64 size = dir_stat.st_size; - - FileInfo info = {}; - info.name = name; - info.fullpath = path_to_full_path(a, filepath); - info.size = size; - array_add(fi, info); - } - - if (fi->count == 0) { - return ReadDirectory_Empty; - } - - return ReadDirectory_None; -} - - -#else -#error Implement read_directory -#endif - -#if !defined(GB_SYSTEM_WINDOWS) -gb_internal bool write_directory(String path) { - char const *pathname = (char *) path.text; - - if (access(pathname, W_OK) < 0) { - return false; - } - - return true; -} -#else -gb_internal bool write_directory(String path) { - String16 wstr = string_to_string16(heap_allocator(), path); - LPCWSTR wdirectory_name = wstr.text; - - HANDLE directory = CreateFileW(wdirectory_name, - GENERIC_WRITE, - 0, - NULL, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - NULL); - - if (directory == INVALID_HANDLE_VALUE) { - DWORD error_code = GetLastError(); - if (error_code == ERROR_ACCESS_DENIED) { - return false; - } - } - - CloseHandle(directory); - return true; -} -#endif +/* + Path handling utilities. +*/ +#if !defined(GB_SYSTEM_WINDOWS) +#include +#endif + +gb_internal String remove_extension_from_path(String const &s) { + if (s.len != 0 && s.text[s.len-1] == '.') { + return s; + } + for (isize i = s.len-1; i >= 0; i--) { + if (s[i] == '.') { + return substring(s, 0, i); + } + } + return s; +} + +gb_internal String remove_directory_from_path(String const &s) { + isize len = 0; + for (isize i = s.len-1; i >= 0; i--) { + if (s[i] == '/' || + s[i] == '\\') { + break; + } + len += 1; + } + return substring(s, s.len-len, s.len); +} + + +// NOTE(Mark Naughton): getcwd as String +#if !defined(GB_SYSTEM_WINDOWS) +gb_internal String get_current_directory(void) { + char cwd[256]; + getcwd(cwd, 256); + + return make_string_c(cwd); +} + +#else +gb_internal String get_current_directory(void) { + gbAllocator a = heap_allocator(); + + wchar_t cwd[256]; + GetCurrentDirectoryW(256, cwd); + + String16 wstr = make_string16_c(cwd); + + return string16_to_string(a, wstr); +} +#endif + +gb_internal bool path_is_directory(String path); + +gb_internal String directory_from_path(String const &s) { + if (path_is_directory(s)) { + return s; + } + + isize i = s.len-1; + for (; i >= 0; i--) { + if (s[i] == '/' || + s[i] == '\\') { + break; + } + } + if (i >= 0) { + return substring(s, 0, i); + } + return substring(s, 0, 0); +} + +#if defined(GB_SYSTEM_WINDOWS) + gb_internal bool path_is_directory(String path) { + gbAllocator a = heap_allocator(); + String16 wstr = string_to_string16(a, path); + defer (gb_free(a, wstr.text)); + + i32 attribs = GetFileAttributesW(wstr.text); + if (attribs < 0) return false; + + return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0; + } + +#else + gb_internal bool path_is_directory(String path) { + gbAllocator a = heap_allocator(); + char *copy = cast(char *)copy_string(a, path).text; + defer (gb_free(a, copy)); + + struct stat s; + if (stat(copy, &s) == 0) { + return (s.st_mode & S_IFDIR) != 0; + } + return false; + } +#endif + + +gb_internal String path_to_full_path(gbAllocator a, String path) { + gbAllocator ha = heap_allocator(); + char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len); + defer (gb_free(ha, path_c)); + + char *fullpath = gb_path_get_full_name(a, path_c); + String res = string_trim_whitespace(make_string_c(fullpath)); +#if defined(GB_SYSTEM_WINDOWS) + for (isize i = 0; i < res.len; i++) { + if (res.text[i] == '\\') { + res.text[i] = '/'; + } + } +#endif + return copy_string(a, res); +} + +struct Path { + String basename; + String name; + String ext; +}; + +// NOTE(Jeroen): Naively turns a Path into a string. +gb_internal String path_to_string(gbAllocator a, Path path) { + if (path.basename.len + path.name.len + path.ext.len == 0) { + return make_string(nullptr, 0); + } + + isize len = path.basename.len + 1 + path.name.len + 1; + if (path.ext.len > 0) { + len += path.ext.len + 1; + } + + u8 *str = gb_alloc_array(a, u8, len); + + isize i = 0; + gb_memmove(str+i, path.basename.text, path.basename.len); i += path.basename.len; + + gb_memmove(str+i, "/", 1); i += 1; + + gb_memmove(str+i, path.name.text, path.name.len); i += path.name.len; + if (path.ext.len > 0) { + gb_memmove(str+i, ".", 1); i += 1; + gb_memmove(str+i, path.ext.text, path.ext.len); i += path.ext.len; + } + str[i] = 0; + + String res = make_string(str, i); + res = string_trim_whitespace(res); + return res; +} + +// NOTE(Jeroen): Naively turns a Path into a string, then normalizes it using `path_to_full_path`. +gb_internal String path_to_full_path(gbAllocator a, Path path) { + String temp = path_to_string(heap_allocator(), path); + defer (gb_free(heap_allocator(), temp.text)); + + return path_to_full_path(a, temp); +} + +// NOTE(Jeroen): Takes a path like "odin" or "W:\Odin", turns it into a full path, +// and then breaks it into its components to make a Path. +gb_internal Path path_from_string(gbAllocator a, String const &path) { + Path res = {}; + + if (path.len == 0) return res; + + String fullpath = path_to_full_path(a, path); + defer (gb_free(heap_allocator(), fullpath.text)); + + res.basename = directory_from_path(fullpath); + res.basename = copy_string(a, res.basename); + + if (path_is_directory(fullpath)) { + // It's a directory. We don't need to tinker with the name and extension. + // It could have a superfluous trailing `/`. Remove it if so. + if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') { + res.basename.len--; + } + return res; + } + + // Note(Dragos): Is the copy_string required if it's a substring? + isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len; + res.name = substring(fullpath, name_start, fullpath.len); + res.name = remove_extension_from_path(res.name); + res.name = copy_string(a, res.name); + + res.ext = path_extension(fullpath, false); // false says not to include the dot. + res.ext = copy_string(a, res.ext); + return res; +} + +// NOTE(Jeroen): Takes a path String and returns the last path element. +gb_internal String last_path_element(String const &path) { + isize count = 0; + u8 * start = (u8 *)(&path.text[path.len - 1]); + for (isize length = path.len; length > 0 && path.text[length - 1] != '/'; length--) { + count++; + start--; + } + if (count > 0) { + start++; // Advance past the `/` and return the substring. + String res = make_string(start, count); + return res; + } + // Must be a root path like `/` or `C:/`, return empty String. + return STR_LIT(""); +} + +gb_internal bool path_is_directory(Path path) { + String path_string = path_to_full_path(heap_allocator(), path); + defer (gb_free(heap_allocator(), path_string.text)); + + return path_is_directory(path_string); +} + +struct FileInfo { + String name; + String fullpath; + i64 size; + bool is_dir; +}; + +enum ReadDirectoryError { + ReadDirectory_None, + + ReadDirectory_InvalidPath, + ReadDirectory_NotExists, + ReadDirectory_Permission, + ReadDirectory_NotDir, + ReadDirectory_Empty, + ReadDirectory_Unknown, + + ReadDirectory_COUNT, +}; + +gb_internal i64 get_file_size(String path) { + char *c_str = alloc_cstring(heap_allocator(), path); + defer (gb_free(heap_allocator(), c_str)); + + gbFile f = {}; + gbFileError err = gb_file_open(&f, c_str); + defer (gb_file_close(&f)); + if (err != gbFileError_None) { + return -1; + } + return gb_file_size(&f); +} + + +#if defined(GB_SYSTEM_WINDOWS) +gb_internal ReadDirectoryError read_directory(String path, Array *fi) { + GB_ASSERT(fi != nullptr); + + + while (path.len > 0) { + Rune end = path[path.len-1]; + if (end == '/') { + path.len -= 1; + } else if (end == '\\') { + path.len -= 1; + } else { + break; + } + } + + if (path.len == 0) { + return ReadDirectory_InvalidPath; + } + { + char *c_str = alloc_cstring(temporary_allocator(), path); + gbFile f = {}; + gbFileError file_err = gb_file_open(&f, c_str); + defer (gb_file_close(&f)); + + switch (file_err) { + case gbFileError_Invalid: return ReadDirectory_InvalidPath; + case gbFileError_NotExists: return ReadDirectory_NotExists; + // case gbFileError_Permission: return ReadDirectory_Permission; + } + } + + if (!path_is_directory(path)) { + return ReadDirectory_NotDir; + } + + + gbAllocator a = heap_allocator(); + char *new_path = gb_alloc_array(a, char, path.len+3); + defer (gb_free(a, new_path)); + + gb_memmove(new_path, path.text, path.len); + gb_memmove(new_path+path.len, "/*", 2); + new_path[path.len+2] = 0; + + String np = make_string(cast(u8 *)new_path, path.len+2); + String16 wstr = string_to_string16(a, np); + defer (gb_free(a, wstr.text)); + + WIN32_FIND_DATAW file_data = {}; + HANDLE find_file = FindFirstFileW(wstr.text, &file_data); + if (find_file == INVALID_HANDLE_VALUE) { + return ReadDirectory_Unknown; + } + defer (FindClose(find_file)); + + array_init(fi, a, 0, 100); + + do { + wchar_t *filename_w = file_data.cFileName; + u64 size = cast(u64)file_data.nFileSizeLow; + size |= (cast(u64)file_data.nFileSizeHigh) << 32; + String name = string16_to_string(a, make_string16_c(filename_w)); + if (name == "." || name == "..") { + gb_free(a, name.text); + continue; + } + + String filepath = {}; + filepath.len = path.len+1+name.len; + filepath.text = gb_alloc_array(a, u8, filepath.len+1); + defer (gb_free(a, filepath.text)); + gb_memmove(filepath.text, path.text, path.len); + gb_memmove(filepath.text+path.len, "/", 1); + gb_memmove(filepath.text+path.len+1, name.text, name.len); + + FileInfo info = {}; + info.name = name; + info.fullpath = path_to_full_path(a, filepath); + info.size = cast(i64)size; + info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + array_add(fi, info); + } while (FindNextFileW(find_file, &file_data)); + + if (fi->count == 0) { + return ReadDirectory_Empty; + } + + return ReadDirectory_None; +} +#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_HAIKU) + +#include + +gb_internal ReadDirectoryError read_directory(String path, Array *fi) { + GB_ASSERT(fi != nullptr); + + gbAllocator a = heap_allocator(); + + char *c_path = alloc_cstring(a, path); + defer (gb_free(a, c_path)); + + DIR *dir = opendir(c_path); + if (!dir) { + switch (errno) { + case ENOENT: + return ReadDirectory_NotExists; + case EACCES: + return ReadDirectory_Permission; + case ENOTDIR: + return ReadDirectory_NotDir; + default: + // ENOMEM: out of memory + // EMFILE: per-process limit on open fds reached + // ENFILE: system-wide limit on total open files reached + return ReadDirectory_Unknown; + } + GB_PANIC("unreachable"); + } + + array_init(fi, a, 0, 100); + + for (;;) { + struct dirent *entry = readdir(dir); + if (entry == nullptr) { + break; + } + + String name = make_string_c(entry->d_name); + if (name == "." || name == "..") { + continue; + } + + String filepath = {}; + filepath.len = path.len+1+name.len; + filepath.text = gb_alloc_array(a, u8, filepath.len+1); + defer (gb_free(a, filepath.text)); + gb_memmove(filepath.text, path.text, path.len); + gb_memmove(filepath.text+path.len, "/", 1); + gb_memmove(filepath.text+path.len+1, name.text, name.len); + filepath.text[filepath.len] = 0; + + + struct stat dir_stat = {}; + + if (stat((char *)filepath.text, &dir_stat)) { + continue; + } + + if (S_ISDIR(dir_stat.st_mode)) { + continue; + } + + i64 size = dir_stat.st_size; + + FileInfo info = {}; + info.name = name; + info.fullpath = path_to_full_path(a, filepath); + info.size = size; + array_add(fi, info); + } + + if (fi->count == 0) { + return ReadDirectory_Empty; + } + + return ReadDirectory_None; +} + + +#else +#error Implement read_directory +#endif + +#if !defined(GB_SYSTEM_WINDOWS) +gb_internal bool write_directory(String path) { + char const *pathname = (char *) path.text; + + if (access(pathname, W_OK) < 0) { + return false; + } + + return true; +} +#else +gb_internal bool write_directory(String path) { + String16 wstr = string_to_string16(heap_allocator(), path); + LPCWSTR wdirectory_name = wstr.text; + + HANDLE directory = CreateFileW(wdirectory_name, + GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + + if (directory == INVALID_HANDLE_VALUE) { + DWORD error_code = GetLastError(); + if (error_code == ERROR_ACCESS_DENIED) { + return false; + } + } + + CloseHandle(directory); + return true; +} +#endif diff --git a/src/threading.cpp b/src/threading.cpp index 725b58c89..ea987890b 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -831,8 +831,53 @@ gb_internal void futex_wait(Futex *f, Footex val) { WaitOnAddress(f, (void *)&val, sizeof(val), INFINITE); } while (f->load() == val); } +#elif defined(GB_SYSTEM_HAIKU) + +#include +#include +#include + +struct MutexCond { + pthread_mutex_t mutex; + pthread_cond_t cond; +}; + +std::unordered_map> futex_map; + +MutexCond* get_mutex_cond(Futex* f) { + if (futex_map.find(f) == futex_map.end()) { + futex_map[f] = std::make_unique(); + pthread_mutex_init(&futex_map[f]->mutex, NULL); + pthread_cond_init(&futex_map[f]->cond, NULL); + } + return futex_map[f].get(); +} + +void futex_signal(Futex *f) { + MutexCond* mc = get_mutex_cond(f); + pthread_mutex_lock(&mc->mutex); + pthread_cond_signal(&mc->cond); + pthread_mutex_unlock(&mc->mutex); +} + +void futex_broadcast(Futex *f) { + MutexCond* mc = get_mutex_cond(f); + pthread_mutex_lock(&mc->mutex); + pthread_cond_broadcast(&mc->cond); + pthread_mutex_unlock(&mc->mutex); +} + +void futex_wait(Futex *f, Footex val) { + MutexCond* mc = get_mutex_cond(f); + pthread_mutex_lock(&mc->mutex); + while (f->load() == val) { + pthread_cond_wait(&mc->cond, &mc->mutex); + } + pthread_mutex_unlock(&mc->mutex); +} + #endif #if defined(GB_SYSTEM_WINDOWS) #pragma warning(pop) -#endif \ No newline at end of file +#endif -- cgit v1.2.3 From 824c831190da1efc351de38743876376a5bb1b08 Mon Sep 17 00:00:00 2001 From: avanspector Date: Sat, 24 Feb 2024 23:46:55 +0100 Subject: Implement futex --- src/threading.cpp | 132 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 99 insertions(+), 33 deletions(-) (limited to 'src/threading.cpp') diff --git a/src/threading.cpp b/src/threading.cpp index ea987890b..1805e51e2 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -831,49 +831,115 @@ gb_internal void futex_wait(Futex *f, Footex val) { WaitOnAddress(f, (void *)&val, sizeof(val), INFINITE); } while (f->load() == val); } + #elif defined(GB_SYSTEM_HAIKU) -#include -#include -#include +// Futex implementation taken from https://tavianator.com/2023/futex.html -struct MutexCond { - pthread_mutex_t mutex; - pthread_cond_t cond; +#include +#include + +struct Futex_Wait_Node { + pthread_t thread; + Futex *futex; + Futex_Wait_Node *prev, *next; }; - -std::unordered_map> futex_map; - -MutexCond* get_mutex_cond(Futex* f) { - if (futex_map.find(f) == futex_map.end()) { - futex_map[f] = std::make_unique(); - pthread_mutex_init(&futex_map[f]->mutex, NULL); - pthread_cond_init(&futex_map[f]->cond, NULL); + +struct Futex_Wait_Queue { + std::atomic_flag spinlock; + Futex_Wait_Node list; + + void lock() { + while (spinlock.test_and_set(std::memory_order_acquire)) { + ; // spin... + } + } + + void unlock() { + spinlock.clear(std::memory_order_release); } - return futex_map[f].get(); -} +}; +// FIXME: This approach may scale badly in the future, +// possible solution - hash map (leads to deadlocks now). + +Futex_Wait_Queue g_waitq = { + .spinlock = ATOMIC_FLAG_INIT, + .list = { + .prev = &g_waitq.list, + .next = &g_waitq.list, + }, +}; + +Futex_Wait_Queue *get_wait_queue(Futex *f) { + // Future hash map method... + return &g_waitq; +} + void futex_signal(Futex *f) { - MutexCond* mc = get_mutex_cond(f); - pthread_mutex_lock(&mc->mutex); - pthread_cond_signal(&mc->cond); - pthread_mutex_unlock(&mc->mutex); + auto waitq = get_wait_queue(f); + + waitq->lock(); + + auto head = &waitq->list; + for (auto waiter = head->next; waiter != head; waiter = waiter->next) { + if (waiter->futex == f) { + pthread_kill(waiter->thread, SIGCONT); + break; + } + } + + waitq->unlock(); } - + void futex_broadcast(Futex *f) { - MutexCond* mc = get_mutex_cond(f); - pthread_mutex_lock(&mc->mutex); - pthread_cond_broadcast(&mc->cond); - pthread_mutex_unlock(&mc->mutex); -} - + auto waitq = get_wait_queue(f); + + waitq->lock(); + + auto head = &waitq->list; + for (auto waiter = head->next; waiter != head; waiter = waiter->next) { + if (waiter->futex == f) { + pthread_kill(waiter->thread, SIGCONT); + } + } + + waitq->unlock(); +} + void futex_wait(Futex *f, Footex val) { - MutexCond* mc = get_mutex_cond(f); - pthread_mutex_lock(&mc->mutex); - while (f->load() == val) { - pthread_cond_wait(&mc->cond, &mc->mutex); - } - pthread_mutex_unlock(&mc->mutex); + auto waitq = get_wait_queue(f); + + waitq->lock(); + + auto head = &waitq->list; + Wait_Node waiter; + waiter.thread = pthread_self(); + waiter.futex = f; + waiter.prev = head; + waiter.next = head->next; + + waiter.prev->next = &waiter; + waiter.next->prev = &waiter; + + sigset_t old_mask, mask; + sigemptyset(&mask); + sigaddset(&mask, SIGCONT); + pthread_sigmask(SIG_BLOCK, &mask, &old_mask); + + if (*f == val) { + waitq->unlock(); + int sig; + sigwait(&mask, &sig); + waitq->lock(); + } + + waiter.prev->next = waiter.next; + waiter.next->prev = waiter.prev; + + pthread_sigmask(SIG_SETMASK, &old_mask, NULL); + + waitq->unlock(); } #endif -- cgit v1.2.3 From 028a79e66c714180852c94db165de3aaa97c1df8 Mon Sep 17 00:00:00 2001 From: avanspector Date: Sun, 25 Feb 2024 02:34:41 +0100 Subject: Update threading.cpp --- src/threading.cpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/threading.cpp') diff --git a/src/threading.cpp b/src/threading.cpp index 1805e51e2..6602bf67e 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -492,6 +492,8 @@ gb_internal u32 thread_current_id(void) { __asm__("mov %%fs:0x10,%0" : "=r"(thread_id)); #elif defined(GB_SYSTEM_LINUX) thread_id = gettid(); +#elif defined(GB_SYSTEM_HAIKU) + thread_id = find_thread(NULL); #else #error Unsupported architecture for thread_current_id() #endif -- cgit v1.2.3 From 24c8b1540920bd181dc399bf86f2ec3a8ea72762 Mon Sep 17 00:00:00 2001 From: avanspector Date: Sun, 25 Feb 2024 02:38:35 +0100 Subject: small fixes --- src/build_settings.cpp | 2 ++ src/threading.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'src/threading.cpp') diff --git a/src/build_settings.cpp b/src/build_settings.cpp index f395cb515..e4e360270 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -885,6 +885,8 @@ gb_internal String internal_odin_root_dir(void) { #include +gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_); + gb_internal String internal_odin_root_dir(void) { String path = global_module_path; isize len, i; diff --git a/src/threading.cpp b/src/threading.cpp index 6602bf67e..56f246955 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -915,7 +915,7 @@ void futex_wait(Futex *f, Footex val) { waitq->lock(); auto head = &waitq->list; - Wait_Node waiter; + Futex_Wait_Node waiter; waiter.thread = pthread_self(); waiter.futex = f; waiter.prev = head; -- cgit v1.2.3 From d4d9f55556ffa71e519ffcc5df431edc097746e2 Mon Sep 17 00:00:00 2001 From: avanspector Date: Fri, 1 Mar 2024 00:41:28 +0100 Subject: Update threading.cpp --- src/threading.cpp | 145 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 102 insertions(+), 43 deletions(-) (limited to 'src/threading.cpp') diff --git a/src/threading.cpp b/src/threading.cpp index 56f246955..a469435d2 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -840,86 +840,131 @@ gb_internal void futex_wait(Futex *f, Footex val) { #include #include + +struct _Spinlock { + std::atomic_flag state; + + void init() { + state.clear(); + } + + void lock() { + while (state.test_and_set(std::memory_order_acquire)) { + #if defined(GB_CPU_X86) + _mm_pause(); + #else + (void)0; // spin... + #endif + } + } + + void unlock() { + state.clear(std::memory_order_release); + } +}; + +struct Futex_Waitq; -struct Futex_Wait_Node { +struct Futex_Waiter { + _Spinlock lock; pthread_t thread; Futex *futex; - Futex_Wait_Node *prev, *next; + Futex_Waitq *waitq; + Futex_Waiter *prev, *next; }; -struct Futex_Wait_Queue { - std::atomic_flag spinlock; - Futex_Wait_Node list; +struct Futex_Waitq { + _Spinlock lock; + Futex_Waiter list; - void lock() { - while (spinlock.test_and_set(std::memory_order_acquire)) { - ; // spin... - } - } - - void unlock() { - spinlock.clear(std::memory_order_release); + void init() { + auto head = &list; + head->prev = head->next = head; } }; // FIXME: This approach may scale badly in the future, // possible solution - hash map (leads to deadlocks now). -Futex_Wait_Queue g_waitq = { - .spinlock = ATOMIC_FLAG_INIT, +Futex_Waitq g_waitq = { + .lock = ATOMIC_FLAG_INIT, .list = { .prev = &g_waitq.list, .next = &g_waitq.list, }, }; -Futex_Wait_Queue *get_wait_queue(Futex *f) { +Futex_Waitq *get_waitq(Futex *f) { // Future hash map method... return &g_waitq; } void futex_signal(Futex *f) { - auto waitq = get_wait_queue(f); + auto waitq = get_waitq(f); - waitq->lock(); + waitq->lock.lock(); auto head = &waitq->list; for (auto waiter = head->next; waiter != head; waiter = waiter->next) { - if (waiter->futex == f) { - pthread_kill(waiter->thread, SIGCONT); - break; - } + if (waiter->futex != f) { + continue; + } + waitq->lock.unlock(); + pthread_kill(waiter->thread, SIGCONT); + return; } - waitq->unlock(); + waitq->lock.unlock(); } void futex_broadcast(Futex *f) { - auto waitq = get_wait_queue(f); + auto waitq = get_waitq(f); - waitq->lock(); + waitq->lock.lock(); auto head = &waitq->list; for (auto waiter = head->next; waiter != head; waiter = waiter->next) { - if (waiter->futex == f) { + if (waiter->futex != f) { + continue; + } + if (waiter->next == head) { + waitq->lock.unlock(); pthread_kill(waiter->thread, SIGCONT); - } + return; + } else { + pthread_kill(waiter->thread, SIGCONT); + } } - waitq->unlock(); + waitq->lock.unlock(); } void futex_wait(Futex *f, Footex val) { - auto waitq = get_wait_queue(f); - - waitq->lock(); - - auto head = &waitq->list; - Futex_Wait_Node waiter; + Futex_Waiter waiter; waiter.thread = pthread_self(); waiter.futex = f; - waiter.prev = head; - waiter.next = head->next; + + auto waitq = get_waitq(f); + while (waitq->lock.state.test_and_set(std::memory_order_acquire)) { + if (f->load(std::memory_order_relaxed) != val) { + return; + } + #if defined(GB_CPU_X86) + _mm_pause(); + #else + (void)0; // spin... + #endif + } + + waiter.waitq = waitq; + waiter.lock.init(); + waiter.lock.lock(); + + auto head = &waitq->list; + waiter.prev = head->prev; + waiter.next = head; + waiter.prev->next = &waiter; + waiter.next->prev = &waiter; waiter.prev->next = &waiter; waiter.next->prev = &waiter; @@ -928,12 +973,25 @@ void futex_wait(Futex *f, Footex val) { sigemptyset(&mask); sigaddset(&mask, SIGCONT); pthread_sigmask(SIG_BLOCK, &mask, &old_mask); - - if (*f == val) { - waitq->unlock(); - int sig; - sigwait(&mask, &sig); - waitq->lock(); + + if (f->load(std::memory_order_relaxed) == val) { + waiter.lock.unlock(); + waitq->lock.unlock(); + + int sig; + sigwait(&mask, &sig); + + waitq->lock.lock(); + waiter.lock.lock(); + + while (waitq != waiter.waitq) { + auto req = waiter.waitq; + waiter.lock.unlock(); + waitq->lock.unlock(); + waitq = req; + waitq->lock.lock(); + waiter.lock.lock(); + } } waiter.prev->next = waiter.next; @@ -941,7 +999,8 @@ void futex_wait(Futex *f, Footex val) { pthread_sigmask(SIG_SETMASK, &old_mask, NULL); - waitq->unlock(); + waiter.lock.unlock(); + waitq->lock.unlock(); } #endif -- cgit v1.2.3 From 9c455b22130d175bac13fb931de08d7ab09308af Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 15 Mar 2024 21:10:11 +0100 Subject: darwin: use new wait on address API if possible --- core/sync/futex_darwin.odin | 64 ++++++++- core/sys/darwin/darwin.odin | 9 ++ core/sys/darwin/sync.odin | 309 ++++++++++++++++++++++++++++++++++++++++++++ src/checker.cpp | 9 ++ src/threading.cpp | 69 ++++++++++ 5 files changed, 458 insertions(+), 2 deletions(-) create mode 100644 core/sys/darwin/sync.odin (limited to 'src/threading.cpp') diff --git a/core/sync/futex_darwin.odin b/core/sync/futex_darwin.odin index 44746e57b..6ea177d1b 100644 --- a/core/sync/futex_darwin.odin +++ b/core/sync/futex_darwin.odin @@ -3,6 +3,7 @@ package sync import "core:c" +import "core:sys/darwin" import "core:time" foreign import System "system:System.framework" @@ -29,8 +30,29 @@ _futex_wait :: proc "contextless" (f: ^Futex, expected: u32) -> bool { } _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, duration: time.Duration) -> bool { + when darwin.WAIT_ON_ADDRESS_AVAILABLE { + s: i32 + if duration > 0 { + s = darwin.os_sync_wait_on_address_with_timeout(f, u64(expected), size_of(Futex), {}, .MACH_ABSOLUTE_TIME, u64(duration)) + } else { + s = darwin.os_sync_wait_on_address(f, u64(expected), size_of(Futex), {}) + } + + if s >= 0 { + return true + } + + switch darwin.errno() { + case -EINTR, -EFAULT: + return true + case -ETIMEDOUT: + return false + case: + _panic("darwin.os_sync_wait_on_address_with_timeout failure") + } + } else { + timeout_ns := u32(duration) * 1000 - s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns) if s >= 0 { return true @@ -45,9 +67,27 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati } return true + } } _futex_signal :: proc "contextless" (f: ^Futex) { + when darwin.WAIT_ON_ADDRESS_AVAILABLE { + loop: for { + s := darwin.os_sync_wake_by_address_any(f, size_of(Futex), {}) + if s >= 0 { + return + } + switch darwin.errno() { + case -EINTR, -EFAULT: + continue loop + case -ENOENT: + return + case: + _panic("darwin.os_sync_wake_by_address_any failure") + } + } + } else { + loop: for { s := __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, 0) if s >= 0 { @@ -62,9 +102,28 @@ _futex_signal :: proc "contextless" (f: ^Futex) { _panic("futex_wake_single failure") } } + + } } _futex_broadcast :: proc "contextless" (f: ^Futex) { + when darwin.WAIT_ON_ADDRESS_AVAILABLE { + loop: for { + s := darwin.os_sync_wake_by_address_all(f, size_of(Futex), {}) + if s >= 0 { + return + } + switch darwin.errno() { + case -EINTR, -EFAULT: + continue loop + case -ENOENT: + return + case: + _panic("darwin.os_sync_wake_by_address_all failure") + } + } + } else { + loop: for { s := __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0) if s >= 0 { @@ -79,5 +138,6 @@ _futex_broadcast :: proc "contextless" (f: ^Futex) { _panic("futex_wake_all failure") } } -} + } +} diff --git a/core/sys/darwin/darwin.odin b/core/sys/darwin/darwin.odin index a3e07277c..ddd25a76c 100644 --- a/core/sys/darwin/darwin.odin +++ b/core/sys/darwin/darwin.odin @@ -3,6 +3,8 @@ package darwin import "core:c" +foreign import system "system:System.framework" + Bool :: b8 RUsage :: struct { @@ -24,3 +26,10 @@ RUsage :: struct { ru_nivcsw: c.long, } +foreign system { + __error :: proc() -> ^i32 --- +} + +errno :: #force_inline proc "contextless" () -> i32 { + return __error()^ +} diff --git a/core/sys/darwin/sync.odin b/core/sys/darwin/sync.odin new file mode 100644 index 000000000..b9fc82ecc --- /dev/null +++ b/core/sys/darwin/sync.odin @@ -0,0 +1,309 @@ +package darwin + +foreign import system "system:System.framework" + +// #define OS_WAIT_ON_ADDR_AVAILABILITY \ +// __API_AVAILABLE(macos(14.4), ios(17.4), tvos(17.4), watchos(10.4)) +when ODIN_OS == .Darwin { + when ODIN_PLATFORM_SUBTARGET == .iOS && MINIMUM_OS_VERSION > 17_04_00 { + WAIT_ON_ADDRESS_AVAILABLE :: true + } else when MINIMUM_OS_VERSION > 14_04_00 { + WAIT_ON_ADDRESS_AVAILABLE :: true + } else { + WAIT_ON_ADDRESS_AVAILABLE :: false + } +} else { + WAIT_ON_ADDRESS_AVAILABLE :: false +} + +os_sync_wait_on_address_flag :: enum u32 { + // This flag should be used as a default flag when no other flags listed below are required. + NONE, + + // This flag should be used when synchronizing among multiple processes by + // placing the @addr passed to os_sync_wait_on_address and its variants + // in a shared memory region. + // + // When using this flag, it is important to pass OS_SYNC_WAKE_BY_ADDRESS_SHARED + // flag along with the exact same @addr to os_sync_wake_by_address_any and + // its variants to correctly find and wake up blocked waiters on the @addr. + // + // This flag should not be used when synchronizing among multiple threads of + // a single process. It allows the kernel to perform performance optimizations + // as the @addr is local to the calling process. + SHARED, +} + +os_sync_wait_on_address_flags :: bit_set[os_sync_wait_on_address_flag; u32] + +os_sync_wake_by_address_flag :: enum u32 { + // This flag should be used as a default flag when no other flags listed below are required. + NONE, + + // This flag should be used when synchronizing among multiple processes by + // placing the @addr passed to os_sync_wake_by_address_any and its variants + // in a shared memory region. + // + // When using this flag, it is important to pass OS_SYNC_WAIT_ON_ADDRESS_SHARED + // flag along with the exact same @addr to os_sync_wait_on_address and + // its variants to correctly find and wake up blocked waiters on the @addr. + // + // This flag should not be used when synchronizing among multiple threads of + // a single process. It allows the kernel to perform performance optimizations + // as the @addr is local the calling process. + SHARED, +} + +os_sync_wake_by_address_flags :: bit_set[os_sync_wake_by_address_flag; u32] + +os_clockid :: enum u32 { + MACH_ABSOLUTE_TIME = 32, +} + +foreign system { + // This function provides an atomic compare-and-wait functionality that + // can be used to implement other higher level synchronization primitives. + // + // It reads a value from @addr, compares it to expected @value and blocks + // the calling thread if they are equal. This sequence of operations is + // done atomically with respect to other concurrent operations that can + // be performed on this @addr by other threads using this same function + // or os_sync_wake_by_addr variants. At this point, the blocked calling + // thread is considered to be a waiter on this @addr, waiting to be woken + // up by a call to os_sync_wake_by_addr variants. If the value at @addr + // turns out to be different than expected, the calling thread returns + // immediately without blocking. + // + // This function is expected to be used for implementing synchronization + // primitives that do not have a sense of ownership (e.g. condition + // variables, semaphores) as it does not provide priority inversion avoidance. + // For locking primitives, it is recommended that you use existing OS + // primitives such as os_unfair_lock API family / pthread mutex or + // std::mutex. + // + // @param addr + // The userspace address to be used for atomic compare-and-wait. + // This address must be aligned to @size. + // + // @param value + // The value expected at @addr. + // + // @param size + // The size of @value, in bytes. This can be either 4 or 8 today. + // For @value of @size 4 bytes, the upper 4 bytes of @value are ignored. + // + // @param flags + // Flags to alter behavior of os_sync_wait_on_address. + // See os_sync_wait_on_address_flags_t. + // + // @return + // If the calling thread is woken up by a call to os_sync_wake_by_addr + // variants or the value at @addr is different than expected, this function + // returns successfully and the return value indicates the number + // of outstanding waiters blocked on this address. + // In the event of an error, returns -1 with errno set to indicate the error. + // + // EINVAL : Invalid flags or size. + // EINVAL : The @addr passed is NULL or misaligned. + // EINVAL : The operation associated with existing kernel state + // at this @addr is inconsistent with what the caller + // has requested. + // It is important to make sure consistent values are + // passed across wait and wake APIs for @addr, @size + // and the shared memory specification + // (See os_sync_wait_on_address_flags_t). + // + // It is possible for the os_sync_wait_on_address and its variants to perform + // an early return in the event of following errors where user may want to + // re-try the wait operation. E.g. low memory conditions could cause such early + // return. + // It is important to read the current value at the @addr before re-trying + // to ensure that the new value still requires waiting on @addr. + // + // ENOMEM : Unable to allocate memory for kernel internal data + // structures. + // EINTR : The syscall was interrupted / spurious wake up. + // EFAULT : Unable to read value from the @addr. Kernel copyin failed. + // It is possible to receive EFAULT error in following cases: + // 1. The @addr is an invalid address. This is a programmer error. + // 2. The @addr is valid; but, this is a transient error such as + // due to low memory conditions. User may want to re-try the wait + // operation. + // Following code snippet illustrates a possible re-try loop. + // + // retry: + // current = atomic_load_explicit(addr, memory_order_relaxed); + // if (current != expected) { + // int ret = os_sync_wait_on_address(addr, current, size, flags); + // if ((ret < 0) && ((errno == EINTR) || (errno == EFAULT))) { + // goto retry; + // } + // } + // + os_sync_wait_on_address :: proc( + addr: rawptr, + value: u64, + size: uint, + flags: os_sync_wait_on_address_flags, + ) -> i32 --- + + // This function is a variant of os_sync_wait_on_address that + // allows the calling thread to specify a deadline + // until which it is willing to block. + // + // @param addr + // The userspace address to be used for atomic compare-and-wait. + // This address must be aligned to @size. + // + // @param value + // The value expected at @addr. + // + // @param size + // The size of @value, in bytes. This can be either 4 or 8 today. + // For @value of @size 4 bytes, the upper 4 bytes of @value are ignored. + // + // @param flags + // Flags to alter behavior of os_sync_wait_on_address_with_deadline. + // See os_sync_wait_on_address_flags_t. + // + // @param clockid + // This value anchors @deadline argument to a specific clock id. + // See os_clockid_t. + // + // @param deadline + // This value is used to specify a deadline until which the calling + // thread is willing to block. + // Passing zero for the @deadline results in an error being returned. + // It is recommended to use os_sync_wait_on_address API to block + // indefinitely until woken up by a call to os_sync_wake_by_address_any + // or os_sync_wake_by_address_all APIs. + // + // @return + // If the calling thread is woken up by a call to os_sync_wake_by_addr + // variants or the value at @addr is different than expected, this function + // returns successfully and the return value indicates the number + // of outstanding waiters blocked on this address. + // In the event of an error, returns -1 with errno set to indicate the error. + // + // In addition to errors returned by os_sync_wait_on_address, this function + // can return the following additional error codes. + // + // EINVAL : Invalid clock id. + // EINVAL : The @deadline passed is 0. + // ETIMEDOUT : Deadline expired. + os_sync_wait_on_address_with_deadline :: proc( + addr: rawptr, + value: u64, + size: uint, + flags: os_sync_wait_on_address_flags, + clockid: os_clockid, + deadline: u64, + ) -> i32 --- + + // This function is a variant of os_sync_wait_on_address that + // allows the calling thread to specify a timeout + // until which it is willing to block. + // + // @param addr + // The userspace address to be used for atomic compare-and-wait. + // This address must be aligned to @size. + // + // @param value + // The value expected at @addr. + // + // @param size + // The size of @value, in bytes. This can be either 4 or 8 today. + // For @value of @size 4 bytes, the upper 4 bytes of @value are ignored. + // + // @param flags + // Flags to alter behavior of os_sync_wait_on_address_with_timeout. + // See os_sync_wait_on_address_flags_t. + // + // @param clockid + // This value anchors @timeout_ns argument to a specific clock id. + // See os_clockid_t. + // + // @param timeout_ns + // This value is used to specify a timeout in nanoseconds until which + // the calling thread is willing to block. + // Passing zero for the @timeout_ns results in an error being returned. + // It is recommended to use os_sync_wait_on_address API to block + // indefinitely until woken up by a call to os_sync_wake_by_address_any + // or os_sync_wake_by_address_all APIs. + // + // @return + // If the calling thread is woken up by a call to os_sync_wake_by_address + // variants or the value at @addr is different than expected, this function + // returns successfully and the return value indicates the number + // of outstanding waiters blocked on this address. + // In the event of an error, returns -1 with errno set to indicate the error. + // + // In addition to errors returned by os_sync_wait_on_address, this function + // can return the following additional error codes. + // + // EINVAL : Invalid clock id. + // EINVAL : The @timeout_ns passed is 0. + // ETIMEDOUT : Timeout expired. + os_sync_wait_on_address_with_timeout :: proc( + addr: rawptr, + value: u64, + size: uint, + flags: os_sync_wait_on_address_flags, + clockid: os_clockid, + timeout_ns: u64, + ) -> i32 --- + + // This function wakes up one waiter out of all those blocked in os_sync_wait_on_address + // or its variants on the @addr. No guarantee is provided about which + // specific waiter is woken up. + // + // @param addr + // The userspace address to be used for waking up the blocked waiter. + // It should be same as what is passed to os_sync_wait_on_address or its variants. + // + // @param size + // The size of lock value, in bytes. This can be either 4 or 8 today. + // It should be same as what is passed to os_sync_wait_on_address or its variants. + // + // @param flags + // Flags to alter behavior of os_sync_wake_by_address_any. + // See os_sync_wake_by_address_flags_t. + // + // @return + // Returns 0 on success. + // In the event of an error, returns -1 with errno set to indicate the error. + // + // EINVAL : Invalid flags or size. + // EINVAL : The @addr passed is NULL. + // EINVAL : The operation associated with existing kernel state + // at this @addr is inconsistent with what caller + // has requested. + // It is important to make sure consistent values are + // passed across wait and wake APIs for @addr, @size + // and the shared memory specification + // (See os_sync_wake_by_address_flags_t). + // ENOENT : No waiter(s) found waiting on the @addr. + os_sync_wake_by_address_any :: proc(addr: rawptr, size: uint, flags: os_sync_wait_on_address_flags) -> i32 --- + + // This function is a variant of os_sync_wake_by_address_any that wakes up all waiters + // blocked in os_sync_wait_on_address or its variants. + // + // @param addr + // The userspace address to be used for waking up the blocked waiters. + // It should be same as what is passed to os_sync_wait_on_address or its variants. + // + // @param size + // The size of lock value, in bytes. This can be either 4 or 8 today. + // It should be same as what is passed to os_sync_wait_on_address or its variants. + // + // @param flags + // Flags to alter behavior of os_sync_wake_by_address_all. + // See os_sync_wake_by_address_flags_t. + // + // @return + // Returns 0 on success. + // In the event of an error, returns -1 with errno set to indicate the error. + // + // This function returns same error codes as returned by os_sync_wait_on_address. + os_sync_wake_by_address_all :: proc(addr: rawptr, size: uint, flags: os_sync_wait_on_address_flags) -> i32 --- +} diff --git a/src/checker.cpp b/src/checker.cpp index 72c0ae574..797cdb5f1 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1097,6 +1097,15 @@ gb_internal void init_universal(void) { scope_insert(intrinsics_pkg->scope, t_atomic_memory_order->Named.type_name); } + { + int minimum_os_version = 0; + if (build_context.minimum_os_version_string != "") { + int major, minor, revision = 0; + sscanf(cast(const char *)(build_context.minimum_os_version_string.text), "%d.%d.%d", &major, &minor, &revision); + minimum_os_version = (major*10000)+(minor*100)+revision; + } + add_global_constant("MINIMUM_OS_VERSION", t_untyped_integer, exact_value_i64(minimum_os_version)); + } add_global_bool_constant("ODIN_DEBUG", bc->ODIN_DEBUG); add_global_bool_constant("ODIN_DISABLE_ASSERT", bc->ODIN_DISABLE_ASSERT); diff --git a/src/threading.cpp b/src/threading.cpp index a469435d2..3197b19a3 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -760,6 +760,11 @@ gb_internal void futex_wait(Futex *f, Footex val) { #elif defined(GB_SYSTEM_OSX) +#if __has_include() + #define DARWIN_WAIT_ON_ADDRESS_AVAILABLE + #include +#endif + #define UL_COMPARE_AND_WAIT 0x00000001 #define ULF_NO_ERRNO 0x01000000 @@ -767,6 +772,23 @@ extern "C" int __ulock_wait(uint32_t operation, void *addr, uint64_t value, uint extern "C" int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value); gb_internal void futex_signal(Futex *f) { + #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE + if (__builtin_available(macOS 14.4, *)) { + for (;;) { + int ret = os_sync_wake_by_address_any(f, sizeof(Futex), OS_SYNC_WAKE_BY_ADDRESS_NONE); + if (ret >= 0) { + return; + } + if (errno == EINTR || errno == EFAULT) { + continue; + } + if (errno == ENOENT) { + return; + } + GB_PANIC("Failed in futex wake %d %d!\n", ret, errno); + } + } else { + #endif for (;;) { int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, 0); if (ret >= 0) { @@ -780,9 +802,29 @@ gb_internal void futex_signal(Futex *f) { } GB_PANIC("Failed in futex wake!\n"); } + #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE + } + #endif } gb_internal void futex_broadcast(Futex *f) { + #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE + if (__builtin_available(macOS 14.4, *)) { + for (;;) { + int ret = os_sync_wake_by_address_all(f, sizeof(Footex), OS_SYNC_WAKE_BY_ADDRESS_NONE); + if (ret >= 0) { + return; + } + if (errno == EINTR || errno == EFAULT) { + continue; + } + if (errno == ENOENT) { + return; + } + GB_PANIC("Failed in futext wake %d %d!\n", ret, errno); + } + } else { + #endif for (;;) { enum { ULF_WAKE_ALL = 0x00000100 }; int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0); @@ -797,9 +839,32 @@ gb_internal void futex_broadcast(Futex *f) { } GB_PANIC("Failed in futex wake!\n"); } + #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE + } + #endif } gb_internal void futex_wait(Futex *f, Footex val) { + #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE + if (__builtin_available(macOS 14.4, *)) { + for (;;) { + int ret = os_sync_wait_on_address(f, cast(uint64_t)(val), sizeof(Footex), OS_SYNC_WAIT_ON_ADDRESS_NONE); + if (ret >= 0) { + if (*f != val) { + return; + } + continue; + } + if (errno == EINTR || errno == EFAULT) { + continue; + } + if (errno == ENOENT) { + return; + } + GB_PANIC("Failed in futex wait %d %d!\n", ret, errno); + } + } else { + #endif for (;;) { int ret = __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, val, 0); if (ret >= 0) { @@ -817,7 +882,11 @@ gb_internal void futex_wait(Futex *f, Footex val) { GB_PANIC("Failed in futex wait!\n"); } + #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE + } + #endif } + #elif defined(GB_SYSTEM_WINDOWS) gb_internal void futex_signal(Futex *f) { -- cgit v1.2.3 From 6d4f30de1a85fe51159808d70a342c1c915d15de Mon Sep 17 00:00:00 2001 From: rick-masters Date: Sun, 24 Mar 2024 16:28:55 +0000 Subject: Fix fields_wait_signal futex. --- src/check_builtin.cpp | 2 ++ src/check_expr.cpp | 1 + src/check_type.cpp | 4 ++++ src/threading.cpp | 2 +- 4 files changed, 8 insertions(+), 1 deletion(-) (limited to 'src/threading.cpp') diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 53e4acbd1..f4aa9567d 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -3393,6 +3393,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As elem->Struct.tags = gb_alloc_array(permanent_allocator(), String, fields.count); elem->Struct.node = dummy_node_struct; type_set_offsets(elem); + wait_signal_set(&elem->Struct.fields_wait_signal); } Type *soa_type = make_soa_struct_slice(c, dummy_node_soa, nullptr, elem); @@ -3766,6 +3767,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As soa_struct->Struct.tags[i] = old_struct->Struct.tags[i]; } } + wait_signal_set(&soa_struct->Struct.fields_wait_signal); Token token = {}; token.string = str_lit("Base_Type"); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index fd10374c1..d19af4a62 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -8873,6 +8873,7 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * break; } + wait_signal_until_available(&t->Struct.fields_wait_signal); isize field_count = t->Struct.fields.count; isize min_field_count = t->Struct.fields.count; for (isize i = min_field_count-1; i >= 0; i--) { diff --git a/src/check_type.cpp b/src/check_type.cpp index 0b9042905..ae79d4edc 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2490,6 +2490,7 @@ gb_internal Type *get_map_cell_type(Type *type) { s->Struct.fields[0] = alloc_entity_field(scope, make_token_ident("v"), alloc_type_array(type, len), false, 0, EntityState_Resolved); s->Struct.fields[1] = alloc_entity_field(scope, make_token_ident("_"), alloc_type_array(t_u8, padding), false, 1, EntityState_Resolved); s->Struct.scope = scope; + wait_signal_set(&s->Struct.fields_wait_signal); gb_unused(type_size_of(s)); return s; @@ -2520,6 +2521,7 @@ gb_internal void init_map_internal_types(Type *type) { metadata_type->Struct.fields[4] = alloc_entity_field(metadata_scope, make_token_ident("value_cell"), value_cell, false, 4, EntityState_Resolved); metadata_type->Struct.scope = metadata_scope; metadata_type->Struct.node = nullptr; + wait_signal_set(&metadata_type->Struct.fields_wait_signal); gb_unused(type_size_of(metadata_type)); @@ -2537,6 +2539,7 @@ gb_internal void init_map_internal_types(Type *type) { debug_type->Struct.fields[3] = alloc_entity_field(scope, make_token_ident("__metadata"), metadata_type, false, 3, EntityState_Resolved); debug_type->Struct.scope = scope; debug_type->Struct.node = nullptr; + wait_signal_set(&debug_type->Struct.fields_wait_signal); gb_unused(type_size_of(debug_type)); @@ -2832,6 +2835,7 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e add_entity(ctx, scope, nullptr, base_type_entity); add_type_info_type(ctx, soa_struct); + wait_signal_set(&soa_struct->Struct.fields_wait_signal); return soa_struct; } diff --git a/src/threading.cpp b/src/threading.cpp index a469435d2..9e4a1607c 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -113,7 +113,7 @@ struct Wait_Signal { gb_internal void wait_signal_until_available(Wait_Signal *ws) { if (ws->futex.load() == 0) { - futex_wait(&ws->futex, 1); + futex_wait(&ws->futex, 0); } } -- cgit v1.2.3 From e5629dafd0be1be42a3bd183a09ff82492b6b386 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 25 Mar 2024 13:23:43 +0000 Subject: Potentially fix a race condition with parapoly types (related to #3328) --- src/check_expr.cpp | 11 +++- src/check_type.cpp | 179 +++++++++++++++++++++++++++-------------------------- src/checker.hpp | 9 ++- src/threading.cpp | 2 - 4 files changed, 104 insertions(+), 97 deletions(-) (limited to 'src/threading.cpp') diff --git a/src/check_expr.cpp b/src/check_expr.cpp index fd10374c1..44d65e376 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -79,7 +79,6 @@ gb_internal Type * check_type_expr (CheckerContext *c, Ast *exp gb_internal Type * make_optional_ok_type (Type *value, bool typed=true); gb_internal Entity * check_selector (CheckerContext *c, Operand *operand, Ast *node, Type *type_hint); gb_internal Entity * check_ident (CheckerContext *c, Operand *o, Ast *n, Type *named_type, Type *type_hint, bool allow_import_name); -gb_internal Entity * find_polymorphic_record_entity (CheckerContext *c, Type *original_type, isize param_count, Array const &ordered_operands, bool *failure); gb_internal void check_not_tuple (CheckerContext *c, Operand *operand); gb_internal void convert_to_typed (CheckerContext *c, Operand *operand, Type *target_type); gb_internal gbString expr_to_string (Ast *expression); @@ -121,6 +120,8 @@ gb_internal isize get_procedure_param_count_excluding_defaults(Type *pt, isize * gb_internal bool is_expr_inferred_fixed_array(Ast *type_expr); +gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types, isize param_count, Array const &ordered_operands); + enum LoadDirectiveResult { LoadDirective_Success = 0, LoadDirective_Error = 1, @@ -7171,8 +7172,12 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O } { - bool failure = false; - Entity *found_entity = find_polymorphic_record_entity(c, original_type, param_count, ordered_operands, &failure); + GenTypesData *found_gen_types = ensure_polymorphic_record_entity_has_gen_types(c, original_type); + + mutex_lock(&found_gen_types->mutex); + defer (mutex_unlock(&found_gen_types->mutex)); + Entity *found_entity = find_polymorphic_record_entity(found_gen_types, param_count, ordered_operands); + if (found_entity) { operand->mode = Addressing_Type; operand->type = found_entity->type; diff --git a/src/check_type.cpp b/src/check_type.cpp index 0b9042905..e22d4b62b 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -260,84 +260,21 @@ gb_internal bool check_custom_align(CheckerContext *ctx, Ast *node, i64 *align_, } -gb_internal Entity *find_polymorphic_record_entity(CheckerContext *ctx, Type *original_type, isize param_count, Array const &ordered_operands, bool *failure) { - rw_mutex_shared_lock(&ctx->info->gen_types_mutex); // @@global - - auto *found_gen_types = map_get(&ctx->info->gen_types, original_type); - if (found_gen_types == nullptr) { - rw_mutex_shared_unlock(&ctx->info->gen_types_mutex); // @@global - return nullptr; - } - - rw_mutex_shared_lock(&found_gen_types->mutex); // @@local - defer (rw_mutex_shared_unlock(&found_gen_types->mutex)); // @@local - - rw_mutex_shared_unlock(&ctx->info->gen_types_mutex); // @@global - - for (Entity *e : found_gen_types->types) { - Type *t = base_type(e->type); - TypeTuple *tuple = nullptr; - switch (t->kind) { - case Type_Struct: - if (t->Struct.polymorphic_params) { - tuple = &t->Struct.polymorphic_params->Tuple; - } - break; - case Type_Union: - if (t->Union.polymorphic_params) { - tuple = &t->Union.polymorphic_params->Tuple; - } - break; - } - GB_ASSERT_MSG(tuple != nullptr, "%s :: %s", type_to_string(e->type), type_to_string(t)); - GB_ASSERT(param_count == tuple->variables.count); - - bool skip = false; - - for (isize j = 0; j < param_count; j++) { - Entity *p = tuple->variables[j]; - Operand o = {}; - if (j < ordered_operands.count) { - o = ordered_operands[j]; - } - if (o.expr == nullptr) { - continue; - } - Entity *oe = entity_of_node(o.expr); - if (p == oe) { - // NOTE(bill): This is the same type, make sure that it will be be same thing and use that - // Saves on a lot of checking too below - continue; - } - - if (p->kind == Entity_TypeName) { - if (is_type_polymorphic(o.type)) { - // NOTE(bill): Do not add polymorphic version to the gen_types - skip = true; - break; - } - if (!are_types_identical(o.type, p->type)) { - skip = true; - break; - } - } else if (p->kind == Entity_Constant) { - if (!compare_exact_values(Token_CmpEq, o.value, p->Constant.value)) { - skip = true; - break; - } - if (!are_types_identical(o.type, p->type)) { - skip = true; - break; - } - } else { - GB_PANIC("Unknown entity kind"); - } - } - if (!skip) { - return e; - } +gb_internal GenTypesData *ensure_polymorphic_record_entity_has_gen_types(CheckerContext *ctx, Type *original_type) { + mutex_lock(&ctx->info->gen_types_mutex); // @@global + + GenTypesData *found_gen_types = nullptr; + auto *found_gen_types_ptr = map_get(&ctx->info->gen_types, original_type); + if (found_gen_types_ptr == nullptr) { + GenTypesData *gen_types = gb_alloc_item(permanent_allocator(), GenTypesData); + gen_types->types = array_make(heap_allocator()); + map_set(&ctx->info->gen_types, original_type, gen_types); + found_gen_types_ptr = map_get(&ctx->info->gen_types, original_type); } - return nullptr; + found_gen_types = *found_gen_types_ptr; + GB_ASSERT(found_gen_types != nullptr); + mutex_unlock(&ctx->info->gen_types_mutex); // @@global + return found_gen_types; } @@ -367,19 +304,16 @@ gb_internal void add_polymorphic_record_entity(CheckerContext *ctx, Ast *node, T // TODO(bill): Is this even correct? Or should the metadata be copied? e->TypeName.objc_metadata = original_type->Named.type_name->TypeName.objc_metadata; - rw_mutex_lock(&ctx->info->gen_types_mutex); - auto *found_gen_types = map_get(&ctx->info->gen_types, original_type); - if (found_gen_types) { - rw_mutex_lock(&found_gen_types->mutex); - array_add(&found_gen_types->types, e); - rw_mutex_unlock(&found_gen_types->mutex); - } else { - GenTypesData gen_types = {}; - gen_types.types = array_make(heap_allocator()); - array_add(&gen_types.types, e); - map_set(&ctx->info->gen_types, original_type, gen_types); + auto *found_gen_types = ensure_polymorphic_record_entity_has_gen_types(ctx, original_type); + mutex_lock(&found_gen_types->mutex); + defer (mutex_unlock(&found_gen_types->mutex)); + + for (Entity *prev : found_gen_types->types) { + if (prev == e) { + return; + } } - rw_mutex_unlock(&ctx->info->gen_types_mutex); + array_add(&found_gen_types->types, e); } @@ -612,6 +546,73 @@ gb_internal bool check_record_poly_operand_specialization(CheckerContext *ctx, T return true; } +gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types, isize param_count, Array const &ordered_operands) { + for (Entity *e : found_gen_types->types) { + Type *t = base_type(e->type); + TypeTuple *tuple = nullptr; + switch (t->kind) { + case Type_Struct: + if (t->Struct.polymorphic_params) { + tuple = &t->Struct.polymorphic_params->Tuple; + } + break; + case Type_Union: + if (t->Union.polymorphic_params) { + tuple = &t->Union.polymorphic_params->Tuple; + } + break; + } + GB_ASSERT_MSG(tuple != nullptr, "%s :: %s", type_to_string(e->type), type_to_string(t)); + GB_ASSERT(param_count == tuple->variables.count); + + bool skip = false; + + for (isize j = 0; j < param_count; j++) { + Entity *p = tuple->variables[j]; + Operand o = {}; + if (j < ordered_operands.count) { + o = ordered_operands[j]; + } + if (o.expr == nullptr) { + continue; + } + Entity *oe = entity_of_node(o.expr); + if (p == oe) { + // NOTE(bill): This is the same type, make sure that it will be be same thing and use that + // Saves on a lot of checking too below + continue; + } + + if (p->kind == Entity_TypeName) { + if (is_type_polymorphic(o.type)) { + // NOTE(bill): Do not add polymorphic version to the gen_types + skip = true; + break; + } + if (!are_types_identical(o.type, p->type)) { + skip = true; + break; + } + } else if (p->kind == Entity_Constant) { + if (!compare_exact_values(Token_CmpEq, o.value, p->Constant.value)) { + skip = true; + break; + } + if (!are_types_identical(o.type, p->type)) { + skip = true; + break; + } + } else { + GB_PANIC("Unknown entity kind"); + } + } + if (!skip) { + return e; + } + } + return nullptr; +}; + gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast *node, Array *poly_operands, Type *named_type, Type *original_type_for_poly) { GB_ASSERT(is_type_struct(struct_type)); diff --git a/src/checker.hpp b/src/checker.hpp index eea99578e..e0dc54a87 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -360,7 +360,7 @@ struct GenProcsData { struct GenTypesData { Array types; - RwMutex mutex; + RecursiveMutex mutex; }; // CheckerInfo stores all the symbol information for a type-checked program @@ -400,8 +400,8 @@ struct CheckerInfo { RecursiveMutex lazy_mutex; // Mutex required for lazy type checking of specific files - RwMutex gen_types_mutex; - PtrMap gen_types; + BlockingMutex gen_types_mutex; + PtrMap gen_types; BlockingMutex type_info_mutex; // NOT recursive Array type_info_types; @@ -560,3 +560,6 @@ gb_internal void init_core_context(Checker *c); gb_internal void init_mem_allocator(Checker *c); gb_internal void add_untyped_expressions(CheckerInfo *cinfo, UntypedExprInfoMap *untyped); + + +gb_internal GenTypesData *ensure_polymorphic_record_entity_has_gen_types(CheckerContext *ctx, Type *original_type); \ No newline at end of file diff --git a/src/threading.cpp b/src/threading.cpp index a469435d2..d9538f66e 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -122,8 +122,6 @@ gb_internal void wait_signal_set(Wait_Signal *ws) { futex_broadcast(&ws->futex); } - - struct MutexGuard { MutexGuard() = delete; MutexGuard(MutexGuard const &) = delete; -- cgit v1.2.3 From 4558f3992a47b4597563152baf26f1d2b5684b4d Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Tue, 16 Apr 2024 14:27:29 +0200 Subject: Initial commit of NetBSD port --- base/runtime/entry_unix.odin | 2 +- base/runtime/heap_allocator_unix.odin | 2 +- base/runtime/os_specific_bsd.odin | 8 +- build_odin.sh | 4 + core/os/os_netbsd.odin | 755 ++++++++++++++++++++++++++++++++++ core/os/stat_unix.odin | 2 +- core/os/stream.odin | 4 +- core/sync/futex_netbsd.odin | 166 ++++++++ core/sync/primitives_netbsd.odin | 9 + core/sys/unix/pthread_netbsd.odin | 103 +++++ core/sys/unix/pthread_unix.odin | 2 +- core/sys/unix/signal_netbsd.odin | 31 ++ core/sys/unix/time_unix.odin | 5 +- core/thread/thread_unix.odin | 2 +- core/time/time_unix.odin | 2 +- src/build_settings.cpp | 17 + src/checker.cpp | 1 + src/gb/gb.h | 43 +- src/path.cpp | 2 +- src/threading.cpp | 11 +- 20 files changed, 1154 insertions(+), 17 deletions(-) create mode 100644 core/os/os_netbsd.odin create mode 100644 core/sync/futex_netbsd.odin create mode 100644 core/sync/primitives_netbsd.odin create mode 100644 core/sys/unix/pthread_netbsd.odin create mode 100644 core/sys/unix/signal_netbsd.odin (limited to 'src/threading.cpp') diff --git a/base/runtime/entry_unix.odin b/base/runtime/entry_unix.odin index e49698e6e..7d7252625 100644 --- a/base/runtime/entry_unix.odin +++ b/base/runtime/entry_unix.odin @@ -1,5 +1,5 @@ //+private -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku //+no-instrumentation package runtime diff --git a/base/runtime/heap_allocator_unix.odin b/base/runtime/heap_allocator_unix.odin index 2b6698885..a8a4e9169 100644 --- a/base/runtime/heap_allocator_unix.odin +++ b/base/runtime/heap_allocator_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku //+private package runtime diff --git a/base/runtime/os_specific_bsd.odin b/base/runtime/os_specific_bsd.odin index 9cd065ff6..46ce51166 100644 --- a/base/runtime/os_specific_bsd.odin +++ b/base/runtime/os_specific_bsd.odin @@ -1,4 +1,4 @@ -//+build freebsd, openbsd +//+build freebsd, openbsd, netbsd //+private package runtime @@ -9,7 +9,11 @@ foreign libc { @(link_name="write") _unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int --- - __error :: proc() -> ^i32 --- + when ODIN_OS == .NetBSD { + @(link_name="__errno") __error :: proc() -> ^i32 --- + } else { + __error :: proc() -> ^i32 --- + } } _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { diff --git a/build_odin.sh b/build_odin.sh index c53766290..df4451060 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -69,6 +69,10 @@ FreeBSD) CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" ;; +NetBSD) + CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" + LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" + ;; Linux) CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" LDFLAGS="$LDFLAGS -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)" diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin new file mode 100644 index 000000000..c75715ac7 --- /dev/null +++ b/core/os/os_netbsd.odin @@ -0,0 +1,755 @@ +package os + +foreign import dl "system:dl" +foreign import libc "system:c" + +import "base:runtime" +import "core:strings" +import "core:sys/unix" +import "core:c" + +Handle :: distinct i32 +File_Time :: distinct u64 +Errno :: distinct i32 + +INVALID_HANDLE :: ~Handle(0) + +ERROR_NONE: Errno : 0 /* No error */ +EPERM: Errno : 1 /* Operation not permitted */ +ENOENT: Errno : 2 /* No such file or directory */ +EINTR: Errno : 4 /* Interrupted system call */ +ESRCH: Errno : 3 /* No such process */ +EIO: Errno : 5 /* Input/output error */ +ENXIO: Errno : 6 /* Device not configured */ +E2BIG: Errno : 7 /* Argument list too long */ +ENOEXEC: Errno : 8 /* Exec format error */ +EBADF: Errno : 9 /* Bad file descriptor */ +ECHILD: Errno : 10 /* No child processes */ +EDEADLK: Errno : 11 /* Resource deadlock avoided. 11 was EAGAIN */ +ENOMEM: Errno : 12 /* Cannot allocate memory */ +EACCES: Errno : 13 /* Permission denied */ +EFAULT: Errno : 14 /* Bad address */ +ENOTBLK: Errno : 15 /* Block device required */ +EBUSY: Errno : 16 /* Device busy */ +EEXIST: Errno : 17 /* File exists */ +EXDEV: Errno : 18 /* Cross-device link */ +ENODEV: Errno : 19 /* Operation not supported by device */ +ENOTDIR: Errno : 20 /* Not a directory */ +EISDIR: Errno : 21 /* Is a directory */ +EINVAL: Errno : 22 /* Invalid argument */ +ENFILE: Errno : 23 /* Too many open files in system */ +EMFILE: Errno : 24 /* Too many open files */ +ENOTTY: Errno : 25 /* Inappropriate ioctl for device */ +ETXTBSY: Errno : 26 /* Text file busy */ +EFBIG: Errno : 27 /* File too large */ +ENOSPC: Errno : 28 /* No space left on device */ +ESPIPE: Errno : 29 /* Illegal seek */ +EROFS: Errno : 30 /* Read-only file system */ +EMLINK: Errno : 31 /* Too many links */ +EPIPE: Errno : 32 /* Broken pipe */ + +/* math software */ +EDOM: Errno : 33 /* Numerical argument out of domain */ +ERANGE: Errno : 34 /* Result too large or too small */ + +/* non-blocking and interrupt i/o */ +EAGAIN: Errno : 35 /* Resource temporarily unavailable */ +EWOULDBLOCK: Errno : EAGAIN /* Operation would block */ +EINPROGRESS: Errno : 36 /* Operation now in progress */ +EALREADY: Errno : 37 /* Operation already in progress */ + +/* ipc/network software -- argument errors */ +ENOTSOCK: Errno : 38 /* Socket operation on non-socket */ +EDESTADDRREQ: Errno : 39 /* Destination address required */ +EMSGSIZE: Errno : 40 /* Message too long */ +EPROTOTYPE: Errno : 41 /* Protocol wrong type for socket */ +ENOPROTOOPT: Errno : 42 /* Protocol option not available */ +EPROTONOSUPPORT: Errno : 43 /* Protocol not supported */ +ESOCKTNOSUPPORT: Errno : 44 /* Socket type not supported */ +EOPNOTSUPP: Errno : 45 /* Operation not supported */ +EPFNOSUPPORT: Errno : 46 /* Protocol family not supported */ +EAFNOSUPPORT: Errno : 47 /* Address family not supported by protocol family */ +EADDRINUSE: Errno : 48 /* Address already in use */ +EADDRNOTAVAIL: Errno : 49 /* Can't assign requested address */ + +/* ipc/network software -- operational errors */ +ENETDOWN: Errno : 50 /* Network is down */ +ENETUNREACH: Errno : 51 /* Network is unreachable */ +ENETRESET: Errno : 52 /* Network dropped connection on reset */ +ECONNABORTED: Errno : 53 /* Software caused connection abort */ +ECONNRESET: Errno : 54 /* Connection reset by peer */ +ENOBUFS: Errno : 55 /* No buffer space available */ +EISCONN: Errno : 56 /* Socket is already connected */ +ENOTCONN: Errno : 57 /* Socket is not connected */ +ESHUTDOWN: Errno : 58 /* Can't send after socket shutdown */ +ETOOMANYREFS: Errno : 59 /* Too many references: can't splice */ +ETIMEDOUT: Errno : 60 /* Operation timed out */ +ECONNREFUSED: Errno : 61 /* Connection refused */ + +ELOOP: Errno : 62 /* Too many levels of symbolic links */ +ENAMETOOLONG: Errno : 63 /* File name too long */ + +/* should be rearranged */ +EHOSTDOWN: Errno : 64 /* Host is down */ +EHOSTUNREACH: Errno : 65 /* No route to host */ +ENOTEMPTY: Errno : 66 /* Directory not empty */ + +/* quotas & mush */ +EPROCLIM: Errno : 67 /* Too many processes */ +EUSERS: Errno : 68 /* Too many users */ +EDQUOT: Errno : 69 /* Disc quota exceeded */ + +/* Network File System */ +ESTALE: Errno : 70 /* Stale NFS file handle */ +EREMOTE: Errno : 71 /* Too many levels of remote in path */ +EBADRPC: Errno : 72 /* RPC struct is bad */ +ERPCMISMATCH: Errno : 73 /* RPC version wrong */ +EPROGUNAVAIL: Errno : 74 /* RPC prog. not avail */ +EPROGMISMATCH: Errno : 75 /* Program version wrong */ +EPROCUNAVAIL: Errno : 76 /* Bad procedure for program */ + +ENOLCK: Errno : 77 /* No locks available */ +ENOSYS: Errno : 78 /* Function not implemented */ + +EFTYPE: Errno : 79 /* Inappropriate file type or format */ +EAUTH: Errno : 80 /* Authentication error */ +ENEEDAUTH: Errno : 81 /* Need authenticator */ + +/* SystemV IPC */ +EIDRM: Errno : 82 /* Identifier removed */ +ENOMSG: Errno : 83 /* No message of desired type */ +EOVERFLOW: Errno : 84 /* Value too large to be stored in data type */ + +/* Wide/multibyte-character handling, ISO/IEC 9899/AMD1:1995 */ +EILSEQ: Errno : 85 /* Illegal byte sequence */ + +/* From IEEE Std 1003.1-2001 */ +/* Base, Realtime, Threads or Thread Priority Scheduling option errors */ +ENOTSUP: Errno : 86 /* Not supported */ + +/* Realtime option errors */ +ECANCELED: Errno : 87 /* Operation canceled */ + +/* Realtime, XSI STREAMS option errors */ +EBADMSG: Errno : 88 /* Bad or Corrupt message */ + +/* XSI STREAMS option errors */ +ENODATA: Errno : 89 /* No message available */ +ENOSR: Errno : 90 /* No STREAM resources */ +ENOSTR: Errno : 91 /* Not a STREAM */ +ETIME: Errno : 92 /* STREAM ioctl timeout */ + +/* File system extended attribute errors */ +ENOATTR: Errno : 93 /* Attribute not found */ + +/* Realtime, XSI STREAMS option errors */ +EMULTIHOP: Errno : 94 /* Multihop attempted */ +ENOLINK: Errno : 95 /* Link has been severed */ +EPROTO: Errno : 96 /* Protocol error */ + +/* Robust mutexes */ +EOWNERDEAD: Errno : 97 /* Previous owner died */ +ENOTRECOVERABLE: Errno : 98 /* State not recoverable */ + +ELAST: Errno : 98 /* Must equal largest errno */ + +/* end of errno */ + +O_RDONLY :: 0x000000000 +O_WRONLY :: 0x000000001 +O_RDWR :: 0x000000002 +O_CREATE :: 0x000000200 +O_EXCL :: 0x000000800 +O_NOCTTY :: 0x000008000 +O_TRUNC :: 0x000000400 +O_NONBLOCK :: 0x000000004 +O_APPEND :: 0x000000008 +O_SYNC :: 0x000000080 +O_ASYNC :: 0x000000040 +O_CLOEXEC :: 0x000400000 + +RTLD_LAZY :: 0x001 +RTLD_NOW :: 0x002 +RTLD_GLOBAL :: 0x100 +RTLD_LOCAL :: 0x200 +RTLD_TRACE :: 0x200 +RTLD_NODELETE :: 0x01000 +RTLD_NOLOAD :: 0x02000 + +MAX_PATH :: 1024 +MAXNAMLEN :: 511 + +args := _alloc_command_line_arguments() + +Unix_File_Time :: struct { + seconds: time_t, + nanoseconds: c.long, +} + +dev_t :: u64 +ino_t :: u64 +nlink_t :: u64 +off_t :: i64 +mode_t :: u16 +pid_t :: u32 +uid_t :: u32 +gid_t :: u32 +blkcnt_t :: i64 +blksize_t :: i32 +fflags_t :: u32 +time_t :: i64 + +OS_Stat :: struct { + device_id: dev_t, + mode: mode_t, + _padding0: i16, + ino: ino_t, + nlink: nlink_t, + uid: uid_t, + gid: gid_t, + _padding1: i32, + rdev: dev_t, + + last_access: Unix_File_Time, + modified: Unix_File_Time, + status_change: Unix_File_Time, + birthtime: Unix_File_Time, + + size: off_t, + blocks: blkcnt_t, + block_size: blksize_t, + + flags: fflags_t, + gen: u32, + lspare: [2]u32, +} + +Dirent :: struct { + ino: ino_t, + reclen: u16, + namlen: u16, + type: u8, + name: [MAXNAMLEN + 1]byte, +} + +Dir :: distinct rawptr // DIR* + +// File type +S_IFMT :: 0o170000 // Type of file mask +S_IFIFO :: 0o010000 // Named pipe (fifo) +S_IFCHR :: 0o020000 // Character special +S_IFDIR :: 0o040000 // Directory +S_IFBLK :: 0o060000 // Block special +S_IFREG :: 0o100000 // Regular +S_IFLNK :: 0o120000 // Symbolic link +S_IFSOCK :: 0o140000 // Socket + +// File mode +// Read, write, execute/search by owner +S_IRWXU :: 0o0700 // RWX mask for owner +S_IRUSR :: 0o0400 // R for owner +S_IWUSR :: 0o0200 // W for owner +S_IXUSR :: 0o0100 // X for owner + +// Read, write, execute/search by group +S_IRWXG :: 0o0070 // RWX mask for group +S_IRGRP :: 0o0040 // R for group +S_IWGRP :: 0o0020 // W for group +S_IXGRP :: 0o0010 // X for group + +// Read, write, execute/search by others +S_IRWXO :: 0o0007 // RWX mask for other +S_IROTH :: 0o0004 // R for other +S_IWOTH :: 0o0002 // W for other +S_IXOTH :: 0o0001 // X for other + +S_ISUID :: 0o4000 // Set user id on execution +S_ISGID :: 0o2000 // Set group id on execution +S_ISVTX :: 0o1000 // Directory restrcted delete + +S_ISLNK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFLNK } +S_ISREG :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFREG } +S_ISDIR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFDIR } +S_ISCHR :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFCHR } +S_ISBLK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFBLK } +S_ISFIFO :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFIFO } +S_ISSOCK :: #force_inline proc(m: mode_t) -> bool { return (m & S_IFMT) == S_IFSOCK } + +F_OK :: 0 // Test for file existance +X_OK :: 1 // Test for execute permission +W_OK :: 2 // Test for write permission +R_OK :: 4 // Test for read permission + +foreign libc { + @(link_name="__errno") __errno_location :: proc() -> ^c.int --- + + @(link_name="open") _unix_open :: proc(path: cstring, flags: c.int, mode: c.int) -> Handle --- + @(link_name="close") _unix_close :: proc(fd: Handle) -> c.int --- + @(link_name="read") _unix_read :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="write") _unix_write :: proc(fd: Handle, buf: rawptr, size: c.size_t) -> c.ssize_t --- + @(link_name="lseek") _unix_seek :: proc(fd: Handle, offset: i64, whence: c.int) -> i64 --- + @(link_name="getpagesize") _unix_getpagesize :: proc() -> c.int --- + @(link_name="stat") _unix_stat :: proc(path: cstring, stat: ^OS_Stat) -> c.int --- + @(link_name="lstat") _unix_lstat :: proc(path: cstring, sb: ^OS_Stat) -> c.int --- + @(link_name="fstat") _unix_fstat :: proc(fd: Handle, stat: ^OS_Stat) -> c.int --- + @(link_name="readlink") _unix_readlink :: proc(path: cstring, buf: ^byte, bufsiz: c.size_t) -> c.ssize_t --- + @(link_name="access") _unix_access :: proc(path: cstring, mask: c.int) -> c.int --- + @(link_name="getcwd") _unix_getcwd :: proc(buf: cstring, len: c.size_t) -> cstring --- + @(link_name="chdir") _unix_chdir :: proc(buf: cstring) -> c.int --- + @(link_name="rename") _unix_rename :: proc(old, new: cstring) -> c.int --- + @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- + @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- + @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + + @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- + @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- + @(link_name="rewinddir") _unix_rewinddir :: proc(dirp: Dir) --- + @(link_name="readdir_r") _unix_readdir_r :: proc(dirp: Dir, entry: ^Dirent, result: ^^Dirent) -> c.int --- + + @(link_name="malloc") _unix_malloc :: proc(size: c.size_t) -> rawptr --- + @(link_name="calloc") _unix_calloc :: proc(num, size: c.size_t) -> rawptr --- + @(link_name="free") _unix_free :: proc(ptr: rawptr) --- + @(link_name="realloc") _unix_realloc :: proc(ptr: rawptr, size: c.size_t) -> rawptr --- + + @(link_name="getenv") _unix_getenv :: proc(cstring) -> cstring --- + @(link_name="realpath") _unix_realpath :: proc(path: cstring, resolved_path: rawptr) -> rawptr --- + @(link_name="sysctlbyname") _sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int --- + + @(link_name="exit") _unix_exit :: proc(status: c.int) -> ! --- +} + +foreign dl { + @(link_name="dlopen") _unix_dlopen :: proc(filename: cstring, flags: c.int) -> rawptr --- + @(link_name="dlsym") _unix_dlsym :: proc(handle: rawptr, symbol: cstring) -> rawptr --- + @(link_name="dlclose") _unix_dlclose :: proc(handle: rawptr) -> c.int --- + @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- +} + +// NOTE(phix): Perhaps share the following functions with FreeBSD if they turn out to be the same in the end. + +is_path_separator :: proc(r: rune) -> bool { + return r == '/' +} + +get_last_error :: proc "contextless" () -> int { + return int(__errno_location()^) +} + +open :: proc(path: string, flags: int = O_RDONLY, mode: int = 0) -> (Handle, Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + handle := _unix_open(cstr, c.int(flags), c.int(mode)) + if handle == -1 { + return INVALID_HANDLE, Errno(get_last_error()) + } + return handle, ERROR_NONE +} + +close :: proc(fd: Handle) -> Errno { + result := _unix_close(fd) + if result == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +// We set a max of 1GB to keep alignment and to be safe. +@(private) +MAX_RW :: 1 << 30 + +read :: proc(fd: Handle, data: []byte) -> (int, Errno) { + to_read := min(c.size_t(len(data)), MAX_RW) + bytes_read := _unix_read(fd, &data[0], to_read) + if bytes_read == -1 { + return -1, Errno(get_last_error()) + } + return int(bytes_read), ERROR_NONE +} + +write :: proc(fd: Handle, data: []byte) -> (int, Errno) { + if len(data) == 0 { + return 0, ERROR_NONE + } + + to_write := min(c.size_t(len(data)), MAX_RW) + bytes_written := _unix_write(fd, &data[0], to_write) + if bytes_written == -1 { + return -1, Errno(get_last_error()) + } + return int(bytes_written), ERROR_NONE +} + +seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { + res := _unix_seek(fd, offset, c.int(whence)) + if res == -1 { + return -1, Errno(get_last_error()) + } + return res, ERROR_NONE +} + +file_size :: proc(fd: Handle) -> (i64, Errno) { + s, err := fstat(fd) + if err != ERROR_NONE { + return -1, err + } + return s.size, ERROR_NONE +} + +rename :: proc(old_path, new_path: string) -> Errno { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + old_path_cstr := strings.clone_to_cstring(old_path, context.temp_allocator) + new_path_cstr := strings.clone_to_cstring(new_path, context.temp_allocator) + res := _unix_rename(old_path_cstr, new_path_cstr) + if res == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +remove :: proc(path: string) -> Errno { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_unlink(path_cstr) + if res == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +make_directory :: proc(path: string, mode: mode_t = 0o775) -> Errno { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_mkdir(path_cstr, mode) + if res == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +remove_directory :: proc(path: string) -> Errno { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_rmdir(path_cstr) + if res == -1 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +is_file_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != ERROR_NONE { + return false + } + return S_ISREG(s.mode) +} + +is_file_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Errno + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != ERROR_NONE { + return false + } + return S_ISREG(s.mode) +} + +is_dir_handle :: proc(fd: Handle) -> bool { + s, err := _fstat(fd) + if err != ERROR_NONE { + return false + } + return S_ISDIR(s.mode) +} + +is_dir_path :: proc(path: string, follow_links: bool = true) -> bool { + s: OS_Stat + err: Errno + if follow_links { + s, err = _stat(path) + } else { + s, err = _lstat(path) + } + if err != ERROR_NONE { + return false + } + return S_ISDIR(s.mode) +} + +is_file :: proc {is_file_path, is_file_handle} +is_dir :: proc {is_dir_path, is_dir_handle} + +// NOTE(bill): Uses startup to initialize it + +stdin: Handle = 0 +stdout: Handle = 1 +stderr: Handle = 2 + +last_write_time :: proc(fd: Handle) -> (File_Time, Errno) { + s, err := _fstat(fd) + if err != ERROR_NONE { + return 0, err + } + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), ERROR_NONE +} + +last_write_time_by_name :: proc(name: string) -> (File_Time, Errno) { + s, err := _stat(name) + if err != ERROR_NONE { + return 0, err + } + modified := s.modified.seconds * 1_000_000_000 + s.modified.nanoseconds + return File_Time(modified), ERROR_NONE +} + +@private +_stat :: proc(path: string) -> (OS_Stat, Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + s: OS_Stat = --- + result := _unix_lstat(cstr, &s) + if result == -1 { + return s, Errno(get_last_error()) + } + return s, ERROR_NONE +} + +@private +_lstat :: proc(path: string) -> (OS_Stat, Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + + // deliberately uninitialized + s: OS_Stat = --- + res := _unix_lstat(cstr, &s) + if res == -1 { + return s, Errno(get_last_error()) + } + return s, ERROR_NONE +} + +@private +_fstat :: proc(fd: Handle) -> (OS_Stat, Errno) { + s: OS_Stat = --- + result := _unix_fstat(fd, &s) + if result == -1 { + return s, Errno(get_last_error()) + } + return s, ERROR_NONE +} + +@private +_fdopendir :: proc(fd: Handle) -> (Dir, Errno) { + dirp := _unix_fdopendir(fd) + if dirp == cast(Dir)nil { + return nil, Errno(get_last_error()) + } + return dirp, ERROR_NONE +} + +@private +_closedir :: proc(dirp: Dir) -> Errno { + rc := _unix_closedir(dirp) + if rc != 0 { + return Errno(get_last_error()) + } + return ERROR_NONE +} + +@private +_rewinddir :: proc(dirp: Dir) { + _unix_rewinddir(dirp) +} + +@private +_readdir :: proc(dirp: Dir) -> (entry: Dirent, err: Errno, end_of_stream: bool) { + result: ^Dirent + rc := _unix_readdir_r(dirp, &entry, &result) + + if rc != 0 { + err = Errno(get_last_error()) + return + } + err = ERROR_NONE + + if result == nil { + end_of_stream = true + return + } + + return +} + +@private +_readlink :: proc(path: string) -> (string, Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + + path_cstr := strings.clone_to_cstring(path, context.temp_allocator) + + bufsz : uint = MAX_PATH + buf := make([]byte, MAX_PATH) + for { + rc := _unix_readlink(path_cstr, &(buf[0]), bufsz) + if rc == -1 { + delete(buf) + return "", Errno(get_last_error()) + } else if rc == int(bufsz) { + bufsz += MAX_PATH + delete(buf) + buf = make([]byte, bufsz) + } else { + return strings.string_from_ptr(&buf[0], rc), ERROR_NONE + } + } + + return "", Errno{} +} + +absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { + return "", Errno(ENOSYS) +} + +absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { + rel := rel + if rel == "" { + rel = "." + } + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == context.allocator) + + rel_cstr := strings.clone_to_cstring(rel, context.temp_allocator) + + path_ptr := _unix_realpath(rel_cstr, nil) + if path_ptr == nil { + return "", Errno(get_last_error()) + } + defer _unix_free(path_ptr) + + path_cstr := transmute(cstring)path_ptr + path = strings.clone( string(path_cstr) ) + + return path, ERROR_NONE +} + +access :: proc(path: string, mask: int) -> (bool, Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + cstr := strings.clone_to_cstring(path, context.temp_allocator) + result := _unix_access(cstr, c.int(mask)) + if result == -1 { + return false, Errno(get_last_error()) + } + return true, ERROR_NONE +} + +lookup_env :: proc(key: string, allocator := context.allocator) -> (value: string, found: bool) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + + path_str := strings.clone_to_cstring(key, context.temp_allocator) + cstr := _unix_getenv(path_str) + if cstr == nil { + return "", false + } + return strings.clone(string(cstr), allocator), true +} + +get_env :: proc(key: string, allocator := context.allocator) -> (value: string) { + value, _ = lookup_env(key, allocator) + return +} + +get_current_directory :: proc() -> string { + // NOTE(tetra): I would use PATH_MAX here, but I was not able to find + // an authoritative value for it across all systems. + // The largest value I could find was 4096, so might as well use the page size. + page_size := get_page_size() + buf := make([dynamic]u8, page_size) + #no_bounds_check for { + cwd := _unix_getcwd(cstring(&buf[0]), c.size_t(len(buf))) + if cwd != nil { + return string(cwd) + } + if Errno(get_last_error()) != ERANGE { + delete(buf) + return "" + } + resize(&buf, len(buf)+page_size) + } + unreachable() +} + +set_current_directory :: proc(path: string) -> (err: Errno) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(path, context.temp_allocator) + res := _unix_chdir(cstr) + if res == -1 do return Errno(get_last_error()) + return ERROR_NONE +} + +exit :: proc "contextless" (code: int) -> ! { + runtime._cleanup_runtime_contextless() + _unix_exit(c.int(code)) +} + +current_thread_id :: proc "contextless" () -> int { + return cast(int) unix.pthread_self() +} + +dlopen :: proc(filename: string, flags: int) -> rawptr { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(filename, context.temp_allocator) + handle := _unix_dlopen(cstr, c.int(flags)) + return handle +} + +dlsym :: proc(handle: rawptr, symbol: string) -> rawptr { + assert(handle != nil) + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + cstr := strings.clone_to_cstring(symbol, context.temp_allocator) + proc_handle := _unix_dlsym(handle, cstr) + return proc_handle +} + +dlclose :: proc(handle: rawptr) -> bool { + assert(handle != nil) + return _unix_dlclose(handle) == 0 +} + +dlerror :: proc() -> string { + return string(_unix_dlerror()) +} + +get_page_size :: proc() -> int { + // NOTE(tetra): The page size never changes, so why do anything complicated + // if we don't have to. + @static page_size := -1 + if page_size != -1 do return page_size + + page_size = int(_unix_getpagesize()) + return page_size +} + +@(private) +_processor_core_count :: proc() -> int { + count : int = 0 + count_size := size_of(count) + if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 { + if count > 0 { + return count + } + } + + return 1 +} + +_alloc_command_line_arguments :: proc() -> []string { + res := make([]string, len(runtime.args__)) + for arg, i in runtime.args__ { + res[i] = string(arg) + } + return res +} diff --git a/core/os/stat_unix.odin b/core/os/stat_unix.odin index 5e83c0e16..3bd62dfc7 100644 --- a/core/os/stat_unix.odin +++ b/core/os/stat_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku package os import "core:time" diff --git a/core/os/stream.odin b/core/os/stream.odin index 25f31218c..4e73284f0 100644 --- a/core/os/stream.odin +++ b/core/os/stream.odin @@ -32,7 +32,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, } case .Read_At: - when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku) { + when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku) { n_int, os_err = read_at(fd, p, offset) n = i64(n_int) if n == 0 && os_err == 0 { @@ -46,7 +46,7 @@ _file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, err = .EOF } case .Write_At: - when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .Haiku) { + when !(ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD || ODIN_OS == .Haiku) { n_int, os_err = write_at(fd, p, offset) n = i64(n_int) if n == 0 && os_err == 0 { diff --git a/core/sync/futex_netbsd.odin b/core/sync/futex_netbsd.odin new file mode 100644 index 000000000..f82af77d6 --- /dev/null +++ b/core/sync/futex_netbsd.odin @@ -0,0 +1,166 @@ +//+private +package sync + +import "core:c" +import "core:time" +import "core:sys/unix" +import "base:runtime" + +@(private="file") +Wait_Node :: struct { + thread: unix.pthread_t, + futex: ^Futex, + prev, next: ^Wait_Node, +} +@(private="file") +atomic_flag :: distinct bool +@(private="file") +Wait_Queue :: struct { + lock: atomic_flag, + list: Wait_Node, +} +@(private="file") +waitq_lock :: proc "contextless" (waitq: ^Wait_Queue) { + for cast(bool)atomic_exchange_explicit(&waitq.lock, atomic_flag(true), .Acquire) { + cpu_relax() // spin... + } +} +@(private="file") +waitq_unlock :: proc "contextless" (waitq: ^Wait_Queue) { + atomic_store_explicit(&waitq.lock, atomic_flag(false), .Release) +} + +// FIXME: This approach may scale badly in the future, +// possible solution - hash map (leads to deadlocks now). +@(private="file") +g_waitq: Wait_Queue + +@(init, private="file") +g_waitq_init :: proc() { + g_waitq = { + list = { + prev = &g_waitq.list, + next = &g_waitq.list, + }, + } +} + +@(private="file") +get_waitq :: #force_inline proc "contextless" (f: ^Futex) -> ^Wait_Queue { + _ = f + return &g_waitq +} + +_futex_wait :: proc "contextless" (f: ^Futex, expect: u32) -> (ok: bool) { + waitq := get_waitq(f) + waitq_lock(waitq) + defer waitq_unlock(waitq) + + head := &waitq.list + waiter := Wait_Node{ + thread = unix.pthread_self(), + futex = f, + prev = head, + next = head.next, + } + + waiter.prev.next = &waiter + waiter.next.prev = &waiter + + old_mask, mask: unix.sigset_t + unix.sigemptyset(&mask) + unix.sigaddset(&mask, unix.SIGCONT) + unix.pthread_sigmask(unix.SIG_BLOCK, &mask, &old_mask) + + if u32(atomic_load_explicit(f, .Acquire)) == expect { + waitq_unlock(waitq) + defer waitq_lock(waitq) + + sig: c.int + unix.sigwait(&mask, &sig) + errno := unix.errno() + ok = errno == unix.ERROR_NONE + } + + waiter.prev.next = waiter.next + waiter.next.prev = waiter.prev + + unix.pthread_sigmask(unix.SIG_SETMASK, &old_mask, nil) + + // FIXME: Add error handling! + return +} + +_futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expect: u32, duration: time.Duration) -> (ok: bool) { + if duration <= 0 { + return false + } + waitq := get_waitq(f) + waitq_lock(waitq) + defer waitq_unlock(waitq) + + head := &waitq.list + waiter := Wait_Node{ + thread = unix.pthread_self(), + futex = f, + prev = head, + next = head.next, + } + + waiter.prev.next = &waiter + waiter.next.prev = &waiter + + old_mask, mask: unix.sigset_t + unix.sigemptyset(&mask) + unix.sigaddset(&mask, unix.SIGCONT) + unix.pthread_sigmask(unix.SIG_BLOCK, &mask, &old_mask) + + if u32(atomic_load_explicit(f, .Acquire)) == expect { + waitq_unlock(waitq) + defer waitq_lock(waitq) + + info: unix.siginfo_t + ts := unix.timespec{ + tv_sec = i64(duration / 1e9), + tv_nsec = i64(duration % 1e9), + } + unix.sigtimedwait(&mask, &info, &ts) + errno := unix.errno() + ok = errno == unix.EAGAIN || errno == unix.ERROR_NONE + } + + waiter.prev.next = waiter.next + waiter.next.prev = waiter.prev + + unix.pthread_sigmask(unix.SIG_SETMASK, &old_mask, nil) + + // FIXME: Add error handling! + return +} + +_futex_signal :: proc "contextless" (f: ^Futex) { + waitq := get_waitq(f) + waitq_lock(waitq) + defer waitq_unlock(waitq) + + head := &waitq.list + for waiter := head.next; waiter != head; waiter = waiter.next { + if waiter.futex == f { + unix.pthread_kill(waiter.thread, unix.SIGCONT) + break + } + } +} + +_futex_broadcast :: proc "contextless" (f: ^Futex) { + waitq := get_waitq(f) + waitq_lock(waitq) + defer waitq_unlock(waitq) + + head := &waitq.list + for waiter := head.next; waiter != head; waiter = waiter.next { + if waiter.futex == f { + unix.pthread_kill(waiter.thread, unix.SIGCONT) + } + } +} diff --git a/core/sync/primitives_netbsd.odin b/core/sync/primitives_netbsd.odin new file mode 100644 index 000000000..2d5b6b7fe --- /dev/null +++ b/core/sync/primitives_netbsd.odin @@ -0,0 +1,9 @@ +//+build netbsd +//+private +package sync + +import "core:sys/unix" + +_current_thread_id :: proc "contextless" () -> int { + return cast(int) unix.pthread_self() +} diff --git a/core/sys/unix/pthread_netbsd.odin b/core/sys/unix/pthread_netbsd.odin new file mode 100644 index 000000000..c334fa56c --- /dev/null +++ b/core/sys/unix/pthread_netbsd.odin @@ -0,0 +1,103 @@ +//+build netbsd +package unix + +import "core:c" + +pthread_t :: distinct u64 + +SEM_T_SIZE :: 8 + +PTHREAD_CONDATTR_T_SIZE :: 16 +PTHREAD_MUTEXATTR_T_SIZE :: 16 +PTHREAD_RWLOCKATTR_T_SIZE :: 16 +PTHREAD_BARRIERATTR_T_SIZE :: 16 + +PTHREAD_COND_T_SIZE :: 40 +PTHREAD_MUTEX_T_SIZE :: 48 +PTHREAD_RWLOCK_T_SIZE :: 64 +PTHREAD_BARRIER_T_SIZE :: 48 +PTHREAD_ATTR_T_SIZE :: 16 + +pthread_cond_t :: struct #align(16) { + _: [PTHREAD_COND_T_SIZE] c.char +} + +pthread_mutex_t :: struct #align(16) { + _: [PTHREAD_MUTEX_T_SIZE] c.char +} + +pthread_rwlock_t :: struct #align(16) { + _: [PTHREAD_RWLOCK_T_SIZE] c.char +} + +pthread_barrier_t :: struct #align(16) { + _: [PTHREAD_BARRIER_T_SIZE] c.char +} + +pthread_attr_t :: struct #align(16) { + _: [PTHREAD_ATTR_T_SIZE] c.char +} + +pthread_condattr_t :: struct #align(16) { + _: [PTHREAD_CONDATTR_T_SIZE] c.char +} + +pthread_mutexattr_t :: struct #align(16) { + _: [PTHREAD_MUTEXATTR_T_SIZE] c.char +} + +pthread_rwlockattr_t :: struct #align(16) { + _: [PTHREAD_RWLOCKATTR_T_SIZE] c.char +} + +pthread_barrierattr_t :: struct #align(16) { + _: [PTHREAD_BARRIERATTR_T_SIZE] c.char +} + +PTHREAD_MUTEX_NORMAL :: 0 +PTHREAD_MUTEX_ERRORCHECK :: 1 +PTHREAD_MUTEX_RECURSIVE :: 2 + +PTHREAD_CREATE_JOINABLE :: 0 +PTHREAD_CREATE_DETACHED :: 1 +PTHREAD_INHERIT_SCHED :: 0 +PTHREAD_EXPLICIT_SCHED :: 1 +PTHREAD_PROCESS_PRIVATE :: 0 +PTHREAD_PROCESS_SHARED :: 1 + +SCHED_NONE :: -1 +SCHED_OTHER :: 0 +SCHED_FIFO :: 1 +SCHED_RR :: 3 + +sched_param :: struct { + sched_priority: c.int, +} + +sem_t :: struct #align(16) { + _: [SEM_T_SIZE] c.char +} + +PTHREAD_CANCEL_ENABLE :: 0 +PTHREAD_CANCEL_DISABLE :: 1 +PTHREAD_CANCEL_DEFERRED :: 0 +PTHREAD_CANCEL_ASYNCHRONOUS :: 1 + +foreign import "system:pthread" + +@(default_calling_convention="c") +foreign pthread { + sem_open :: proc(name: cstring, flags: c.int) -> ^sem_t --- + + sem_init :: proc(sem: ^sem_t, pshared: c.int, initial_value: c.uint) -> c.int --- + sem_destroy :: proc(sem: ^sem_t) -> c.int --- + sem_post :: proc(sem: ^sem_t) -> c.int --- + sem_wait :: proc(sem: ^sem_t) -> c.int --- + sem_trywait :: proc(sem: ^sem_t) -> c.int --- + + pthread_yield :: proc() --- + + pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- + pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- + pthread_cancel :: proc (thread: pthread_t) -> c.int --- +} diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin index 4fe3c8dfa..5760560ee 100644 --- a/core/sys/unix/pthread_unix.odin +++ b/core/sys/unix/pthread_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku package unix foreign import "system:pthread" diff --git a/core/sys/unix/signal_netbsd.odin b/core/sys/unix/signal_netbsd.odin new file mode 100644 index 000000000..6655a6d38 --- /dev/null +++ b/core/sys/unix/signal_netbsd.odin @@ -0,0 +1,31 @@ +package unix + +import "core:c" + +foreign import libc "system:c" + +ERROR_NONE :: 0 +EAGAIN :: 35 + +SIGCONT :: 19 + +SIG_BLOCK :: 1 +SIG_UNBLOCK :: 2 +SIG_SETMASK :: 3 + +siginfo_t :: struct { si_pad: [128]c.char } +sigset_t :: struct { bits: [4]c.uint } + +foreign libc { + sigemptyset :: proc(set: ^sigset_t) -> c.int --- + sigaddset :: proc(set: ^sigset_t, _signal: c.int) -> c.int --- + + sigtimedwait :: proc(set: ^sigset_t, info: ^siginfo_t, timeout: ^timespec) -> c.int --- + sigwait :: proc(set: ^sigset_t, _signal: ^c.int) -> c.int --- + + @(private="file", link_name="__errno") get_error_location :: proc() -> ^c.int --- +} + +errno :: #force_inline proc "contextless" () -> int { + return int(get_error_location()^) +} diff --git a/core/sys/unix/time_unix.odin b/core/sys/unix/time_unix.odin index 088dc378b..6e7386ae3 100644 --- a/core/sys/unix/time_unix.odin +++ b/core/sys/unix/time_unix.odin @@ -1,4 +1,4 @@ -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku package unix when ODIN_OS == .Darwin { @@ -65,7 +65,8 @@ seconds_since_boot :: proc "c" () -> f64 { return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9 } - +// TODO(phix): 'nanosleep' here might refere to the wrong version on NetBSD. Need to investigate this further. +// Normally this is solved by just including 'time.h'. So I need to understand the macro-magic going on in there. inline_nanosleep :: proc "c" (nanoseconds: i64) -> (remaining: timespec, res: i32) { s, ns := nanoseconds / 1e9, nanoseconds % 1e9 requested := timespec{tv_sec=s, tv_nsec=ns} diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index c75710873..3640251dd 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -1,4 +1,4 @@ -// +build linux, darwin, freebsd, openbsd, haiku +// +build linux, darwin, freebsd, openbsd, netbsd, haiku // +private package thread diff --git a/core/time/time_unix.odin b/core/time/time_unix.odin index 1c46b5994..f3e0d6640 100644 --- a/core/time/time_unix.odin +++ b/core/time/time_unix.odin @@ -1,5 +1,5 @@ //+private -//+build linux, darwin, freebsd, openbsd, haiku +//+build linux, darwin, freebsd, openbsd, netbsd, haiku package time import "core:sys/unix" diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 30d6f0b3c..cd57816ab 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -18,6 +18,7 @@ enum TargetOsKind : u16 { TargetOs_essence, TargetOs_freebsd, TargetOs_openbsd, + TargetOs_netbsd, TargetOs_haiku, TargetOs_wasi, @@ -79,6 +80,7 @@ gb_global String target_os_names[TargetOs_COUNT] = { str_lit("essence"), str_lit("freebsd"), str_lit("openbsd"), + str_lit("netbsd"), str_lit("haiku"), str_lit("wasi"), @@ -549,6 +551,14 @@ gb_global TargetMetrics target_openbsd_amd64 = { str_lit("e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"), }; +gb_global TargetMetrics target_netbsd_amd64 = { + TargetOs_netbsd, + TargetArch_amd64, + 8, 8, 8, 16, + str_lit("x86_64-unknown-netbsd-elf"), + str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"), +}; + gb_global TargetMetrics target_haiku_amd64 = { TargetOs_haiku, TargetArch_amd64, @@ -655,6 +665,7 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freebsd_amd64"), &target_freebsd_amd64 }, { str_lit("openbsd_amd64"), &target_openbsd_amd64 }, + { str_lit("netbsd_amd64"), &target_netbsd_amd64 }, { str_lit("haiku_amd64"), &target_haiku_amd64 }, { str_lit("freestanding_wasm32"), &target_freestanding_wasm32 }, @@ -1410,6 +1421,8 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta metrics = &target_freebsd_amd64; #elif defined(GB_SYSTEM_OPENBSD) metrics = &target_openbsd_amd64; + #elif defined(GB_SYSTEM_NETBSD) + metrics = &target_netbsd_amd64; #elif defined(GB_SYSTEM_HAIKU) metrics = &target_haiku_amd64; #elif defined(GB_CPU_ARM) @@ -1523,6 +1536,9 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta case TargetOs_openbsd: bc->link_flags = str_lit("-arch x86-64 "); break; + case TargetOs_netbsd: + bc->link_flags = str_lit("-arch x86-64 "); + break; case TargetOs_haiku: bc->link_flags = str_lit("-arch x86-64 "); break; @@ -2002,6 +2018,7 @@ gb_internal bool init_build_paths(String init_filename) { case TargetOs_essence: case TargetOs_freebsd: case TargetOs_openbsd: + case TargetOs_netbsd: case TargetOs_haiku: gb_printf_err("-no-crt on unix systems requires either -default-to-nil-allocator or -default-to-panic-allocator to also be present because the default allocator requires crt\n"); return false; diff --git a/src/checker.cpp b/src/checker.cpp index 244e7efbd..77d894624 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1010,6 +1010,7 @@ gb_internal void init_universal(void) { {"FreeBSD", TargetOs_freebsd}, {"Haiku", TargetOs_haiku}, {"OpenBSD", TargetOs_openbsd}, + {"NetBSD", TargetOs_netbsd}, {"WASI", TargetOs_wasi}, {"JS", TargetOs_js}, {"Freestanding", TargetOs_freestanding}, diff --git a/src/gb/gb.h b/src/gb/gb.h index 868e11a16..4e05a3a0a 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -83,6 +83,10 @@ extern "C" { #ifndef GB_SYSTEM_OPENBSD #define GB_SYSTEM_OPENBSD 1 #endif + #elif defined(__NetBSD__) + #ifndef GB_SYSTEM_NETBSD + #define GB_SYSTEM_NETBSD 1 + #endif #elif defined(__HAIKU__) || defined(__haiku__) #ifndef GB_SYSTEM_HAIKU #define GB_SYSTEM_HAIKU 1 @@ -208,7 +212,7 @@ extern "C" { #endif #include // NOTE(bill): malloc on linux #include - #if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__HAIKU__) + #if !defined(GB_SYSTEM_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) && !defined(__HAIKU__) #include #endif #include @@ -250,6 +254,11 @@ extern "C" { #define lseek64 lseek #endif +#if defined(GB_SYSTEM_NETBSD) + #include + #define lseek64 lseek +#endif + #if defined(GB_SYSTEM_HAIKU) #include #include @@ -817,6 +826,13 @@ typedef struct gbAffinity { isize thread_count; isize threads_per_core; } gbAffinity; +#elif defined(GB_SYSTEM_NETBSD) +typedef struct gbAffinity { + b32 is_accurate; + isize core_count; + isize thread_count; + isize threads_per_core; +} gbAffinity; #elif defined(GB_SYSTEM_HAIKU) typedef struct gbAffinity { b32 is_accurate; @@ -3267,6 +3283,31 @@ isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { GB_ASSERT(0 <= core && core < a->core_count); return a->threads_per_core; } + +#elif defined(GB_SYSTEM_NETBSD) +#include + +void gb_affinity_init(gbAffinity *a) { + a->core_count = sysconf(_SC_NPROCESSORS_ONLN); + a->threads_per_core = 1; + a->is_accurate = a->core_count > 0; + a->core_count = a->is_accurate ? a->core_count : 1; + a->thread_count = a->core_count; +} + +void gb_affinity_destroy(gbAffinity *a) { + gb_unused(a); +} + +b32 gb_affinity_set(gbAffinity *a, isize core, isize thread_index) { + return true; +} + +isize gb_affinity_thread_count_for_core(gbAffinity *a, isize core) { + GB_ASSERT(0 <= core && core < a->core_count); + return a->threads_per_core; +} + #elif defined(GB_SYSTEM_HAIKU) #include diff --git a/src/path.cpp b/src/path.cpp index b07f20870..2f016a578 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -341,7 +341,7 @@ gb_internal ReadDirectoryError read_directory(String path, Array *fi) return ReadDirectory_None; } -#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_HAIKU) +#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_NETBSD) || defined(GB_SYSTEM_HAIKU) #include diff --git a/src/threading.cpp b/src/threading.cpp index fbe8997d1..77aa8edf7 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -627,8 +627,12 @@ gb_internal void thread_set_name(Thread *t, char const *name) { #elif defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) pthread_set_name_np(t->posix_handle, name); #else - // TODO(bill): Test if this works - pthread_setname_np(t->posix_handle, name); + #ifdef GB_SYSTEM_NETBSD + // TODO(phix): Could be that libs are to old on NetBSD? Just ignore for now. + #else + // TODO(bill): Test if this works + pthread_setname_np(t->posix_handle, name); + #endif #endif } @@ -901,10 +905,11 @@ gb_internal void futex_wait(Futex *f, Footex val) { } while (f->load() == val); } -#elif defined(GB_SYSTEM_HAIKU) +#elif defined(GB_SYSTEM_HAIKU) || defined(GB_SYSTEM_NETBSD) // Futex implementation taken from https://tavianator.com/2023/futex.html +#include #include #include -- cgit v1.2.3 From 80067a959b51d4bda4aaf96677ca1d2e4831323d Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Wed, 17 Apr 2024 09:42:41 +0200 Subject: Added thread name Call pthread_setname_np with the correct number of arguments on NetBSD. --- src/threading.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'src/threading.cpp') diff --git a/src/threading.cpp b/src/threading.cpp index 77aa8edf7..79ed8e8a4 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -626,13 +626,11 @@ gb_internal void thread_set_name(Thread *t, char const *name) { pthread_setname_np(name); #elif defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) pthread_set_name_np(t->posix_handle, name); +#elif defined(GB_SYSTEM_NETBSD) + pthread_setname_np(t->posix_handle, "%s", (void*)name); #else - #ifdef GB_SYSTEM_NETBSD - // TODO(phix): Could be that libs are to old on NetBSD? Just ignore for now. - #else - // TODO(bill): Test if this works - pthread_setname_np(t->posix_handle, name); - #endif + // TODO(bill): Test if this works + pthread_setname_np(t->posix_handle, name); #endif } -- cgit v1.2.3 From 3000508c027c9d30c168266d0ae276cc14de3982 Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Thu, 25 Apr 2024 21:50:34 +0200 Subject: Switched to native futex on NetBSD --- core/sync/futex_netbsd.odin | 183 ++++++++++--------------------------- core/sys/unix/signal_netbsd.odin | 30 ------ core/sys/unix/syscalls_netbsd.odin | 3 + src/threading.cpp | 13 ++- 4 files changed, 58 insertions(+), 171 deletions(-) delete mode 100644 core/sys/unix/signal_netbsd.odin create mode 100644 core/sys/unix/syscalls_netbsd.odin (limited to 'src/threading.cpp') diff --git a/core/sync/futex_netbsd.odin b/core/sync/futex_netbsd.odin index 4f52e952f..06ff00a98 100644 --- a/core/sync/futex_netbsd.odin +++ b/core/sync/futex_netbsd.odin @@ -1,165 +1,74 @@ //+private package sync -import "core:c" +import "base:intrinsics" import "core:time" +import "core:c" import "core:sys/unix" -@(private="file") -Wait_Node :: struct { - thread: unix.pthread_t, - futex: ^Futex, - prev, next: ^Wait_Node, -} -@(private="file") -atomic_flag :: distinct bool -@(private="file") -Wait_Queue :: struct { - lock: atomic_flag, - list: Wait_Node, -} -@(private="file") -waitq_lock :: proc "contextless" (waitq: ^Wait_Queue) { - for cast(bool)atomic_exchange_explicit(&waitq.lock, atomic_flag(true), .Acquire) { - cpu_relax() // spin... - } -} -@(private="file") -waitq_unlock :: proc "contextless" (waitq: ^Wait_Queue) { - atomic_store_explicit(&waitq.lock, atomic_flag(false), .Release) -} +foreign import libc "system:c" -// FIXME: This approach may scale badly in the future, -// possible solution - hash map (leads to deadlocks now). -@(private="file") -g_waitq: Wait_Queue +FUTEX_PRIVATE_FLAG :: 128 -@(init, private="file") -g_waitq_init :: proc() { - g_waitq = { - list = { - prev = &g_waitq.list, - next = &g_waitq.list, - }, - } -} +FUTEX_WAIT_PRIVATE :: 0 | FUTEX_PRIVATE_FLAG +FUTEX_WAKE_PRIVATE :: 1 | FUTEX_PRIVATE_FLAG -@(private="file") -get_waitq :: #force_inline proc "contextless" (f: ^Futex) -> ^Wait_Queue { - _ = f - return &g_waitq -} +EINTR :: 4 /* Interrupted system call */ +EAGAIN :: 35 /* Resource temporarily unavailable */ +ETIMEDOUT :: 60 /* Operation timed out */ -_futex_wait :: proc "contextless" (f: ^Futex, expect: u32) -> (ok: bool) { - waitq := get_waitq(f) - waitq_lock(waitq) - defer waitq_unlock(waitq) +Time_Spec :: struct { + time_sec: uint, + time_nsec: uint, +} - head := &waitq.list - waiter := Wait_Node{ - thread = unix.pthread_self(), - futex = f, - prev = head, - next = head.next, +get_last_error :: proc "contextless" () -> int { + foreign libc { + __errno :: proc() -> ^c.int --- } + return int(__errno()^) +} - waiter.prev.next = &waiter - waiter.next.prev = &waiter - - old_mask, mask: unix.sigset_t - unix.sigemptyset(&mask) - unix.sigaddset(&mask, unix.SIGCONT) - unix.pthread_sigmask(unix.SIG_BLOCK, &mask, &old_mask) - - if u32(atomic_load_explicit(f, .Acquire)) == expect { - waitq_unlock(waitq) - defer waitq_lock(waitq) - - sig: c.int - unix.sigwait(&mask, &sig) - errno := unix.errno() - ok = errno == unix.ERROR_NONE +_futex_wait :: proc "contextless" (futex: ^Futex, expected: u32) -> bool { + if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), 0) == -1 { + switch get_last_error() { + case EINTR, EAGAIN: + return true + case: + _panic("futex_wait failure") + } } - - waiter.prev.next = waiter.next - waiter.next.prev = waiter.prev - - unix.pthread_sigmask(unix.SIG_SETMASK, &old_mask, nil) - - // FIXME: Add error handling! - return + return true } -_futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expect: u32, duration: time.Duration) -> (ok: bool) { +_futex_wait_with_timeout :: proc "contextless" (futex: ^Futex, expected: u32, duration: time.Duration) -> bool { if duration <= 0 { return false } - waitq := get_waitq(f) - waitq_lock(waitq) - defer waitq_unlock(waitq) - - head := &waitq.list - waiter := Wait_Node{ - thread = unix.pthread_self(), - futex = f, - prev = head, - next = head.next, - } - - waiter.prev.next = &waiter - waiter.next.prev = &waiter - - old_mask, mask: unix.sigset_t - unix.sigemptyset(&mask) - unix.sigaddset(&mask, unix.SIGCONT) - unix.pthread_sigmask(unix.SIG_BLOCK, &mask, &old_mask) - - if u32(atomic_load_explicit(f, .Acquire)) == expect { - waitq_unlock(waitq) - defer waitq_lock(waitq) - - info: unix.siginfo_t - ts := unix.timespec{ - tv_sec = i64(duration / 1e9), - tv_nsec = i64(duration % 1e9), + if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAIT_PRIVATE, uintptr(expected), cast(uintptr) &Time_Spec{ + time_sec = cast(uint)(duration / 1e9), + time_nsec = cast(uint)(duration % 1e9), + }) == -1 { + switch get_last_error() { + case EINTR, EAGAIN: + return true + case ETIMEDOUT: + return false + case: + _panic("futex_wait_with_timeout failure") } - unix.sigtimedwait(&mask, &info, &ts) - errno := unix.errno() - ok = errno == unix.EAGAIN || errno == unix.ERROR_NONE } - - waiter.prev.next = waiter.next - waiter.next.prev = waiter.prev - - unix.pthread_sigmask(unix.SIG_SETMASK, &old_mask, nil) - - // FIXME: Add error handling! - return + return true } -_futex_signal :: proc "contextless" (f: ^Futex) { - waitq := get_waitq(f) - waitq_lock(waitq) - defer waitq_unlock(waitq) - - head := &waitq.list - for waiter := head.next; waiter != head; waiter = waiter.next { - if waiter.futex == f { - unix.pthread_kill(waiter.thread, unix.SIGCONT) - break - } +_futex_signal :: proc "contextless" (futex: ^Futex) { + if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, 1) == -1 { + _panic("futex_wake_single failure") } } -_futex_broadcast :: proc "contextless" (f: ^Futex) { - waitq := get_waitq(f) - waitq_lock(waitq) - defer waitq_unlock(waitq) - - head := &waitq.list - for waiter := head.next; waiter != head; waiter = waiter.next { - if waiter.futex == f { - unix.pthread_kill(waiter.thread, unix.SIGCONT) - } +_futex_broadcast :: proc "contextless" (futex: ^Futex) { + if cast(int) intrinsics.syscall(unix.SYS___futex, uintptr(futex), FUTEX_WAKE_PRIVATE, uintptr(max(i32))) == -1 { + _panic("_futex_wake_all failure") } } diff --git a/core/sys/unix/signal_netbsd.odin b/core/sys/unix/signal_netbsd.odin deleted file mode 100644 index c32f0bfbe..000000000 --- a/core/sys/unix/signal_netbsd.odin +++ /dev/null @@ -1,30 +0,0 @@ -package unix - -import "core:c" - -foreign import libc "system:c" - -ERROR_NONE :: 0 -EAGAIN :: 35 - -SIGCONT :: 19 - -SIG_BLOCK :: 1 -SIG_UNBLOCK :: 2 -SIG_SETMASK :: 3 - -siginfo_t :: struct { _: [128]u8 } -sigset_t :: struct { _: [4]u32 } - -foreign libc { - @(link_name="__sigemptyset14") sigemptyset :: proc(set: ^sigset_t) -> c.int --- - @(link_name="__sigaddset14") sigaddset :: proc(set: ^sigset_t, _signal: c.int) -> c.int --- - @(link_name="__sigtimedwait50") sigtimedwait :: proc(set: ^sigset_t, info: ^siginfo_t, timeout: ^timespec) -> c.int --- - @(link_name="sigwait") sigwait :: proc(set: ^sigset_t, _signal: ^c.int) -> c.int --- - - @(private="file", link_name="__errno") get_error_location :: proc() -> ^c.int --- -} - -errno :: #force_inline proc "contextless" () -> int { - return int(get_error_location()^) -} diff --git a/core/sys/unix/syscalls_netbsd.odin b/core/sys/unix/syscalls_netbsd.odin new file mode 100644 index 000000000..e92dc6d92 --- /dev/null +++ b/core/sys/unix/syscalls_netbsd.odin @@ -0,0 +1,3 @@ +package unix + +SYS___futex : uintptr : 166 diff --git a/src/threading.cpp b/src/threading.cpp index 79ed8e8a4..b2cfa6d8e 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -634,9 +634,15 @@ gb_internal void thread_set_name(Thread *t, char const *name) { #endif } -#if defined(GB_SYSTEM_LINUX) -#include +#if defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_NETBSD) + #include +#ifdef GB_SYSTEM_LINUX + #include +#else + #include + #define SYS_futex SYS___futex +#endif gb_internal void futex_signal(Futex *addr) { int ret = syscall(SYS_futex, addr, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1, NULL, NULL, 0); @@ -903,11 +909,10 @@ gb_internal void futex_wait(Futex *f, Footex val) { } while (f->load() == val); } -#elif defined(GB_SYSTEM_HAIKU) || defined(GB_SYSTEM_NETBSD) +#elif defined(GB_SYSTEM_HAIKU) // Futex implementation taken from https://tavianator.com/2023/futex.html -#include #include #include -- cgit v1.2.3 From 1165d65c94db7210d1fdb34fdaa44b68ca80c4c6 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 5 May 2024 07:26:45 -0400 Subject: Minimally support compiling Odin on FreeBSD arm64 This is enough to get Odin itself compiling and the demo running. --- core/os/os_freebsd.odin | 2 +- src/build_settings.cpp | 15 ++++++++++++++- src/gb/gb.h | 2 ++ src/threading.cpp | 2 ++ 4 files changed, 19 insertions(+), 2 deletions(-) (limited to 'src/threading.cpp') diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index be86854dd..cdd44d301 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -159,7 +159,7 @@ blkcnt_t :: i64 blksize_t :: i32 fflags_t :: u32 -when ODIN_ARCH == .amd64 /* LP64 */ { +when ODIN_ARCH == .amd64 || ODIN_ARCH == .arm64 /* LP64 */ { time_t :: i64 } else { time_t :: i32 diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 8509394ff..42a9a4258 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -549,6 +549,14 @@ gb_global TargetMetrics target_freebsd_amd64 = { str_lit("e-m:w-i64:64-f80:128-n8:16:32:64-S128"), }; +gb_global TargetMetrics target_freebsd_arm64 = { + TargetOs_freebsd, + TargetArch_arm64, + 8, 8, 16, 16, + str_lit("aarch64-unknown-freebsd-elf"), + str_lit("e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"), +}; + gb_global TargetMetrics target_openbsd_amd64 = { TargetOs_openbsd, TargetArch_amd64, @@ -670,6 +678,7 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freebsd_i386"), &target_freebsd_i386 }, { str_lit("freebsd_amd64"), &target_freebsd_amd64 }, + { str_lit("freebsd_arm64"), &target_freebsd_arm64 }, { str_lit("openbsd_amd64"), &target_openbsd_amd64 }, { str_lit("haiku_amd64"), &target_haiku_amd64 }, @@ -1424,7 +1433,11 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta metrics = &target_darwin_amd64; #endif #elif defined(GB_SYSTEM_FREEBSD) - metrics = &target_freebsd_amd64; + #if defined(GB_CPU_ARM) + metrics = &target_freebsd_arm64; + #else + metrics = &target_freebsd_amd64; + #endif #elif defined(GB_SYSTEM_OPENBSD) metrics = &target_openbsd_amd64; #elif defined(GB_SYSTEM_HAIKU) diff --git a/src/gb/gb.h b/src/gb/gb.h index 868e11a16..c55ff8a9a 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -3009,6 +3009,8 @@ gb_inline u32 gb_thread_current_id(void) { thread_id = gettid(); #elif defined(GB_SYSTEM_HAIKU) thread_id = find_thread(NULL); +#elif defined(GB_SYSTEM_FREEBSD) + thread_id = pthread_getthreadid_np(); #else #error Unsupported architecture for gb_thread_current_id() #endif diff --git a/src/threading.cpp b/src/threading.cpp index fbe8997d1..f2e0789f8 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -492,6 +492,8 @@ gb_internal u32 thread_current_id(void) { thread_id = gettid(); #elif defined(GB_SYSTEM_HAIKU) thread_id = find_thread(NULL); +#elif defined(GB_SYSTEM_FREEBSD) + thread_id = pthread_getthreadid_np(); #else #error Unsupported architecture for thread_current_id() #endif -- cgit v1.2.3 From 58f07698e849ca7542c53f9687b6c3d4a7f478e9 Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Wed, 5 Jun 2024 10:18:47 +0200 Subject: Added arm64 support for NetBSD --- .github/workflows/ci.yml | 3 ++- src/build_settings.cpp | 17 +++++++++++++++-- src/gb/gb.h | 3 +++ src/threading.cpp | 2 ++ 4 files changed, 22 insertions(+), 3 deletions(-) (limited to 'src/threading.cpp') diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aefc8add5..33bc303f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build, Check, and Test - timeout-minutes: 25 + timeout-minutes: 15 uses: vmactions/netbsd-vm@v1 with: release: "10.0" @@ -31,6 +31,7 @@ jobs: ./odin version ./odin report ./odin check examples/all -vet -strict-style -target:netbsd_amd64 + ./odin check examples/all -vet -strict-style -target:netbsd_arm64 (cd tests/core; gmake all_bsd) (cd tests/internal; gmake all_bsd) (cd tests/issues; ./run.sh) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 3c7ff3f1e..a7e455818 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1039,6 +1039,13 @@ gb_global TargetMetrics target_netbsd_amd64 = { str_lit("x86_64-unknown-netbsd-elf"), }; +gb_global TargetMetrics target_netbsd_arm64 = { + TargetOs_netbsd, + TargetArch_arm64, + 8, 8, 16, 16, + str_lit("aarch64-unknown-netbsd-elf"), +}; + gb_global TargetMetrics target_haiku_amd64 = { TargetOs_haiku, TargetArch_amd64, @@ -1154,8 +1161,10 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freebsd_amd64"), &target_freebsd_amd64 }, { str_lit("freebsd_arm64"), &target_freebsd_arm64 }, - { str_lit("openbsd_amd64"), &target_openbsd_amd64 }, { str_lit("netbsd_amd64"), &target_netbsd_amd64 }, + { str_lit("netbsd_arm64"), &target_netbsd_arm64 }, + + { str_lit("openbsd_amd64"), &target_openbsd_amd64 }, { str_lit("haiku_amd64"), &target_haiku_amd64 }, { str_lit("freestanding_wasm32"), &target_freestanding_wasm32 }, @@ -1916,7 +1925,11 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta #elif defined(GB_SYSTEM_OPENBSD) metrics = &target_openbsd_amd64; #elif defined(GB_SYSTEM_NETBSD) - metrics = &target_netbsd_amd64; + #if defined(GB_CPU_ARM) + metrics = &target_netbsd_arm64; + #else + metrics = &target_netbsd_amd64; + #endif #elif defined(GB_SYSTEM_HAIKU) metrics = &target_haiku_amd64; #elif defined(GB_CPU_ARM) diff --git a/src/gb/gb.h b/src/gb/gb.h index 17d5e97d1..22a30a04b 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -256,6 +256,7 @@ extern "C" { #if defined(GB_SYSTEM_NETBSD) #include + #include #define lseek64 lseek #endif @@ -3027,6 +3028,8 @@ gb_inline u32 gb_thread_current_id(void) { thread_id = find_thread(NULL); #elif defined(GB_SYSTEM_FREEBSD) thread_id = pthread_getthreadid_np(); +#elif defined(GB_SYSTEM_NETBSD) + thread_id = (u32)_lwp_self(); #else #error Unsupported architecture for gb_thread_current_id() #endif diff --git a/src/threading.cpp b/src/threading.cpp index 48c58e8f4..717dcb874 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -494,6 +494,8 @@ gb_internal u32 thread_current_id(void) { thread_id = find_thread(NULL); #elif defined(GB_SYSTEM_FREEBSD) thread_id = pthread_getthreadid_np(); +#elif defined(GB_SYSTEM_NETBSD) + thread_id = (u32)_lwp_self(); #else #error Unsupported architecture for thread_current_id() #endif -- cgit v1.2.3 From cdede4928cbbe38e043f3a784020b2ed40c5470a Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Sat, 13 Jul 2024 23:16:22 -0700 Subject: move to a growing queue --- src/thread_pool.cpp | 112 ++++++++++++++++++++++++++++++++++------------------ src/threading.cpp | 39 +++++++++++++----- 2 files changed, 102 insertions(+), 49 deletions(-) (limited to 'src/threading.cpp') diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp index 5dbbe37c4..2b176db1c 100644 --- a/src/thread_pool.cpp +++ b/src/thread_pool.cpp @@ -16,7 +16,6 @@ struct ThreadPool { std::atomic running; Futex tasks_available; - Futex tasks_left; }; @@ -46,7 +45,7 @@ gb_internal void thread_pool_destroy(ThreadPool *pool) { for_array_off(i, 1, pool->threads) { Thread *t = &pool->threads[i]; - pool->tasks_available.fetch_add(1, std::memory_order_relaxed); + pool->tasks_available.fetch_add(1, std::memory_order_acquire); futex_broadcast(&pool->tasks_available); thread_join_and_destroy(t); } @@ -54,51 +53,86 @@ gb_internal void thread_pool_destroy(ThreadPool *pool) { gb_free(pool->threads_allocator, pool->threads.data); } -void thread_pool_queue_push(Thread *thread, WorkerTask task) { - u64 capture; - u64 new_capture; - do { - capture = thread->head_and_tail.load(); - - u64 mask = thread->capacity - 1; - u64 head = (capture >> 32) & mask; - u64 tail = ((u32)capture) & mask; +TaskRingBuffer *taskring_grow(TaskRingBuffer *ring, ssize_t bottom, ssize_t top) { + TaskRingBuffer *new_ring = taskring_init(ring->size * 2); + for (ssize_t i = top; i < bottom; i++) { + new_ring->buffer[i % new_ring->size] = ring->buffer[i % ring->size]; + } + return new_ring; +} - u64 new_head = (head + 1) & mask; - GB_ASSERT_MSG(new_head != tail, "Thread Queue Full!"); +void thread_pool_queue_push(Thread *thread, WorkerTask task) { + ssize_t bot = thread->queue.bottom.load(std::memory_order_relaxed); + ssize_t top = thread->queue.top.load(std::memory_order_acquire); + TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_relaxed); + + ssize_t size = bot - top; + if (size > (cur_ring->size - 1)) { + // Queue is full + thread->queue.ring = taskring_grow(thread->queue.ring, bot, top); + cur_ring = thread->queue.ring.load(std::memory_order_relaxed); + } - // This *must* be done in here, to avoid a potential race condition where we no longer own the slot by the time we're assigning - thread->queue[head] = task; - new_capture = (new_head << 32) | tail; - } while (!thread->head_and_tail.compare_exchange_weak(capture, new_capture)); + cur_ring->buffer[bot % cur_ring->size] = task; + std::atomic_thread_fence(std::memory_order_release); + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); thread->pool->tasks_left.fetch_add(1, std::memory_order_release); thread->pool->tasks_available.fetch_add(1, std::memory_order_relaxed); futex_broadcast(&thread->pool->tasks_available); } -bool thread_pool_queue_pop(Thread *thread, WorkerTask *task) { - u64 capture; - u64 new_capture; - do { - capture = thread->head_and_tail.load(std::memory_order_acquire); - - u64 mask = thread->capacity - 1; - u64 head = (capture >> 32) & mask; - u64 tail = ((u32)capture) & mask; +bool thread_pool_queue_take(Thread *thread, WorkerTask *task) { + ssize_t bot = thread->queue.bottom.load(std::memory_order_relaxed) - 1; + TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_relaxed); + thread->queue.bottom.store(bot, std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_seq_cst); + + ssize_t top = thread->queue.top.load(std::memory_order_relaxed); + if (top <= bot) { + + // Queue is not empty + *task = cur_ring->buffer[bot % cur_ring->size]; + if (top == bot) { + // Only one entry left in queue + if (!thread->queue.top.compare_exchange_strong(top, top + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { + // Race failed + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); + return false; + } - u64 new_tail = (tail + 1) & mask; - if (tail == head) { - return false; + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); + return true; } - // Making a copy of the task before we increment the tail, avoiding the same potential race condition as above - *task = thread->queue[tail]; - - new_capture = (head << 32) | new_tail; - } while (!thread->head_and_tail.compare_exchange_weak(capture, new_capture, std::memory_order_release)); + // We got a task without hitting a race + return true; + } else { + // Queue is empty + thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); + return false; + } +} - return true; +bool thread_pool_queue_steal(Thread *thread, WorkerTask *task) { + ssize_t top = thread->queue.top.load(std::memory_order_acquire); + std::atomic_thread_fence(std::memory_order_seq_cst); + ssize_t bot = thread->queue.bottom.load(std::memory_order_acquire); + + bool ret = false; + if (top < bot) { + // Queue is not empty + TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_consume); + *task = cur_ring->buffer[top % cur_ring->size]; + + if (!thread->queue.top.compare_exchange_strong(top, top + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { + // Race failed + ret = false; + } else { + ret = true; + } + } + return ret; } gb_internal bool thread_pool_add_task(ThreadPool *pool, WorkerTaskProc *proc, void *data) { @@ -115,12 +149,11 @@ gb_internal void thread_pool_wait(ThreadPool *pool) { while (pool->tasks_left.load(std::memory_order_acquire)) { // if we've got tasks on our queue, run them - while (thread_pool_queue_pop(current_thread, &task)) { + while (thread_pool_queue_take(current_thread, &task)) { task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); } - // is this mem-barriered enough? // This *must* be executed in this order, so the futex wakes immediately // if rem_tasks has changed since we checked last, otherwise the program @@ -145,7 +178,7 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { usize finished_tasks = 0; i32 state; - while (thread_pool_queue_pop(current_thread, &task)) { + while (thread_pool_queue_take(current_thread, &task)) { task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); @@ -167,7 +200,7 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { Thread *thread = &pool->threads.data[idx]; WorkerTask task; - if (thread_pool_queue_pop(thread, &task)) { + if (thread_pool_queue_steal(thread, &task)) { task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); @@ -182,6 +215,7 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { // if we've done all our work, and there's nothing to steal, go to sleep state = pool->tasks_available.load(std::memory_order_acquire); + if (!pool->running) { break; } futex_wait(&pool->tasks_available, state); main_loop_continue:; diff --git a/src/threading.cpp b/src/threading.cpp index 717dcb874..dda98631b 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -46,6 +46,18 @@ typedef struct WorkerTask { void *data; } WorkerTask; +typedef struct TaskRingBuffer { + std::atomic size; + std::atomic buffer; +} TaskRingBuffer; + +typedef struct TaskQueue { + std::atomic top; + std::atomic bottom; + + std::atomic ring; +} TaskQueue; + struct Thread { #if defined(GB_SYSTEM_WINDOWS) void *win32_handle; @@ -54,12 +66,9 @@ struct Thread { #endif isize idx; - - WorkerTask *queue; - size_t capacity; - std::atomic head_and_tail; - isize stack_size; + + struct TaskQueue queue; struct ThreadPool *pool; }; @@ -551,6 +560,18 @@ gb_internal void *internal_thread_proc(void *arg) { } #endif +TaskRingBuffer *taskring_init(ssize_t size) { + TaskRingBuffer *ring = (TaskRingBuffer *)gb_alloc(heap_allocator(), sizeof(TaskRingBuffer)); + ring->size = size; + ring->buffer = (WorkerTask *)gb_alloc_array(heap_allocator(), WorkerTask, ring->size); + return ring; +} + +void thread_queue_destroy(TaskQueue *q) { + gb_free(heap_allocator(), (*q->ring).buffer); + gb_free(heap_allocator(), q->ring); +} + gb_internal void thread_init(ThreadPool *pool, Thread *t, isize idx) { gb_zero_item(t); #if defined(GB_SYSTEM_WINDOWS) @@ -559,14 +580,12 @@ gb_internal void thread_init(ThreadPool *pool, Thread *t, isize idx) { t->posix_handle = 0; #endif - t->capacity = 1 << 14; // must be a power of 2 - t->queue = gb_alloc_array(heap_allocator(), WorkerTask, t->capacity); - t->head_and_tail = 0; + // Size must be a power of 2 + t->queue.ring = taskring_init(1 << 14); t->pool = pool; t->idx = idx; } - gb_internal void thread_init_and_start(ThreadPool *pool, Thread *t, isize idx) { thread_init(pool, t, idx); isize stack_size = 0; @@ -598,7 +617,7 @@ gb_internal void thread_join_and_destroy(Thread *t) { t->posix_handle = 0; #endif - gb_free(heap_allocator(), t->queue); + thread_queue_destroy(&t->queue); } gb_internal void thread_set_name(Thread *t, char const *name) { -- cgit v1.2.3 From 64feb7599e8ec01c2ec7c8d709df1cc70651c06b Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Sun, 14 Jul 2024 00:33:40 -0700 Subject: move to isize --- src/thread_pool.cpp | 18 +++++++++--------- src/threading.cpp | 8 ++++---- 2 files changed, 13 insertions(+), 13 deletions(-) (limited to 'src/threading.cpp') diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp index da7e724a8..bf953ddd0 100644 --- a/src/thread_pool.cpp +++ b/src/thread_pool.cpp @@ -59,20 +59,20 @@ gb_internal void thread_pool_destroy(ThreadPool *pool) { gb_free(pool->threads_allocator, pool->threads.data); } -TaskRingBuffer *taskring_grow(TaskRingBuffer *ring, ssize_t bottom, ssize_t top) { +TaskRingBuffer *taskring_grow(TaskRingBuffer *ring, isize bottom, isize top) { TaskRingBuffer *new_ring = taskring_init(ring->size * 2); - for (ssize_t i = top; i < bottom; i++) { + for (isize i = top; i < bottom; i++) { new_ring->buffer[i % new_ring->size] = ring->buffer[i % ring->size]; } return new_ring; } void thread_pool_queue_push(Thread *thread, WorkerTask task) { - ssize_t bot = thread->queue.bottom.load(std::memory_order_relaxed); - ssize_t top = thread->queue.top.load(std::memory_order_acquire); + isize bot = thread->queue.bottom.load(std::memory_order_relaxed); + isize top = thread->queue.top.load(std::memory_order_acquire); TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_relaxed); - ssize_t size = bot - top; + isize size = bot - top; if (size > (cur_ring->size - 1)) { // Queue is full thread->queue.ring = taskring_grow(thread->queue.ring, bot, top); @@ -89,12 +89,12 @@ void thread_pool_queue_push(Thread *thread, WorkerTask task) { } GrabState thread_pool_queue_take(Thread *thread, WorkerTask *task) { - ssize_t bot = thread->queue.bottom.load(std::memory_order_relaxed) - 1; + isize bot = thread->queue.bottom.load(std::memory_order_relaxed) - 1; TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_relaxed); thread->queue.bottom.store(bot, std::memory_order_relaxed); std::atomic_thread_fence(std::memory_order_seq_cst); - ssize_t top = thread->queue.top.load(std::memory_order_relaxed); + isize top = thread->queue.top.load(std::memory_order_relaxed); if (top <= bot) { // Queue is not empty @@ -121,9 +121,9 @@ GrabState thread_pool_queue_take(Thread *thread, WorkerTask *task) { } GrabState thread_pool_queue_steal(Thread *thread, WorkerTask *task) { - ssize_t top = thread->queue.top.load(std::memory_order_acquire); + isize top = thread->queue.top.load(std::memory_order_acquire); std::atomic_thread_fence(std::memory_order_seq_cst); - ssize_t bot = thread->queue.bottom.load(std::memory_order_acquire); + isize bot = thread->queue.bottom.load(std::memory_order_acquire); GrabState ret = GrabEmpty; if (top < bot) { diff --git a/src/threading.cpp b/src/threading.cpp index dda98631b..ac79efb05 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -47,13 +47,13 @@ typedef struct WorkerTask { } WorkerTask; typedef struct TaskRingBuffer { - std::atomic size; + std::atomic size; std::atomic buffer; } TaskRingBuffer; typedef struct TaskQueue { - std::atomic top; - std::atomic bottom; + std::atomic top; + std::atomic bottom; std::atomic ring; } TaskQueue; @@ -560,7 +560,7 @@ gb_internal void *internal_thread_proc(void *arg) { } #endif -TaskRingBuffer *taskring_init(ssize_t size) { +TaskRingBuffer *taskring_init(isize size) { TaskRingBuffer *ring = (TaskRingBuffer *)gb_alloc(heap_allocator(), sizeof(TaskRingBuffer)); ring->size = size; ring->buffer = (WorkerTask *)gb_alloc_array(heap_allocator(), WorkerTask, ring->size); -- cgit v1.2.3 From d1450e3d880ec70b306dc735f7f694f265348ef1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jul 2024 14:44:22 +0100 Subject: Fix styling issues --- src/thread_pool.cpp | 39 ++++++++++++++++++++------------------- src/threading.cpp | 12 ++++++------ 2 files changed, 26 insertions(+), 25 deletions(-) (limited to 'src/threading.cpp') diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp index bf953ddd0..62cca6de6 100644 --- a/src/thread_pool.cpp +++ b/src/thread_pool.cpp @@ -11,14 +11,14 @@ gb_internal bool thread_pool_add_task(ThreadPool *pool, WorkerTaskProc *proc, vo gb_internal void thread_pool_wait(ThreadPool *pool); enum GrabState { - GrabSuccess = 0, - GrabEmpty = 1, - GrabFailed = 2, + Grab_Success = 0, + Grab_Empty = 1, + Grab_Failed = 2, }; struct ThreadPool { - gbAllocator threads_allocator; - Slice threads; + gbAllocator threads_allocator; + Slice threads; std::atomic running; Futex tasks_available; @@ -59,8 +59,8 @@ gb_internal void thread_pool_destroy(ThreadPool *pool) { gb_free(pool->threads_allocator, pool->threads.data); } -TaskRingBuffer *taskring_grow(TaskRingBuffer *ring, isize bottom, isize top) { - TaskRingBuffer *new_ring = taskring_init(ring->size * 2); +TaskRingBuffer *task_ring_grow(TaskRingBuffer *ring, isize bottom, isize top) { + TaskRingBuffer *new_ring = task_ring_init(ring->size * 2); for (isize i = top; i < bottom; i++) { new_ring->buffer[i % new_ring->size] = ring->buffer[i % ring->size]; } @@ -75,7 +75,7 @@ void thread_pool_queue_push(Thread *thread, WorkerTask task) { isize size = bot - top; if (size > (cur_ring->size - 1)) { // Queue is full - thread->queue.ring = taskring_grow(thread->queue.ring, bot, top); + thread->queue.ring = task_ring_grow(thread->queue.ring, bot, top); cur_ring = thread->queue.ring.load(std::memory_order_relaxed); } @@ -104,19 +104,19 @@ GrabState thread_pool_queue_take(Thread *thread, WorkerTask *task) { if (!thread->queue.top.compare_exchange_strong(top, top + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { // Race failed thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); - return GrabEmpty; + return Grab_Empty; } thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); - return GrabSuccess; + return Grab_Success; } // We got a task without hitting a race - return GrabSuccess; + return Grab_Success; } else { // Queue is empty thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); - return GrabEmpty; + return Grab_Empty; } } @@ -125,7 +125,7 @@ GrabState thread_pool_queue_steal(Thread *thread, WorkerTask *task) { std::atomic_thread_fence(std::memory_order_seq_cst); isize bot = thread->queue.bottom.load(std::memory_order_acquire); - GrabState ret = GrabEmpty; + GrabState ret = Grab_Empty; if (top < bot) { // Queue is not empty TaskRingBuffer *cur_ring = thread->queue.ring.load(std::memory_order_consume); @@ -133,9 +133,9 @@ GrabState thread_pool_queue_steal(Thread *thread, WorkerTask *task) { if (!thread->queue.top.compare_exchange_strong(top, top + 1, std::memory_order_seq_cst, std::memory_order_relaxed)) { // Race failed - ret = GrabFailed; + ret = Grab_Failed; } else { - ret = GrabSuccess; + ret = Grab_Success; } } return ret; @@ -208,11 +208,10 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { WorkerTask task; GrabState ret = thread_pool_queue_steal(thread, &task); - if (ret == GrabFailed) { - goto main_loop_continue; - } else if (ret == GrabEmpty) { + switch (ret) { + case Grab_Empty: continue; - } else if (ret == GrabSuccess) { + case Grab_Success: task.do_work(task.data); pool->tasks_left.fetch_sub(1, std::memory_order_release); @@ -220,6 +219,8 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { futex_signal(&pool->tasks_left); } + /*fallthrough*/ + case Grab_Failed: goto main_loop_continue; } } diff --git a/src/threading.cpp b/src/threading.cpp index ac79efb05..ff0fdfcde 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -66,9 +66,9 @@ struct Thread { #endif isize idx; - isize stack_size; + isize stack_size; - struct TaskQueue queue; + struct TaskQueue queue; struct ThreadPool *pool; }; @@ -560,10 +560,10 @@ gb_internal void *internal_thread_proc(void *arg) { } #endif -TaskRingBuffer *taskring_init(isize size) { - TaskRingBuffer *ring = (TaskRingBuffer *)gb_alloc(heap_allocator(), sizeof(TaskRingBuffer)); +TaskRingBuffer *task_ring_init(isize size) { + TaskRingBuffer *ring = gb_alloc_item(heap_allocator(), TaskRingBuffer); ring->size = size; - ring->buffer = (WorkerTask *)gb_alloc_array(heap_allocator(), WorkerTask, ring->size); + ring->buffer = gb_alloc_array(heap_allocator(), WorkerTask, ring->size); return ring; } @@ -581,7 +581,7 @@ gb_internal void thread_init(ThreadPool *pool, Thread *t, isize idx) { #endif // Size must be a power of 2 - t->queue.ring = taskring_init(1 << 14); + t->queue.ring = task_ring_init(1 << 14); t->pool = pool; t->idx = idx; } -- cgit v1.2.3 From a8f84c87ae7b4d30cf197f54cbc05da024a17d24 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jul 2024 01:05:29 +0100 Subject: Add the permanent and temporary arenas directly on the `Thread` --- src/common_memory.cpp | 71 +++++++++++++++++++++++++++++++++++++++++++++++---- src/thread_pool.cpp | 5 +++- src/threading.cpp | 11 ++++++-- 3 files changed, 79 insertions(+), 8 deletions(-) (limited to 'src/threading.cpp') diff --git a/src/common_memory.cpp b/src/common_memory.cpp index 42a2125dc..95803d3c8 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -66,6 +66,14 @@ gb_internal isize arena_align_forward_offset(Arena *arena, isize alignment) { return alignment_offset; } +gb_internal void thread_init_arenas(Thread *t) { + t->permanent_arena = gb_alloc_item(heap_allocator(), Arena); + t->temporary_arena = gb_alloc_item(heap_allocator(), Arena); + + t->permanent_arena->minimum_block_size = DEFAULT_MINIMUM_BLOCK_SIZE; + t->temporary_arena->minimum_block_size = DEFAULT_MINIMUM_BLOCK_SIZE; +} + gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment) { GB_ASSERT(gb_is_power_of_two(alignment)); @@ -363,14 +371,67 @@ gb_internal GB_ALLOCATOR_PROC(arena_allocator_proc) { } -gb_global gb_thread_local Arena permanent_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; +enum ThreadArenaKind : uintptr { + ThreadArena_Permanent, + ThreadArena_Temporary, +}; + +gb_global Arena default_permanent_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; +gb_global Arena default_temporary_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; + + +gb_internal Thread *get_current_thread(void); + +gb_internal Arena *get_arena(ThreadArenaKind kind) { + Thread *t = get_current_thread(); + switch (kind) { + case ThreadArena_Permanent: return t ? t->permanent_arena : &default_permanent_arena; + case ThreadArena_Temporary: return t ? t->temporary_arena : &default_temporary_arena; + } + GB_PANIC("INVALID ARENA KIND"); + return nullptr; +} + + + +gb_internal GB_ALLOCATOR_PROC(thread_arena_allocator_proc) { + void *ptr = nullptr; + ThreadArenaKind kind = cast(ThreadArenaKind)cast(uintptr)allocator_data; + Arena *arena = get_arena(kind); + + switch (type) { + case gbAllocation_Alloc: + ptr = arena_alloc(arena, size, alignment); + break; + case gbAllocation_Free: + break; + case gbAllocation_Resize: + if (size == 0) { + ptr = nullptr; + } else if (size <= old_size) { + ptr = old_memory; + } else { + ptr = arena_alloc(arena, size, alignment); + gb_memmove(ptr, old_memory, old_size); + } + break; + case gbAllocation_FreeAll: + GB_PANIC("use arena_free_all directly"); + arena_free_all(arena); + break; + } + + return ptr; +} + + + gb_internal gbAllocator permanent_allocator() { - return arena_allocator(&permanent_arena); + return {thread_arena_allocator_proc, cast(void *)cast(uintptr)ThreadArena_Permanent}; } -gb_global gb_thread_local Arena temporary_arena = {nullptr, DEFAULT_MINIMUM_BLOCK_SIZE}; gb_internal gbAllocator temporary_allocator() { - return arena_allocator(&temporary_arena); + return {thread_arena_allocator_proc, cast(void *)cast(uintptr)ThreadArena_Permanent}; } @@ -378,7 +439,7 @@ gb_internal gbAllocator temporary_allocator() { // #define TEMPORARY_ALLOCATOR_GUARD() -#define TEMPORARY_ALLOCATOR_GUARD() TEMP_ARENA_GUARD(&temporary_arena) +#define TEMPORARY_ALLOCATOR_GUARD() TEMP_ARENA_GUARD(get_arena(ThreadArena_Temporary)) #define PERMANENT_ALLOCATOR_GUARD() diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp index 62cca6de6..8363a4553 100644 --- a/src/thread_pool.cpp +++ b/src/thread_pool.cpp @@ -3,7 +3,10 @@ struct WorkerTask; struct ThreadPool; -gb_thread_local Thread *current_thread; +gb_global gb_thread_local Thread *current_thread; +gb_internal Thread *get_current_thread(void) { + return current_thread; +} gb_internal void thread_pool_init(ThreadPool *pool, isize worker_count, char const *worker_name); gb_internal void thread_pool_destroy(ThreadPool *pool); diff --git a/src/threading.cpp b/src/threading.cpp index ff0fdfcde..c622ac87e 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -70,6 +70,9 @@ struct Thread { struct TaskQueue queue; struct ThreadPool *pool; + + struct Arena *permanent_arena; + struct Arena *temporary_arena; }; typedef std::atomic Futex; @@ -560,18 +563,20 @@ gb_internal void *internal_thread_proc(void *arg) { } #endif -TaskRingBuffer *task_ring_init(isize size) { +gb_internal TaskRingBuffer *task_ring_init(isize size) { TaskRingBuffer *ring = gb_alloc_item(heap_allocator(), TaskRingBuffer); ring->size = size; ring->buffer = gb_alloc_array(heap_allocator(), WorkerTask, ring->size); return ring; } -void thread_queue_destroy(TaskQueue *q) { +gb_internal void thread_queue_destroy(TaskQueue *q) { gb_free(heap_allocator(), (*q->ring).buffer); gb_free(heap_allocator(), q->ring); } +gb_internal void thread_init_arenas(Thread *t); + gb_internal void thread_init(ThreadPool *pool, Thread *t, isize idx) { gb_zero_item(t); #if defined(GB_SYSTEM_WINDOWS) @@ -584,6 +589,8 @@ gb_internal void thread_init(ThreadPool *pool, Thread *t, isize idx) { t->queue.ring = task_ring_init(1 << 14); t->pool = pool; t->idx = idx; + + thread_init_arenas(t); } gb_internal void thread_init_and_start(ThreadPool *pool, Thread *t, isize idx) { -- cgit v1.2.3 From 03426175aea8b3d3a1ebea550613f2155ea07f9a Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 15 Jul 2024 22:45:16 +0200 Subject: add workaround for kernel panics on MacOS --- src/threading.cpp | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) (limited to 'src/threading.cpp') diff --git a/src/threading.cpp b/src/threading.cpp index c622ac87e..011b66028 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -796,13 +796,27 @@ gb_internal void futex_wait(Futex *f, Footex val) { #elif defined(GB_SYSTEM_OSX) +// IMPORTANT NOTE(laytan): We use `OS_SYNC_*_SHARED` and `UL_COMPARE_AND_WAIT_SHARED` flags here. +// these flags tell the kernel that we are using these futexes across different processes which +// causes it to opt-out of some optimisations. +// +// BUT this is not actually the case! We should be using the normal non-shared version and letting +// the kernel optimize (I've measured it to be about 10% faster at the parsing/type checking stages). +// +// However we have reports of people on MacOS running into kernel panics, and this seems to fix it for them. +// Which means there is probably a bug in the kernel in one of these non-shared optimisations causing the panic. +// +// The panic also doesn't seem to happen on normal M1 CPUs, and happen more on later CPUs or pro/max series. +// Probably because they have more going on in terms of threads etc. + #if __has_include() #define DARWIN_WAIT_ON_ADDRESS_AVAILABLE #include #endif -#define UL_COMPARE_AND_WAIT 0x00000001 -#define ULF_NO_ERRNO 0x01000000 +#define UL_COMPARE_AND_WAIT 0x00000001 +#define UL_COMPARE_AND_WAIT_SHARED 0x00000003 +#define ULF_NO_ERRNO 0x01000000 extern "C" int __ulock_wait(uint32_t operation, void *addr, uint64_t value, uint32_t timeout); /* timeout is specified in microseconds */ extern "C" int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value); @@ -811,7 +825,7 @@ gb_internal void futex_signal(Futex *f) { #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE if (__builtin_available(macOS 14.4, *)) { for (;;) { - int ret = os_sync_wake_by_address_any(f, sizeof(Futex), OS_SYNC_WAKE_BY_ADDRESS_NONE); + int ret = os_sync_wake_by_address_any(f, sizeof(Futex), OS_SYNC_WAKE_BY_ADDRESS_SHARED); if (ret >= 0) { return; } @@ -826,7 +840,7 @@ gb_internal void futex_signal(Futex *f) { } else { #endif for (;;) { - int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, 0); + int ret = __ulock_wake(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO, f, 0); if (ret >= 0) { return; } @@ -847,7 +861,7 @@ gb_internal void futex_broadcast(Futex *f) { #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE if (__builtin_available(macOS 14.4, *)) { for (;;) { - int ret = os_sync_wake_by_address_all(f, sizeof(Footex), OS_SYNC_WAKE_BY_ADDRESS_NONE); + int ret = os_sync_wake_by_address_all(f, sizeof(Footex), OS_SYNC_WAKE_BY_ADDRESS_SHARED); if (ret >= 0) { return; } @@ -863,7 +877,7 @@ gb_internal void futex_broadcast(Futex *f) { #endif for (;;) { enum { ULF_WAKE_ALL = 0x00000100 }; - int ret = __ulock_wake(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0); + int ret = __ulock_wake(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0); if (ret == 0) { return; } @@ -884,7 +898,7 @@ gb_internal void futex_wait(Futex *f, Footex val) { #ifdef DARWIN_WAIT_ON_ADDRESS_AVAILABLE if (__builtin_available(macOS 14.4, *)) { for (;;) { - int ret = os_sync_wait_on_address(f, cast(uint64_t)(val), sizeof(Footex), OS_SYNC_WAIT_ON_ADDRESS_NONE); + int ret = os_sync_wait_on_address(f, cast(uint64_t)(val), sizeof(Footex), OS_SYNC_WAIT_ON_ADDRESS_SHARED); if (ret >= 0) { if (*f != val) { return; @@ -902,7 +916,7 @@ gb_internal void futex_wait(Futex *f, Footex val) { } else { #endif for (;;) { - int ret = __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, val, 0); + int ret = __ulock_wait(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO, f, val, 0); if (ret >= 0) { if (*f != val) { return; -- cgit v1.2.3 From b7b3ada3b1bce95d0add13acb8b8192437bad83b Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 18 Aug 2024 19:31:52 +0200 Subject: UL_COMPARE_AND_WAIT_SHARED is macOS 10.15+ --- src/threading.cpp | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) (limited to 'src/threading.cpp') diff --git a/src/threading.cpp b/src/threading.cpp index 011b66028..e30a20a06 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -839,8 +839,16 @@ gb_internal void futex_signal(Futex *f) { } } else { #endif + // UL_COMPARE_AND_WAIT_SHARED is only available on macOS 10.15+ + int WAIT_FLAG; + if (__builtin_available(macOS 10.15, *)) { + WAIT_FLAG = UL_COMPARE_AND_WAIT_SHARED; + } else { + WAIT_FLAG = UL_COMPARE_AND_WAIT; + } + for (;;) { - int ret = __ulock_wake(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO, f, 0); + int ret = __ulock_wake(WAIT_FLAG | ULF_NO_ERRNO, f, 0); if (ret >= 0) { return; } @@ -875,9 +883,17 @@ gb_internal void futex_broadcast(Futex *f) { } } else { #endif + // UL_COMPARE_AND_WAIT_SHARED is only available on macOS 10.15+ + int WAIT_FLAG; + if (__builtin_available(macOS 10.15, *)) { + WAIT_FLAG = UL_COMPARE_AND_WAIT_SHARED; + } else { + WAIT_FLAG = UL_COMPARE_AND_WAIT; + } + for (;;) { enum { ULF_WAKE_ALL = 0x00000100 }; - int ret = __ulock_wake(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0); + int ret = __ulock_wake(WAIT_FLAG | ULF_NO_ERRNO | ULF_WAKE_ALL, f, 0); if (ret == 0) { return; } @@ -915,8 +931,16 @@ gb_internal void futex_wait(Futex *f, Footex val) { } } else { #endif + // UL_COMPARE_AND_WAIT_SHARED is only available on macOS 10.15+ + int WAIT_FLAG; + if (__builtin_available(macOS 10.15, *)) { + WAIT_FLAG = UL_COMPARE_AND_WAIT_SHARED; + } else { + WAIT_FLAG = UL_COMPARE_AND_WAIT; + } + for (;;) { - int ret = __ulock_wait(UL_COMPARE_AND_WAIT_SHARED | ULF_NO_ERRNO, f, val, 0); + int ret = __ulock_wait(WAIT_FLAG | ULF_NO_ERRNO, f, val, 0); if (ret >= 0) { if (*f != val) { return; -- cgit v1.2.3 From 28c643d23f989937c8d530b49a2369e8cd9d39e2 Mon Sep 17 00:00:00 2001 From: Laytan Date: Sun, 1 Sep 2024 15:51:39 +0200 Subject: riscv compiler support --- src/bug_report.cpp | 8 +++++++- src/build_settings.cpp | 4 +++- src/gb/gb.h | 17 +++++++++++++++-- src/main.cpp | 6 ++++++ src/threading.cpp | 3 +++ 5 files changed, 34 insertions(+), 4 deletions(-) (limited to 'src/threading.cpp') diff --git a/src/bug_report.cpp b/src/bug_report.cpp index 916d4016a..5f768f57f 100644 --- a/src/bug_report.cpp +++ b/src/bug_report.cpp @@ -155,7 +155,7 @@ gb_internal void report_windows_product_type(DWORD ProductType) { #endif gb_internal void odin_cpuid(int leaf, int result[]) { - #if defined(GB_CPU_ARM) + #if defined(GB_CPU_ARM) || defined(GB_CPU_RISCV) return; #elif defined(GB_CPU_X86) @@ -225,6 +225,12 @@ gb_internal void report_cpu_info() { gb_printf("ARM\n"); #endif } + #elif defined(GB_CPU_RISCV) + #if defined(GB_ARCH_64_BIT) + gb_printf("RISCV64\n"); + #else + gb_printf("RISCV32\n"); + #endif #else gb_printf("Unknown\n"); #endif diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 3d56f4202..fe0e478c7 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1525,6 +1525,8 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta metrics = &target_haiku_amd64; #elif defined(GB_CPU_ARM) metrics = &target_linux_arm64; + #elif defined(GB_CPU_RISCV) + metrics = &target_linux_riscv64; #else metrics = &target_linux_amd64; #endif @@ -1647,7 +1649,7 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta // Disallow on wasm bc->use_separate_modules = false; - } if(bc->metrics.arch == TargetArch_riscv64) { + } if(bc->metrics.arch == TargetArch_riscv64 && bc->cross_compiling) { bc->link_flags = str_lit("-target riscv64 "); } else { // NOTE: for targets other than darwin, we don't specify a `-target` link flag. diff --git a/src/gb/gb.h b/src/gb/gb.h index 38dabc9bd..0e65696e2 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -39,7 +39,7 @@ extern "C" { #endif #endif -#if defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__64BIT__) || defined(__powerpc64__) || defined(__ppc64__) || defined(__aarch64__) +#if defined(_WIN64) || defined(__x86_64__) || defined(_M_X64) || defined(__64BIT__) || defined(__powerpc64__) || defined(__ppc64__) || defined(__aarch64__) || (defined(__riscv) && __riscv_xlen == 64) #ifndef GB_ARCH_64_BIT #define GB_ARCH_64_BIT 1 #endif @@ -144,6 +144,13 @@ extern "C" { #define GB_CACHE_LINE_SIZE 64 #endif +#elif defined(__riscv) + #ifndef GB_CPU_RISCV + #define GB_CPU_RISCV 1 + #endif + #ifndef GB_CACHE_LINE_SIZE + #define GB_CACHE_LINE_SIZE 64 + #endif #else #error Unknown CPU Type #endif @@ -2562,7 +2569,7 @@ gb_inline void *gb_memcopy(void *dest, void const *source, isize n) { void *dest_copy = dest; __asm__ __volatile__("rep movsb" : "+D"(dest_copy), "+S"(source), "+c"(n) : : "memory"); -#elif defined(GB_CPU_ARM) +#elif defined(GB_CPU_ARM) || defined(GB_CPU_RISCV) u8 *s = cast(u8 *)source; u8 *d = cast(u8 *)dest; for (isize i = 0; i < n; i++) { @@ -6267,6 +6274,12 @@ gb_no_inline isize gb_snprintf_va(char *text, isize max_len, char const *fmt, va asm volatile("mrs %0, cntvct_el0" : "=r"(virtual_timer_value)); return virtual_timer_value; } +#elif defined(__riscv) + gb_inline u64 gb_rdtsc(void) { + u64 result = 0; + __asm__ volatile("rdcycle %0" : "=r"(result)); + return result; + } #else #warning "gb_rdtsc not supported" gb_inline u64 gb_rdtsc(void) { return 0; } diff --git a/src/main.cpp b/src/main.cpp index 5131bdc21..34f3c832d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3252,6 +3252,12 @@ int main(int arg_count, char const **arg_ptr) { gb_printf_err("missing required target feature: \"%.*s\", enable it by setting a different -microarch or explicitly adding it through -target-features\n", LIT(disabled)); gb_exit(1); } + + // NOTE(laytan): some weird errors on LLVM 14 that LLVM 17 fixes. + if (LLVM_VERSION_MAJOR < 17) { + gb_printf_err("Invalid LLVM version %s, RISC-V targets require at least LLVM 17\n", LLVM_VERSION_STRING); + gb_exit(1); + } } if (build_context.show_debug_messages) { diff --git a/src/threading.cpp b/src/threading.cpp index e30a20a06..af8fd803c 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -529,6 +529,9 @@ gb_internal gb_inline void yield_thread(void) { _mm_pause(); #elif defined(GB_CPU_ARM) __asm__ volatile ("yield" : : : "memory"); +#elif defined(GB_CPU_RISCV) + // I guess? + __asm__ volatile ("nop" : : : "memory"); #else #error Unknown architecture #endif -- cgit v1.2.3