From 99121d6ff2b02f3d16b791eb103bb9f9e8b96475 Mon Sep 17 00:00:00 2001 From: Tetralux Date: Sat, 26 Oct 2019 22:35:36 +0000 Subject: Implement core:thread and core:sync on Unix using pthreads Also do some cleanup and refactoring of the thread, sync and time APIs. - remove 'semaphore_release' because 'post' and 'wait' is easier to understand - change 'semaphore_wait' to '*_wait_for' to match Condition - pthreads can be given a stack, but doing so requires the user to set up the guard pages manually. BE WARNED. The alignment requirements of the stack are also platform-dependant; it may need to be page size aligned on some systems. Unclear which systems, however. See 'os.get_page_size', and 'mem.make_aligned'. HOWEVER: I was unable to get custom stacks with guard pages working reliably, so while you can do it, the API does not support it. - add 'os.get_page_size', 'mem.make_aligned', and 'mem.new_aligned'. - removed thread return values because windows and linux are not consistent; windows returns 'i32' and pthreads return 'void*'; besides which, if you really wanted to communicate how the thread exited, you probably wouldn't do it with the thread's exit code. - fixed 'thread.is_done' on Windows; it didn't report true immediately after calling 'thread.join'. - moved time related stuff out of 'core:os' to 'core:time'. - add 'mem.align_backward' - fixed default allocator alignment The heap on Windows, and calloc on Linux, both have no facility to request alignment. It's a bit of hack, but the heap_allocator now overallocates; `size + alignment` bytes, and aligns things to at least 2. It does both of these things to ensure that there is at least two bytes before the payload, which it uses to store how much padding it needed to insert in order to fulfil the alignment requested. - make conditions more sane by matching the Windows behaviour. The fact that they were signalled now lingers until a thread tries to wait, causing them to just pass by uninterrupted, without sleeping or locking the underlying mutex, as it would otherwise need to do. This means that a thread no longer has to be waiting in order to be signalled, which avoids timing bugs that causes deadlocks that are hard to debug and fix. See the comment on the `sync.Condition.flag` field. - add thread priority: `thread.create(worker_proc, .High)` --- core/sys/darwin/mach_darwin.odin | 29 +++++++++++ core/sys/unix/pthread_darwin.odin | 80 ++++++++++++++++++++++++++++ core/sys/unix/pthread_linux.odin | 106 +++++++++++++++++++++++++++++++++++++ core/sys/unix/pthread_unix.odin | 107 ++++++++++++++++++++++++++++++++++++++ core/sys/win32/general.odin | 19 +++++++ core/sys/win32/kernel32.odin | 1 + 6 files changed, 342 insertions(+) create mode 100644 core/sys/darwin/mach_darwin.odin create mode 100644 core/sys/unix/pthread_darwin.odin create mode 100644 core/sys/unix/pthread_linux.odin create mode 100644 core/sys/unix/pthread_unix.odin (limited to 'core/sys') diff --git a/core/sys/darwin/mach_darwin.odin b/core/sys/darwin/mach_darwin.odin new file mode 100644 index 000000000..52a145507 --- /dev/null +++ b/core/sys/darwin/mach_darwin.odin @@ -0,0 +1,29 @@ +package darwin; + +foreign import "system:pthread" + +import "core:c" + +// NOTE(tetra): Unclear whether these should be aligned 16 or not. +// However all other sync primitives are aligned for robustness. +// I cannot currently align these though. +// See core/sys/unix/pthread_linux.odin/pthread_t. +task_t :: distinct u64; +semaphore_t :: distinct u64; + +kern_return_t :: distinct u64; +thread_act_t :: distinct u64; + +@(default_calling_convention="c") +foreign pthread { + mach_task_self :: proc() -> task_t ---; + + semaphore_create :: proc(task: task_t, semaphore: ^semaphore_t, policy, value: c.int) -> kern_return_t ---; + semaphore_destroy :: proc(task: task_t, semaphore: semaphore_t) -> kern_return_t ---; + + semaphore_signal :: proc(semaphore: semaphore_t) -> kern_return_t ---; + semaphore_signal_all :: proc(semaphore: semaphore_t) -> kern_return_t ---; + semaphore_signal_thread :: proc(semaphore: semaphore_t, thread: thread_act_t) -> kern_return_t ---; + + semaphore_wait :: proc(semaphore: semaphore_t) -> kern_return_t ---; +} diff --git a/core/sys/unix/pthread_darwin.odin b/core/sys/unix/pthread_darwin.odin new file mode 100644 index 000000000..97a750b9b --- /dev/null +++ b/core/sys/unix/pthread_darwin.odin @@ -0,0 +1,80 @@ +package unix; + +import "core:c" + +// NOTE(tetra): No 32-bit Macs. +// Source: _pthread_types.h on my Mac. +PTHREAD_SIZE :: 8176; +PTHREAD_ATTR_SIZE :: 56; +PTHREAD_MUTEXATTR_SIZE :: 8; +PTHREAD_MUTEX_SIZE :: 56; +PTHREAD_CONDATTR_SIZE :: 8; +PTHREAD_COND_SIZE :: 40; +PTHREAD_ONCE_SIZE :: 8; +PTHREAD_RWLOCK_SIZE :: 192; +PTHREAD_RWLOCKATTR_SIZE :: 16; + +pthread_t :: opaque struct #align 16 { + sig: c.long, + cleanup_stack: rawptr, + _: [PTHREAD_SIZE] c.char, +}; + +pthread_attr_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_ATTR_SIZE] c.char, +}; + +pthread_cond_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_COND_SIZE] c.char, +}; + +pthread_condattr_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_CONDATTR_SIZE] c.char, +}; + +pthread_mutex_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_MUTEX_SIZE] c.char, +}; + +pthread_mutexattr_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_MUTEXATTR_SIZE] c.char, +}; + +pthread_once_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_ONCE_SIZE] c.char, +}; + +pthread_rwlock_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_RWLOCK_SIZE] c.char, +}; + +pthread_rwlockattr_t :: opaque struct #align 16 { + sig: c.long, + _: [PTHREAD_RWLOCKATTR_SIZE] c.char, +}; + +SCHED_OTHER :: 1; // Avoid if you are writing portable software. +SCHED_FIFO :: 4; +SCHED_RR :: 2; // Round robin. + +SCHED_PARAM_SIZE :: 4; + +sched_param :: struct { + sched_priority: c.int, + _: [SCHED_PARAM_SIZE] c.char, +}; + +// Source: https://github.com/apple/darwin-libpthread/blob/03c4628c8940cca6fd6a82957f683af804f62e7f/pthread/pthread.h#L138 +PTHREAD_CREATE_JOINABLE :: 1; +PTHREAD_CREATE_DETACHED :: 2; +PTHREAD_INHERIT_SCHED :: 1; +PTHREAD_EXPLICIT_SCHED :: 2; +PTHREAD_PROCESS_SHARED :: 1; +PTHREAD_PROCESS_PRIVATE :: 2; diff --git a/core/sys/unix/pthread_linux.odin b/core/sys/unix/pthread_linux.odin new file mode 100644 index 000000000..18ef09a69 --- /dev/null +++ b/core/sys/unix/pthread_linux.odin @@ -0,0 +1,106 @@ +package unix; + +import "core:c" + +// TODO(tetra): For robustness, I'd like to mark this with align 16. +// I cannot currently do this. +// And at the time of writing there is a bug with putting it +// as the only field in a struct. +pthread_t :: distinct u64; +// pthread_t :: struct #align 16 { x: u64 }; + +// NOTE(tetra): Got all the size constants from pthreadtypes-arch.h on my +// Linux machine. + +PTHREAD_COND_T_SIZE :: 48; + +PTHREAD_MUTEXATTR_T_SIZE :: 4; +PTHREAD_CONDATTR_T_SIZE :: 4; +PTHREAD_RWLOCKATTR_T_SIZE :: 8; +PTHREAD_BARRIERATTR_T_SIZE :: 4; + +// WARNING: The sizes of these things are different yet again +// on non-X86! +when size_of(int) == 8 { + PTHREAD_ATTR_T_SIZE :: 56; + PTHREAD_MUTEX_T_SIZE :: 40; + PTHREAD_RWLOCK_T_SIZE :: 56; + PTHREAD_BARRIER_T_SIZE :: 32; +} else when size_of(int) == 4 { + PTHREAD_ATTR_T_SIZE :: 32; + PTHREAD_MUTEX_T_SIZE :: 32; + PTHREAD_RWLOCK_T_SIZE :: 44; + PTHREAD_BARRIER_T_SIZE :: 20; +} + +pthread_cond_t :: opaque struct #align 16 { + _: [PTHREAD_COND_T_SIZE] c.char, +}; +pthread_mutex_t :: opaque struct #align 16 { + _: [PTHREAD_MUTEX_T_SIZE] c.char, +}; +pthread_rwlock_t :: opaque struct #align 16 { + _: [PTHREAD_RWLOCK_T_SIZE] c.char, +}; +pthread_barrier_t :: opaque struct #align 16 { + _: [PTHREAD_BARRIER_T_SIZE] c.char, +}; + +pthread_attr_t :: opaque struct #align 16 { + _: [PTHREAD_ATTR_T_SIZE] c.char, +}; +pthread_condattr_t :: opaque struct #align 16 { + _: [PTHREAD_CONDATTR_T_SIZE] c.char, +}; +pthread_mutexattr_t :: opaque struct #align 16 { + _: [PTHREAD_MUTEXATTR_T_SIZE] c.char, +}; +pthread_rwlockattr_t :: opaque struct #align 16 { + _: [PTHREAD_RWLOCKATTR_T_SIZE] c.char, +}; +pthread_barrierattr_t :: opaque struct #align 16 { + _: [PTHREAD_BARRIERATTR_T_SIZE] c.char, +}; + + +// TODO(tetra, 2019-11-01): Maybe make `enum c.int`s for these? +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_OTHER :: 0; +SCHED_FIFO :: 1; +SCHED_RR :: 2; // Round robin. + +sched_param :: struct { + sched_priority: c.int, +} + +sem_t :: struct #align 16 { + _: [SEM_T_SIZE] c.char, +} + +when size_of(int) == 8 { + SEM_T_SIZE :: 32; +} else when size_of(int) == 4 { + SEM_T_SIZE :: 16; +} + +foreign import "system:pthread" + +@(default_calling_convention="c") +foreign pthread { + // create named semaphore. + // used in process-shared semaphores. + 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 ---; + // sem_timedwait :: proc(sem: ^sem_t, timeout: time.TimeSpec) -> c.int ---; +} diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin new file mode 100644 index 000000000..885048a59 --- /dev/null +++ b/core/sys/unix/pthread_unix.odin @@ -0,0 +1,107 @@ +package unix; + +foreign import "system:pthread" + +import "core:c" +import "core:time" + +// +// On success, these functions return 0. +// + +@(default_calling_convention="c") +foreign pthread { + pthread_create :: proc(t: ^pthread_t, attrs: ^pthread_attr_t, routine: proc(data: rawptr) -> rawptr, arg: rawptr) -> c.int ---; + + // retval is a pointer to a location to put the return value of the thread proc. + pthread_join :: proc(t: pthread_t, retval: rawptr) -> c.int ---; + + pthread_self :: proc() -> pthread_t ---; + + pthread_equal :: proc(a, b: pthread_t) -> b32 ---; + + sched_get_priority_min :: proc(policy: c.int) -> c.int ---; + sched_get_priority_max :: proc(policy: c.int) -> c.int ---; + + // NOTE: POSIX says this can fail with OOM. + pthread_attr_init :: proc(attrs: ^pthread_attr_t) -> c.int ---; + + pthread_attr_destroy :: proc(attrs: ^pthread_attr_t) -> c.int ---; + + pthread_attr_getschedparam :: proc(attrs: ^pthread_attr_t, param: ^sched_param) -> c.int ---; + pthread_attr_setschedparam :: proc(attrs: ^pthread_attr_t, param: ^sched_param) -> c.int ---; + + pthread_attr_getschedpolicy :: proc(t: ^pthread_attr_t, policy: ^c.int) -> c.int ---; + pthread_attr_setschedpolicy :: proc(t: ^pthread_attr_t, policy: c.int) -> c.int ---; + + // states: PTHREAD_CREATE_DETACHED, PTHREAD_CREATE_JOINABLE + pthread_attr_setdetachstate :: proc(attrs: ^pthread_attr_t, detach_state: c.int) -> c.int ---; + + // scheds: PTHREAD_INHERIT_SCHED, PTHREAD_EXPLICIT_SCHED + pthread_attr_setinheritsched :: proc(attrs: ^pthread_attr_t, sched: c.int) -> c.int ---; + + // NOTE(tetra, 2019-11-06): WARNING: Different systems have different alignment requirements. + // For maximum usefulness, use the OS's page size. + // ALSO VERY MAJOR WARNING: `stack_ptr` must be the LAST byte of the stack on systems + // where the stack grows downwards, which is the common case, so far as I know. + // On systems where it grows upwards, give the FIRST byte instead. + // ALSO SLIGHTLY LESS MAJOR WARNING: Using this procedure DISABLES automatically-provided + // guard pages. If you are using this procedure, YOU must set them up manually. + // If you forget to do this, you WILL get stack corruption bugs if you do not EXTREMELY + // know what you are doing! + pthread_attr_setstack :: proc(attrs: ^pthread_attr_t, stack_ptr: rawptr, stack_size: u64) -> c.int ---; + pthread_attr_getstack :: proc(attrs: ^pthread_attr_t, stack_ptr: ^rawptr, stack_size: ^u64) -> c.int ---; +} + +@(default_calling_convention="c") +foreign pthread { + // NOTE: POSIX says this can fail with OOM. + pthread_cond_init :: proc(cond: ^pthread_cond_t, attrs: ^pthread_condattr_t) -> c.int ---; + + pthread_cond_destroy :: proc(cond: ^pthread_cond_t) -> c.int ---; + + pthread_cond_signal :: proc(cond: ^pthread_cond_t) -> c.int ---; + + // same as signal, but wakes up _all_ threads that are waiting + pthread_cond_broadcast :: proc(cond: ^pthread_cond_t) -> c.int ---; + + + // assumes the mutex is pre-locked + pthread_cond_wait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t) -> c.int ---; + pthread_cond_timedwait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t, timeout: ^time.TimeSpec) -> c.int ---; + + pthread_condattr_init :: proc(attrs: ^pthread_condattr_t) -> c.int ---; + pthread_condattr_destroy :: proc(attrs: ^pthread_condattr_t) -> c.int ---; + + // p-shared = "process-shared" - i.e: is this condition shared among multiple processes? + // values: PTHREAD_PROCESS_PRIVATE, PTHREAD_PROCESS_SHARED + pthread_condattr_setpshared :: proc(attrs: ^pthread_condattr_t, value: c.int) -> c.int ---; + pthread_condattr_getpshared :: proc(attrs: ^pthread_condattr_t, result: ^c.int) -> c.int ---; + +} + +@(default_calling_convention="c") +foreign pthread { + // NOTE: POSIX says this can fail with OOM. + pthread_mutex_init :: proc(mutex: ^pthread_mutex_t, attrs: ^pthread_mutexattr_t) -> c.int ---; + + pthread_mutex_destroy :: proc(mutex: ^pthread_mutex_t) -> c.int ---; + + pthread_mutex_trylock :: proc(mutex: ^pthread_mutex_t) -> c.int ---; + + pthread_mutex_lock :: proc(mutex: ^pthread_mutex_t) -> c.int ---; + + pthread_mutex_timedlock :: proc(mutex: ^pthread_mutex_t, timeout: ^time.TimeSpec) -> c.int ---; + + pthread_mutex_unlock :: proc(mutex: ^pthread_mutex_t) -> c.int ---; + + + pthread_mutexattr_init :: proc(attrs: ^pthread_mutexattr_t) -> c.int ---; + pthread_mutexattr_destroy :: proc(attrs: ^pthread_mutexattr_t) -> c.int ---; + + // p-shared = "process-shared" - i.e: is this mutex shared among multiple processes? + // values: PTHREAD_PROCESS_PRIVATE, PTHREAD_PROCESS_SHARED + pthread_mutexattr_setpshared :: proc(attrs: ^pthread_mutexattr_t, value: c.int) -> c.int ---; + pthread_mutexattr_getpshared :: proc(attrs: ^pthread_mutexattr_t, result: ^c.int) -> c.int ---; + +} \ No newline at end of file diff --git a/core/sys/win32/general.odin b/core/sys/win32/general.odin index 16241de05..16853c5cd 100644 --- a/core/sys/win32/general.odin +++ b/core/sys/win32/general.odin @@ -300,6 +300,25 @@ File_Notify_Information :: struct { file_name: [1]u16, } +// https://docs.microsoft.com/en-gb/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info +System_Info :: struct { + using _: struct #raw_union { + oem_id: u32, + using _: struct #raw_union { + processor_architecture: u16, + _: u16, // reserved + }, + }, + page_size: u32, + minimum_application_address: rawptr, + maximum_application_address: rawptr, + active_processor_mask: u32, + number_of_processors: u32, + processor_type: u32, + allocation_granularity: u32, + processor_level: u16, + processor_revision: u16, +} // https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_osversioninfoexa OS_Version_Info_Ex_A :: struct { diff --git a/core/sys/win32/kernel32.odin b/core/sys/win32/kernel32.odin index f647ab7e0..3caeb4963 100644 --- a/core/sys/win32/kernel32.odin +++ b/core/sys/win32/kernel32.odin @@ -31,6 +31,7 @@ foreign kernel32 { @(link_name="GetCommandLineA") get_command_line_a :: proc() -> cstring ---; @(link_name="GetCommandLineW") get_command_line_w :: proc() -> Wstring ---; @(link_name="GetSystemMetrics") get_system_metrics :: proc(index: i32) -> i32 ---; + @(link_name="GetSystemInfo") get_system_info :: proc(info: ^System_Info) ---; @(link_name="GetVersionExA") get_version :: proc(osvi: ^OS_Version_Info_Ex_A) ---; @(link_name="GetCurrentThreadId") get_current_thread_id :: proc() -> u32 ---; -- cgit v1.2.3