From 0a3f73077e1630da7bd122b4fc341136f37d0336 Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Mon, 14 Jul 2025 17:23:36 +0700 Subject: [PATCH 01/90] Get/Set Thread names --- core/sys/posix/pthread.odin | 2 +- core/thread/thread.odin | 22 +++++++++++ core/thread/thread_name_darwin.odin | 13 +++++++ core/thread/thread_name_freebsd.odin | 13 +++++++ core/thread/thread_name_linux.odin | 13 +++++++ core/thread/thread_name_netbsd.odin | 13 +++++++ core/thread/thread_name_openbsd.odin | 13 +++++++ core/thread/thread_other.odin | 5 +++ core/thread/thread_unix.odin | 56 +++++++++++++++++++++++++++- core/thread/thread_windows.odin | 33 ++++++++++++++++ 10 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 core/thread/thread_name_darwin.odin create mode 100644 core/thread/thread_name_freebsd.odin create mode 100644 core/thread/thread_name_linux.odin create mode 100644 core/thread/thread_name_netbsd.odin create mode 100644 core/thread/thread_name_openbsd.odin diff --git a/core/sys/posix/pthread.odin b/core/sys/posix/pthread.odin index 36a3cd7b3..44b0c91d7 100644 --- a/core/sys/posix/pthread.odin +++ b/core/sys/posix/pthread.odin @@ -5,7 +5,7 @@ import "core:c" when ODIN_OS == .Darwin { foreign import lib "system:System.framework" -} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .Linux { +} else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .Linux || ODIN_OS == .OpenBSD { foreign import lib "system:pthread" } else { foreign import lib "system:c" diff --git a/core/thread/thread.odin b/core/thread/thread.odin index 194c7bfef..d4fb48862 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -155,6 +155,28 @@ yield :: proc() { _yield() } +/* +Get thread's name/description. + +If thread is nil the procedure will get the name of the calling thread. + +allocates memory for the returned string using provided allocator. +*/ +get_name :: proc(thread: ^Thread, allocator := context.allocator, loc := #caller_location) -> (string, runtime.Allocator_Error) #optional_allocator_error { + return _get_name(thread, allocator, loc) +} + +/* +Set thread's name/description. + +If thread is nil the procedure will set the name of the calling thread. + +MacOS: only support changing the name of the calling thread. +if thread is not nil the procedure will do nothing. +*/ +set_name :: proc(thread: ^Thread, name: string) { + _set_name(thread, name) +} /* Run a procedure on a different thread. diff --git a/core/thread/thread_name_darwin.odin b/core/thread/thread_name_darwin.odin new file mode 100644 index 000000000..866f608a3 --- /dev/null +++ b/core/thread/thread_name_darwin.odin @@ -0,0 +1,13 @@ +#+build darwin +#+private +package thread + +import "core:sys/posix" +import "core:c" + +foreign import pthread "system:System.framework" + +foreign pthread { + pthread_getname_np :: proc(thread: posix.pthread_t, name: [^]u8, len: c.size_t) -> posix.Errno --- + pthread_setname_np :: proc(name: [^]u8) -> posix.Errno --- +} diff --git a/core/thread/thread_name_freebsd.odin b/core/thread/thread_name_freebsd.odin new file mode 100644 index 000000000..9a36a54d2 --- /dev/null +++ b/core/thread/thread_name_freebsd.odin @@ -0,0 +1,13 @@ +#+build freebsd +#+private +package thread + +import "core:sys/posix" +import "core:c" + +foreign import pthread "system:pthread" + +foreign pthread { + pthread_getname_np :: proc(thread: posix.pthread_t, name: [^]u8, len: c.size_t) -> posix.Errno --- + pthread_setname_np :: proc(thread: posix.pthread_t, name: [^]u8) -> posix.Errno --- +} diff --git a/core/thread/thread_name_linux.odin b/core/thread/thread_name_linux.odin new file mode 100644 index 000000000..67d72e5c9 --- /dev/null +++ b/core/thread/thread_name_linux.odin @@ -0,0 +1,13 @@ +#+build linux +#+private +package thread + +import "core:sys/posix" +import "core:c" + +foreign import pthread "system:pthread" + +foreign pthread { + pthread_getname_np :: proc(thread: posix.pthread_t, name: [^]u8, len: c.size_t) -> posix.Errno --- + pthread_setname_np :: proc(thread: posix.pthread_t, name: [^]u8) -> posix.Errno --- +} diff --git a/core/thread/thread_name_netbsd.odin b/core/thread/thread_name_netbsd.odin new file mode 100644 index 000000000..ce350e5a4 --- /dev/null +++ b/core/thread/thread_name_netbsd.odin @@ -0,0 +1,13 @@ +#+build netbsd +#+private +package thread + +import "core:sys/posix" +import "core:c" + +foreign import pthread "system:pthread" + +foreign pthread { + pthread_getname_np :: proc(thread: posix.pthread_t, name: [^]u8, len: c.size_t) -> posix.Errno --- + pthread_setname_np :: proc(thread: posix.pthread_t, name: [^]u8, arg: rawptr) -> posix.Errno --- +} diff --git a/core/thread/thread_name_openbsd.odin b/core/thread/thread_name_openbsd.odin new file mode 100644 index 000000000..e91970649 --- /dev/null +++ b/core/thread/thread_name_openbsd.odin @@ -0,0 +1,13 @@ +#+build openbsd +#+private +package thread + +import "core:sys/posix" +import "core:c" + +foreign import pthread "system:pthread" + +foreign pthread { + pthread_get_name_np :: proc(thread: posix.pthread_t, name: [^]u8, len: c.size_t) --- + pthread_set_name_np :: proc(thread: posix.pthread_t, name: [^]u8) --- +} diff --git a/core/thread/thread_other.odin b/core/thread/thread_other.odin index dde2a8e48..d9fd9305e 100644 --- a/core/thread/thread_other.odin +++ b/core/thread/thread_other.odin @@ -2,6 +2,7 @@ package thread import "base:intrinsics" +import "base:runtime" _IS_SUPPORTED :: false @@ -45,3 +46,7 @@ _yield :: proc() { unimplemented("core:thread procedure not supported on target") } +_get_name :: proc(thread: Thread, allocator : runtime.Allocator, loc : runtime.Source_Code_Location) -> (string, runtime.Allocator_Error) { + unimplemented("core:thread procedure not supported on target") +} + diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 1431442a9..5c935bbe6 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -7,7 +7,7 @@ import "core:sync" import "core:sys/posix" _IS_SUPPORTED :: true - +_MAX_PTHREAD_NAME_LENGTH :: 16 // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. // Also see core/sys/darwin/mach_darwin.odin/semaphore_t. Thread_Os_Specific :: struct #align(16) { @@ -182,3 +182,57 @@ _terminate :: proc(t: ^Thread, exit_code: int) { _yield :: proc() { posix.sched_yield() } + +_get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.Source_Code_Location) -> (name:string, err:runtime.Allocator_Error) { + // Haiku doesn't have pthread_getname yet + when ODIN_OS == .Haiku { + unimplemented("core:thread get_name for haiku is not yet supported") + } + + tid : posix.pthread_t + if thread == nil do tid = transmute(posix.pthread_t)sync.current_thread_id() + else do tid = thread.unix_thread + + buf := make([]u8, _MAX_PTHREAD_NAME_LENGTH, allocator, loc) or_return + + when ODIN_OS == .Darwin || ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { + pthread_getname_np(tid, raw_data(buf), len(buf)) + } else when ODIN_OS == .OpenBSD { + pthread_get_name_np(tid, raw_data(buf), len(buf)) + } + + name = transmute(string)buf + + return +} + +_set_name :: proc(thread: ^Thread, name:string) { + // Haiku doesn't have pthread_getname yet + when ODIN_OS == .Haiku { + unimplemented("core:thread set_name for haiku is not yet supported") + } else when ODIN_OS == .Darwin { + if thread != nil do return + } else { + tid: posix.pthread_t + if thread == nil do tid = transmute(posix.pthread_t)sync.current_thread_id + else do tid = t.unix_thread + } + + buf : [_MAX_PTHREAD_NAME_LENGTH]u8 + copy_from_string(buf[:], name) + + // _MAX_PTHREAD_NAME_LENGTH includes terminating null + buf[len(buf) - 1] = 0 + + when ODIN_OS == .Darwin { + pthread_setname_np(raw_data(buf[:])) + } else when ODIN_OS == .OpenBSD { + pthread_set_name_np(tid, raw_data(buf[:])) + } else when ODIN_OS == .NetBSD { + format := []u8{'%','s', 0} + pthread_setname_np(tid, raw_data(format), raw_data(buf[:])) + } else { + pthread_setname_np(tid, raw_data(buf[:])) + } + +} \ No newline at end of file diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 358e3e7f1..1609043dd 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -6,8 +6,11 @@ import "base:intrinsics" import "base:runtime" import "core:sync" import win32 "core:sys/windows" +import "core:unicode/utf16" _IS_SUPPORTED :: true +//NOTE(peperronii): not sure about the exact length but there must be a limit +_THREAD_DESCRIPTION_LENGTH :: 64 Thread_Os_Specific :: struct { win32_thread: win32.HANDLE, @@ -152,3 +155,33 @@ _yield :: proc() { win32.SwitchToThread() } +_get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc : runtime.Source_Code_Location) -> (name:string, err:runtime.Allocator_Error) { + t_handle : win32.HANDLE + if thread == nil do t_handle = win32.GetCurrentThread() + else do t_handle = t.win32_thread + + buf_8 : [_THREAD_DESCRIPTION_LENGTH * 2]u8 + buf_16 : [_THREAD_DESCRIPTION_LENGTH]u16 + win32.GetThreadDescription(t_handle, raw_data(buf_16[:])) + n := utf16.decode_to_utf(buf_8[:], buf_16[:]) + buf := make([]u8, n, allocator, loc) + + copy(buf, buf_8[:]) + + name = transmute(string)buf + + return +} + +_set_name :: proc(thread: ^Thread, name: string) { + t_handle : win32.HANDLE + if thread == nil do t_handle = win32.GetCurrentThread() + else do t_handle = t.win32_thread + + buf : [_THREAD_DESCRIPTION_LENGTH]u16 + utf16.encode_string(buf_16[:], name) + // _THREAD_DESCRIPTION_LENGTH includes terminating null + buf[len(buf) - 1] = 0 + win32.SetThreadDescription(t_handle, raw_data(buf[:])) +} + From b87b5431b1e59a465f98748a3816a056bfac35aa Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Mon, 14 Jul 2025 18:10:35 +0700 Subject: [PATCH 02/90] substitute 'do' --- core/thread/thread_unix.odin | 14 ++++++++++---- core/thread/thread_windows.odin | 14 ++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 5c935bbe6..285b1fee9 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -190,8 +190,11 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So } tid : posix.pthread_t - if thread == nil do tid = transmute(posix.pthread_t)sync.current_thread_id() - else do tid = thread.unix_thread + if thread == nil { + tid = transmute(posix.pthread_t)sync.current_thread_id() + } else { + tid = thread.unix_thread + } buf := make([]u8, _MAX_PTHREAD_NAME_LENGTH, allocator, loc) or_return @@ -214,8 +217,11 @@ _set_name :: proc(thread: ^Thread, name:string) { if thread != nil do return } else { tid: posix.pthread_t - if thread == nil do tid = transmute(posix.pthread_t)sync.current_thread_id - else do tid = t.unix_thread + if thread == nil { + tid = transmute(posix.pthread_t)sync.current_thread_id + } else { + tid = t.unix_thread + } } buf : [_MAX_PTHREAD_NAME_LENGTH]u8 diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 1609043dd..e73bb30c7 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -157,8 +157,11 @@ _yield :: proc() { _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc : runtime.Source_Code_Location) -> (name:string, err:runtime.Allocator_Error) { t_handle : win32.HANDLE - if thread == nil do t_handle = win32.GetCurrentThread() - else do t_handle = t.win32_thread + if thread == nil { + t_handle = win32.GetCurrentThread() + } else { + t_handle = t.win32_thread + } buf_8 : [_THREAD_DESCRIPTION_LENGTH * 2]u8 buf_16 : [_THREAD_DESCRIPTION_LENGTH]u16 @@ -175,8 +178,11 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc : runtime.S _set_name :: proc(thread: ^Thread, name: string) { t_handle : win32.HANDLE - if thread == nil do t_handle = win32.GetCurrentThread() - else do t_handle = t.win32_thread + if thread == nil { + t_handle = win32.GetCurrentThread() + } else { + t_handle = t.win32_thread + } buf : [_THREAD_DESCRIPTION_LENGTH]u16 utf16.encode_string(buf_16[:], name) From 6f3cc3168275a6797a6872894aa89acd02754709 Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Mon, 14 Jul 2025 18:17:19 +0700 Subject: [PATCH 03/90] substitute sync.current_thread_id with posix.pthread_self for unix --- core/thread/thread_unix.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 285b1fee9..cadf7ba9a 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -191,7 +191,7 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So tid : posix.pthread_t if thread == nil { - tid = transmute(posix.pthread_t)sync.current_thread_id() + tid = posix.pthread_self() } else { tid = thread.unix_thread } @@ -218,7 +218,7 @@ _set_name :: proc(thread: ^Thread, name:string) { } else { tid: posix.pthread_t if thread == nil { - tid = transmute(posix.pthread_t)sync.current_thread_id + tid = posix.pthread_self() } else { tid = t.unix_thread } From 7265782ddcadc29aa0ad94a45a6e0c15d4731396 Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Mon, 14 Jul 2025 20:39:52 +0700 Subject: [PATCH 04/90] remove unnecessary spaces, fix t -> thread, added more description --- core/thread/thread.odin | 7 +++++-- core/thread/thread_unix.odin | 6 +++--- core/thread/thread_windows.odin | 14 +++++++------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index d4fb48862..ef207a015 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -167,11 +167,14 @@ get_name :: proc(thread: ^Thread, allocator := context.allocator, loc := #caller } /* -Set thread's name/description. +Set thread's name/description. If thread is nil the procedure will set the name of the calling thread. -MacOS: only support changing the name of the calling thread. +the provided string must be available until this procedure ends +and will be truncated to fit their platform's limit. + +MacOS: only supports changing the name of the calling thread. if thread is not nil the procedure will do nothing. */ set_name :: proc(thread: ^Thread, name: string) { diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index cadf7ba9a..cc10c45d6 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -189,7 +189,7 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So unimplemented("core:thread get_name for haiku is not yet supported") } - tid : posix.pthread_t + tid: posix.pthread_t if thread == nil { tid = posix.pthread_self() } else { @@ -220,11 +220,11 @@ _set_name :: proc(thread: ^Thread, name:string) { if thread == nil { tid = posix.pthread_self() } else { - tid = t.unix_thread + tid = thread.unix_thread } } - buf : [_MAX_PTHREAD_NAME_LENGTH]u8 + buf: [_MAX_PTHREAD_NAME_LENGTH]u8 copy_from_string(buf[:], name) // _MAX_PTHREAD_NAME_LENGTH includes terminating null diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index e73bb30c7..f0e324f3e 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -156,15 +156,15 @@ _yield :: proc() { } _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc : runtime.Source_Code_Location) -> (name:string, err:runtime.Allocator_Error) { - t_handle : win32.HANDLE + t_handle: win32.HANDLE if thread == nil { t_handle = win32.GetCurrentThread() } else { - t_handle = t.win32_thread + t_handle = thread.win32_thread } - buf_8 : [_THREAD_DESCRIPTION_LENGTH * 2]u8 - buf_16 : [_THREAD_DESCRIPTION_LENGTH]u16 + buf_8: [_THREAD_DESCRIPTION_LENGTH * 2]u8 + buf_16: [_THREAD_DESCRIPTION_LENGTH]u16 win32.GetThreadDescription(t_handle, raw_data(buf_16[:])) n := utf16.decode_to_utf(buf_8[:], buf_16[:]) buf := make([]u8, n, allocator, loc) @@ -177,14 +177,14 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc : runtime.S } _set_name :: proc(thread: ^Thread, name: string) { - t_handle : win32.HANDLE + t_handle: win32.HANDLE if thread == nil { t_handle = win32.GetCurrentThread() } else { - t_handle = t.win32_thread + t_handle = thread.win32_thread } - buf : [_THREAD_DESCRIPTION_LENGTH]u16 + buf: [_THREAD_DESCRIPTION_LENGTH]u16 utf16.encode_string(buf_16[:], name) // _THREAD_DESCRIPTION_LENGTH includes terminating null buf[len(buf) - 1] = 0 From 8bc1ffd0dfc507cb22b326ecfcf215fb13a6ca04 Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Mon, 14 Jul 2025 20:58:22 +0700 Subject: [PATCH 05/90] remove 'do' --- core/thread/thread_unix.odin | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index cc10c45d6..37df05046 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -214,7 +214,9 @@ _set_name :: proc(thread: ^Thread, name:string) { when ODIN_OS == .Haiku { unimplemented("core:thread set_name for haiku is not yet supported") } else when ODIN_OS == .Darwin { - if thread != nil do return + if thread != nil { + return + } } else { tid: posix.pthread_t if thread == nil { From c07830035e13a475022a900ab067e3f586af0cc5 Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Mon, 14 Jul 2025 21:23:28 +0700 Subject: [PATCH 06/90] fix proc and variable typos --- core/thread/thread_windows.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index f0e324f3e..653a21e14 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -166,10 +166,10 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc : runtime.S buf_8: [_THREAD_DESCRIPTION_LENGTH * 2]u8 buf_16: [_THREAD_DESCRIPTION_LENGTH]u16 win32.GetThreadDescription(t_handle, raw_data(buf_16[:])) - n := utf16.decode_to_utf(buf_8[:], buf_16[:]) + n := utf16.decode_to_utf8(buf_8[:], buf_16[:]) buf := make([]u8, n, allocator, loc) - copy(buf, buf_8[:]) + copy(buf[:], buf_8[:]) name = transmute(string)buf @@ -185,7 +185,7 @@ _set_name :: proc(thread: ^Thread, name: string) { } buf: [_THREAD_DESCRIPTION_LENGTH]u16 - utf16.encode_string(buf_16[:], name) + utf16.encode_string(buf[:], name) // _THREAD_DESCRIPTION_LENGTH includes terminating null buf[len(buf) - 1] = 0 win32.SetThreadDescription(t_handle, raw_data(buf[:])) From 552b4b64fc33cd29124022767dbfbc2ade6a3527 Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Mon, 14 Jul 2025 21:31:33 +0700 Subject: [PATCH 07/90] fix GetThreadDescription ^PCWSTR to PWSTR --- core/sys/windows/kernel32.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 76f2897ac..640afda30 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -168,7 +168,7 @@ foreign kernel32 { ResumeThread :: proc(thread: HANDLE) -> DWORD --- GetThreadPriority :: proc(thread: HANDLE) -> c_int --- SetThreadPriority :: proc(thread: HANDLE, priority: c_int) -> BOOL --- - GetThreadDescription :: proc(hThread: HANDLE, ppszThreadDescription: ^PCWSTR) -> HRESULT --- + GetThreadDescription :: proc(hThread: HANDLE, ppszThreadDescription: PWSTR) -> HRESULT --- SetThreadDescription :: proc(hThread: HANDLE, lpThreadDescription: PCWSTR) -> HRESULT --- GetExitCodeThread :: proc(thread: HANDLE, exit_code: ^DWORD) -> BOOL --- TerminateThread :: proc(thread: HANDLE, exit_code: DWORD) -> BOOL --- From d875d2d445307a01a6a25c70ee9bc7bef4d0e7a6 Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Mon, 14 Jul 2025 21:43:28 +0700 Subject: [PATCH 08/90] added set_name in thread_other --- core/thread/thread_other.odin | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/thread/thread_other.odin b/core/thread/thread_other.odin index d9fd9305e..ef71fa086 100644 --- a/core/thread/thread_other.odin +++ b/core/thread/thread_other.odin @@ -46,7 +46,10 @@ _yield :: proc() { unimplemented("core:thread procedure not supported on target") } -_get_name :: proc(thread: Thread, allocator : runtime.Allocator, loc : runtime.Source_Code_Location) -> (string, runtime.Allocator_Error) { +_get_name :: proc(thread: ^Thread, allocator : runtime.Allocator, loc : runtime.Source_Code_Location) -> (string, runtime.Allocator_Error) { unimplemented("core:thread procedure not supported on target") } +_set_name :: proc(thread: ^Thread, name:string) { + unimplemented("core:thread procedure not supported on target") +} From 02111e75159c2d106f3364d46e4def614bbeb1fb Mon Sep 17 00:00:00 2001 From: "WP. Yingamphol" <145095511+peperronii@users.noreply.github.com> Date: Tue, 15 Jul 2025 09:28:38 +0700 Subject: [PATCH 09/90] no #optional_allocator_error Co-authored-by: Laytan --- core/thread/thread.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index ef207a015..6a5f78156 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -162,7 +162,7 @@ If thread is nil the procedure will get the name of the calling thread. allocates memory for the returned string using provided allocator. */ -get_name :: proc(thread: ^Thread, allocator := context.allocator, loc := #caller_location) -> (string, runtime.Allocator_Error) #optional_allocator_error { +get_name :: proc(thread: ^Thread, allocator := context.allocator, loc := #caller_location) -> (string, runtime.Allocator_Error) { return _get_name(thread, allocator, loc) } From 08c298808b13c3b513ddf6d87fefc48421715889 Mon Sep 17 00:00:00 2001 From: "WP. Yingamphol" <145095511+peperronii@users.noreply.github.com> Date: Tue, 15 Jul 2025 09:29:10 +0700 Subject: [PATCH 10/90] spacing Co-authored-by: Laytan --- core/thread/thread_unix.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 37df05046..bda59482d 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -183,7 +183,7 @@ _yield :: proc() { posix.sched_yield() } -_get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.Source_Code_Location) -> (name:string, err:runtime.Allocator_Error) { +_get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.Source_Code_Location) -> (name: string, err: runtime.Allocator_Error) { // Haiku doesn't have pthread_getname yet when ODIN_OS == .Haiku { unimplemented("core:thread get_name for haiku is not yet supported") From 05b769734b7534eed36138271d969f8477385b8d Mon Sep 17 00:00:00 2001 From: "WP. Yingamphol" <145095511+peperronii@users.noreply.github.com> Date: Tue, 15 Jul 2025 09:29:39 +0700 Subject: [PATCH 11/90] more spacing Co-authored-by: Laytan --- core/thread/thread_windows.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 653a21e14..9281635e5 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -155,7 +155,7 @@ _yield :: proc() { win32.SwitchToThread() } -_get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc : runtime.Source_Code_Location) -> (name:string, err:runtime.Allocator_Error) { +_get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.Source_Code_Location) -> (name: string, err: runtime.Allocator_Error) { t_handle: win32.HANDLE if thread == nil { t_handle = win32.GetCurrentThread() From 5ffba9b222984dbb93282c3bdb54324eabad5f6f Mon Sep 17 00:00:00 2001 From: "WP. Yingamphol" <145095511+peperronii@users.noreply.github.com> Date: Tue, 15 Jul 2025 09:30:49 +0700 Subject: [PATCH 12/90] use copy proc group instead of copy_from_string Co-authored-by: Laytan --- core/thread/thread_unix.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index bda59482d..a9c07cf77 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -227,7 +227,7 @@ _set_name :: proc(thread: ^Thread, name:string) { } buf: [_MAX_PTHREAD_NAME_LENGTH]u8 - copy_from_string(buf[:], name) + copy(buf[:], name) // _MAX_PTHREAD_NAME_LENGTH includes terminating null buf[len(buf) - 1] = 0 From 805c3228b8a9e2714f26dbea235f05341d5c6566 Mon Sep 17 00:00:00 2001 From: "WP. Yingamphol" <145095511+peperronii@users.noreply.github.com> Date: Tue, 15 Jul 2025 09:32:02 +0700 Subject: [PATCH 13/90] Update core/thread/thread_unix.odin Co-authored-by: Laytan --- core/thread/thread_unix.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index a9c07cf77..46c58f6eb 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -209,7 +209,7 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So return } -_set_name :: proc(thread: ^Thread, name:string) { +_set_name :: proc(thread: ^Thread, name: string) { // Haiku doesn't have pthread_getname yet when ODIN_OS == .Haiku { unimplemented("core:thread set_name for haiku is not yet supported") From 9aa4afe58533f2d7d0c1880fbab62c4f87db0509 Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Tue, 15 Jul 2025 10:41:57 +0700 Subject: [PATCH 14/90] GetThreadDescription ^PWSTR --- core/sys/windows/kernel32.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 640afda30..dc7de26cd 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -168,7 +168,7 @@ foreign kernel32 { ResumeThread :: proc(thread: HANDLE) -> DWORD --- GetThreadPriority :: proc(thread: HANDLE) -> c_int --- SetThreadPriority :: proc(thread: HANDLE, priority: c_int) -> BOOL --- - GetThreadDescription :: proc(hThread: HANDLE, ppszThreadDescription: PWSTR) -> HRESULT --- + GetThreadDescription :: proc(hThread: HANDLE, ppszThreadDescription: ^PWSTR) -> HRESULT --- SetThreadDescription :: proc(hThread: HANDLE, lpThreadDescription: PCWSTR) -> HRESULT --- GetExitCodeThread :: proc(thread: HANDLE, exit_code: ^DWORD) -> BOOL --- TerminateThread :: proc(thread: HANDLE, exit_code: DWORD) -> BOOL --- From 8f81b8761a4a36bc0ef6ef57267b6636974ad752 Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Tue, 15 Jul 2025 10:55:54 +0700 Subject: [PATCH 15/90] changed [^]u8 to cstring for netbsd, added truncate_to_byte(name,0) on name return --- core/thread/thread_name_netbsd.odin | 2 +- core/thread/thread_unix.odin | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/thread/thread_name_netbsd.odin b/core/thread/thread_name_netbsd.odin index ce350e5a4..12f31f760 100644 --- a/core/thread/thread_name_netbsd.odin +++ b/core/thread/thread_name_netbsd.odin @@ -9,5 +9,5 @@ foreign import pthread "system:pthread" foreign pthread { pthread_getname_np :: proc(thread: posix.pthread_t, name: [^]u8, len: c.size_t) -> posix.Errno --- - pthread_setname_np :: proc(thread: posix.pthread_t, name: [^]u8, arg: rawptr) -> posix.Errno --- + pthread_setname_np :: proc(thread: posix.pthread_t, name: cstring, arg: rawptr) -> posix.Errno --- } diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 46c58f6eb..80766fad6 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -5,6 +5,7 @@ package thread import "base:runtime" import "core:sync" import "core:sys/posix" +import "core:strings" _IS_SUPPORTED :: true _MAX_PTHREAD_NAME_LENGTH :: 16 @@ -205,7 +206,8 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So } name = transmute(string)buf - + name = strings.truncate_to_byte(name, 0) + return } @@ -237,8 +239,7 @@ _set_name :: proc(thread: ^Thread, name: string) { } else when ODIN_OS == .OpenBSD { pthread_set_name_np(tid, raw_data(buf[:])) } else when ODIN_OS == .NetBSD { - format := []u8{'%','s', 0} - pthread_setname_np(tid, raw_data(format), raw_data(buf[:])) + pthread_setname_np(tid, "%s", raw_data(buf[:])) } else { pthread_setname_np(tid, raw_data(buf[:])) } From 228e2752a3d04814531472d36917ef3bcf2bfa16 Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Tue, 15 Jul 2025 12:29:26 +0700 Subject: [PATCH 16/90] use win32.wstring_to_utf8, default to temp_allocator, guarantees proper null termination --- core/thread/thread.odin | 2 +- core/thread/thread_windows.odin | 15 ++++----------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index 6a5f78156..56853a080 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -162,7 +162,7 @@ If thread is nil the procedure will get the name of the calling thread. allocates memory for the returned string using provided allocator. */ -get_name :: proc(thread: ^Thread, allocator := context.allocator, loc := #caller_location) -> (string, runtime.Allocator_Error) { +get_name :: proc(thread: ^Thread, allocator := context.temp_allocator, loc := #caller_location) -> (string, runtime.Allocator_Error) { return _get_name(thread, allocator, loc) } diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 9281635e5..ba1da9aa8 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -163,15 +163,9 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So t_handle = thread.win32_thread } - buf_8: [_THREAD_DESCRIPTION_LENGTH * 2]u8 - buf_16: [_THREAD_DESCRIPTION_LENGTH]u16 - win32.GetThreadDescription(t_handle, raw_data(buf_16[:])) - n := utf16.decode_to_utf8(buf_8[:], buf_16[:]) - buf := make([]u8, n, allocator, loc) - - copy(buf[:], buf_8[:]) - - name = transmute(string)buf + buf_16: win32.PWSTR + win32.GetThreadDescription(t_handle, &buf_16) + name = win32.wstring_to_utf8(buf_16, -1, allocator) or_return return } @@ -185,9 +179,8 @@ _set_name :: proc(thread: ^Thread, name: string) { } buf: [_THREAD_DESCRIPTION_LENGTH]u16 - utf16.encode_string(buf[:], name) // _THREAD_DESCRIPTION_LENGTH includes terminating null - buf[len(buf) - 1] = 0 + utf16.encode_string(buf[:len(buf) - 1], name) win32.SetThreadDescription(t_handle, raw_data(buf[:])) } From 8d278acba5b19cc8a42eebfe1ba38c677a6c5759 Mon Sep 17 00:00:00 2001 From: "WP. Yingamphol" <145095511+peperronii@users.noreply.github.com> Date: Tue, 15 Jul 2025 12:51:48 +0700 Subject: [PATCH 17/90] fix system:System.Framework pthread.odin --- core/sys/posix/pthread.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sys/posix/pthread.odin b/core/sys/posix/pthread.odin index 44b0c91d7..ae4264e49 100644 --- a/core/sys/posix/pthread.odin +++ b/core/sys/posix/pthread.odin @@ -4,7 +4,7 @@ package posix import "core:c" when ODIN_OS == .Darwin { - foreign import lib "system:System.framework" + foreign import lib "system:System" } else when ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD || ODIN_OS == .Linux || ODIN_OS == .OpenBSD { foreign import lib "system:pthread" } else { From a3571136516c19caf13ccc5f0cb5529ee8fe749b Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Tue, 22 Jul 2025 10:52:29 +0700 Subject: [PATCH 18/90] thread.create et al with name parameter --- core/thread/thread.odin | 69 ++++++++++++++------------------- core/thread/thread_other.odin | 2 +- core/thread/thread_unix.odin | 29 +++++++------- core/thread/thread_windows.odin | 17 ++++---- 4 files changed, 53 insertions(+), 64 deletions(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index 56853a080..d4a27de13 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -48,6 +48,9 @@ Thread :: struct { // started. Should be set after the thread has been created, but before // it is started. data: rawptr, + // Thread's Name/Description that will get set during thread creation + // for thread's creation only : do not refer to it, use thread.get_name instead + name: Maybe(string), // User-supplied integer, that will be available to the thread once it is // started. Should be set after the thread has been created, but before // it is started. @@ -102,8 +105,8 @@ thread will be in a suspended state, until `start()` procedure is called. To start the thread, call `start()`. Also the `create_and_start()` procedure can be called to create and start the thread immediately. */ -create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { - return _create(procedure, priority) +create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal, name: Maybe(string) = nil) -> ^Thread { + return _create(procedure, priority, name) } /* @@ -166,20 +169,6 @@ get_name :: proc(thread: ^Thread, allocator := context.temp_allocator, loc := #c return _get_name(thread, allocator, loc) } -/* -Set thread's name/description. - -If thread is nil the procedure will set the name of the calling thread. - -the provided string must be available until this procedure ends -and will be truncated to fit their platform's limit. - -MacOS: only supports changing the name of the calling thread. -if thread is not nil the procedure will do nothing. -*/ -set_name :: proc(thread: ^Thread, name: string) { - _set_name(thread, name) -} /* Run a procedure on a different thread. @@ -191,8 +180,8 @@ to execute. The thread will have priority specified by the `priority` parameter. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { - create_and_start(fn, init_context, priority, true) +run :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, name: Maybe(string) = nil) { + create_and_start(fn, init_context, priority, true, name) } /* @@ -206,8 +195,8 @@ to execute. The thread will have priority specified by the `priority` parameter. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) { - create_and_start_with_data(data, fn, init_context, priority, true) +run_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, name: Maybe(string) = nil) { + create_and_start_with_data(data, fn, init_context, priority, true, name) } /* @@ -221,9 +210,9 @@ to execute. The thread will have priority specified by the `priority` parameter. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) +run_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, name: Maybe(string) = nil) where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { - create_and_start_with_poly_data(data, fn, init_context, priority, true) + create_and_start_with_poly_data(data, fn, init_context, priority, true, name) } /* @@ -237,9 +226,9 @@ to execute. The thread will have priority specified by the `priority` parameter. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) +run_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, name: Maybe(string) = nil) where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { - create_and_start_with_poly_data2(arg1, arg2, fn, init_context, priority, true) + create_and_start_with_poly_data2(arg1, arg2, fn, init_context, priority, true, name) } /* @@ -253,9 +242,9 @@ to execute. The thread will have priority specified by the `priority` parameter. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -run_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) +run_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, name: Maybe(string) = nil) where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { - create_and_start_with_poly_data3(arg1, arg2, arg3, fn, init_context, priority, true) + create_and_start_with_poly_data3(arg1, arg2, arg3, fn, init_context, priority, true, name) } /* @@ -269,9 +258,9 @@ to execute. The thread will have priority specified by the `priority` parameter. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -run_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal) +run_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, name: Maybe(string) = nil) where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS { - create_and_start_with_poly_data4(arg1, arg2, arg3, arg4, fn, init_context, priority, true) + create_and_start_with_poly_data4(arg1, arg2, arg3, arg4, fn, init_context, priority, true, name) } /* @@ -292,12 +281,12 @@ That includes calling `join`, which needs to dereference ^Thread`. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) { +create_and_start :: proc(fn: proc(), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false, name: Maybe(string) = nil) -> (t: ^Thread) { thread_proc :: proc(t: ^Thread) { fn := cast(proc())t.data fn() } - if t = create(thread_proc, priority); t == nil { + if t = create(thread_proc, priority, name); t == nil { return } t.data = rawptr(fn) @@ -327,14 +316,14 @@ That includes calling `join`, which needs to dereference ^Thread`. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) { +create_and_start_with_data :: proc(data: rawptr, fn: proc(data: rawptr), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false, name: Maybe(string) = nil) -> (t: ^Thread) { thread_proc :: proc(t: ^Thread) { fn := cast(proc(rawptr))t.data assert(t.user_index >= 1) data := t.user_args[0] fn(data) } - if t = create(thread_proc, priority); t == nil { + if t = create(thread_proc, priority, name); t == nil { return } t.data = rawptr(fn) @@ -366,7 +355,7 @@ That includes calling `join`, which needs to dereference ^Thread`. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) +create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false, name: Maybe(string) = nil) -> (t: ^Thread) where size_of(T) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T))t.data @@ -374,7 +363,7 @@ create_and_start_with_poly_data :: proc(data: $T, fn: proc(data: T), init_contex data := (^T)(&t.user_args[0])^ fn(data) } - if t = create(thread_proc, priority); t == nil { + if t = create(thread_proc, priority, name); t == nil { return } t.data = rawptr(fn) @@ -411,7 +400,7 @@ That includes calling `join`, which needs to dereference ^Thread`. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) +create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false, name: Maybe(string) = nil) -> (t: ^Thread) where size_of(T1) + size_of(T2) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T1, T2))t.data @@ -423,7 +412,7 @@ create_and_start_with_poly_data2 :: proc(arg1: $T1, arg2: $T2, fn: proc(T1, T2), fn(arg1, arg2) } - if t = create(thread_proc, priority); t == nil { + if t = create(thread_proc, priority, name); t == nil { return } t.data = rawptr(fn) @@ -462,7 +451,7 @@ That includes calling `join`, which needs to dereference ^Thread`. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) +create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: proc(arg1: T1, arg2: T2, arg3: T3), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false, name: Maybe(string) = nil) -> (t: ^Thread) where size_of(T1) + size_of(T2) + size_of(T3) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T1, T2, T3))t.data @@ -475,7 +464,7 @@ create_and_start_with_poly_data3 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, fn: pr fn(arg1, arg2, arg3) } - if t = create(thread_proc, priority); t == nil { + if t = create(thread_proc, priority, name); t == nil { return } t.data = rawptr(fn) @@ -515,7 +504,7 @@ That includes calling `join`, which needs to dereference ^Thread`. is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. */ -create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false) -> (t: ^Thread) +create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: $T4, fn: proc(arg1: T1, arg2: T2, arg3: T3, arg4: T4), init_context: Maybe(runtime.Context) = nil, priority := Thread_Priority.Normal, self_cleanup := false, name: Maybe(string) = nil) -> (t: ^Thread) where size_of(T1) + size_of(T2) + size_of(T3) + size_of(T4) <= size_of(rawptr) * MAX_USER_ARGUMENTS { thread_proc :: proc(t: ^Thread) { fn := cast(proc(T1, T2, T3, T4))t.data @@ -529,7 +518,7 @@ create_and_start_with_poly_data4 :: proc(arg1: $T1, arg2: $T2, arg3: $T3, arg4: fn(arg1, arg2, arg3, arg4) } - if t = create(thread_proc, priority); t == nil { + if t = create(thread_proc, priority, name); t == nil { return } t.data = rawptr(fn) diff --git a/core/thread/thread_other.odin b/core/thread/thread_other.odin index ef71fa086..83ea31fe1 100644 --- a/core/thread/thread_other.odin +++ b/core/thread/thread_other.odin @@ -14,7 +14,7 @@ _thread_priority_map := [Thread_Priority]i32{ .High = +2, } -_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^Thread { +_create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal, name: Maybe(string) = nil) -> ^Thread { unimplemented("core:thread procedure not supported on target") } diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 80766fad6..c723c434e 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -19,7 +19,7 @@ Thread_Os_Specific :: struct #align(16) { // Creates a thread which will run the given procedure. // It then waits for `start` to be called. // -_create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { +_create :: proc(procedure: Thread_Proc, priority: Thread_Priority, name: Maybe(string)) -> ^Thread { __unix_thread_entry_proc :: proc "c" (t: rawptr) -> rawptr { t := (^Thread)(t) @@ -58,6 +58,8 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { runtime.run_thread_local_cleaners() } + _set_name(t) + t.procedure(t) } @@ -211,23 +213,18 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So return } -_set_name :: proc(thread: ^Thread, name: string) { - // Haiku doesn't have pthread_getname yet - when ODIN_OS == .Haiku { - unimplemented("core:thread set_name for haiku is not yet supported") - } else when ODIN_OS == .Darwin { - if thread != nil { - return - } - } else { - tid: posix.pthread_t - if thread == nil { - tid = posix.pthread_self() - } else { - tid = thread.unix_thread - } +_set_name :: proc(thread: ^Thread) { + if ODIN_OS == .Haiku { + return } + name, ok := thread.name.? + if !ok { + return + } + + tid := thread.unix_thread + buf: [_MAX_PTHREAD_NAME_LENGTH]u8 copy(buf[:], name) diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index ba1da9aa8..bd586ed49 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -25,7 +25,7 @@ _thread_priority_map := [Thread_Priority]i32{ .High = +2, } -_create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { +_create :: proc(procedure: Thread_Proc, priority: Thread_Priority, name: Maybe(string)) -> ^Thread { win32_thread_id: win32.DWORD __windows_thread_entry_proc :: proc "system" (t_: rawptr) -> win32.DWORD { @@ -47,6 +47,8 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { runtime.run_thread_local_cleaners() } + _set_name(t) + t.procedure(t) } @@ -79,6 +81,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { thread.win32_thread = win32_thread thread.win32_thread_id = win32_thread_id thread.id = int(win32_thread_id) + thread.name = name ok := win32.SetThreadPriority(win32_thread, _thread_priority_map[priority]) assert(ok == true) @@ -170,14 +173,14 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So return } -_set_name :: proc(thread: ^Thread, name: string) { - t_handle: win32.HANDLE - if thread == nil { - t_handle = win32.GetCurrentThread() - } else { - t_handle = thread.win32_thread +_set_name :: proc(thread: ^Thread) { + name, ok := thread.name.? + if !ok { + return } + t_handle = thread.win32_thread + buf: [_THREAD_DESCRIPTION_LENGTH]u16 // _THREAD_DESCRIPTION_LENGTH includes terminating null utf16.encode_string(buf[:len(buf) - 1], name) From 3c9d05a8c62b6d82fec65c4628b058a31646fca4 Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Tue, 22 Jul 2025 11:00:49 +0700 Subject: [PATCH 19/90] fix tid/handle bug on macos/win --- core/thread/thread_unix.odin | 4 +++- core/thread/thread_windows.odin | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index c723c434e..7491ec457 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -223,7 +223,9 @@ _set_name :: proc(thread: ^Thread) { return } - tid := thread.unix_thread + when ODIN_OS != .Darwin { + tid := thread.unix_thread + } buf: [_MAX_PTHREAD_NAME_LENGTH]u8 copy(buf[:], name) diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index bd586ed49..e535b1adf 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -179,7 +179,7 @@ _set_name :: proc(thread: ^Thread) { return } - t_handle = thread.win32_thread + t_handle := thread.win32_thread buf: [_THREAD_DESCRIPTION_LENGTH]u16 // _THREAD_DESCRIPTION_LENGTH includes terminating null From 2f770ff241303637661af6a621d9f10004d846ed Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Tue, 22 Jul 2025 11:18:45 +0700 Subject: [PATCH 20/90] check for Haiku before calling _set_name --- core/thread/thread_unix.odin | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 7491ec457..6a0a1fcfd 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -58,7 +58,9 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority, name: Maybe(s runtime.run_thread_local_cleaners() } - _set_name(t) + when ODIN_OS != .Haiku { + _set_name(t) + } t.procedure(t) } @@ -214,10 +216,6 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So } _set_name :: proc(thread: ^Thread) { - if ODIN_OS == .Haiku { - return - } - name, ok := thread.name.? if !ok { return From c1643326619d6598467b2fbc06feee86a49f057a Mon Sep 17 00:00:00 2001 From: "WP. Yingamphol" Date: Wed, 20 Aug 2025 07:10:19 +0700 Subject: [PATCH 21/90] Apply suggestions from code review Improve code description Co-authored-by: Sunagatov Denis --- core/thread/thread.odin | 4 +++- core/thread/thread_other.odin | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index d4a27de13..c0174636a 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -161,7 +161,9 @@ yield :: proc() { /* Get thread's name/description. -If thread is nil the procedure will get the name of the calling thread. +This procedure returns the name of the given thread. If `thread` is `nil`, this procedure returns the name of the calling thread. + +**Note(linux, bsd)**: Because the thread name is stored in as the `cmdline`, if the thread name was not set, the command that has been used to create the process as the name of the thread. allocates memory for the returned string using provided allocator. */ diff --git a/core/thread/thread_other.odin b/core/thread/thread_other.odin index 83ea31fe1..1bdc11a4e 100644 --- a/core/thread/thread_other.odin +++ b/core/thread/thread_other.odin @@ -47,9 +47,9 @@ _yield :: proc() { } _get_name :: proc(thread: ^Thread, allocator : runtime.Allocator, loc : runtime.Source_Code_Location) -> (string, runtime.Allocator_Error) { - unimplemented("core:thread procedure not supported on target") + unimplemented("core:thread procedure not supported on this target") } _set_name :: proc(thread: ^Thread, name:string) { - unimplemented("core:thread procedure not supported on target") + unimplemented("core:thread procedure not supported on this target") } From 8884ad01f5188b1297ddd7eba0f4a0ff9795ab56 Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Wed, 20 Aug 2025 21:00:25 +0700 Subject: [PATCH 22/90] added name argument description --- core/thread/thread.odin | 42 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index c0174636a..53f9b3f62 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -104,7 +104,11 @@ thread will be in a suspended state, until `start()` procedure is called. To start the thread, call `start()`. Also the `create_and_start()` procedure can be called to create and start the thread immediately. + +Optionally specify the thread's name/description. +the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. */ + create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal, name: Maybe(string) = nil) -> ^Thread { return _create(procedure, priority, name) } @@ -178,6 +182,9 @@ This procedure runs the given procedure on another thread. The context specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. +Optionally specify the thread's name/description. +the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. + **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. @@ -193,6 +200,9 @@ This procedure runs the given procedure on another thread. The context specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. +Optionally specify the thread's name/description. +the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. + **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. @@ -208,6 +218,9 @@ This procedure runs the given procedure on another thread. The context specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. +Optionally specify the thread's name/description. +the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. + **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. @@ -224,6 +237,9 @@ This procedure runs the given procedure on another thread. The context specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. +Optionally specify the thread's name/description. +the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. + **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. @@ -240,6 +256,9 @@ This procedure runs the given procedure on another thread. The context specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. +Optionally specify the thread's name/description. +the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. + **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. @@ -256,6 +275,9 @@ This procedure runs the given procedure on another thread. The context specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. +Optionally specify the thread's name/description. +the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. + **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` in order to free the resources associated with the temporary allocations. @@ -276,6 +298,9 @@ If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be automatically freed. +Optionally specify the thread's name/description. +the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. + **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -311,6 +336,9 @@ If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be automatically freed. +Optionally specify the thread's name/description. +the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. + **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -350,6 +378,9 @@ If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be automatically freed. +Optionally specify the thread's name/description. +the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. + **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -395,6 +426,9 @@ If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be automatically freed. +Optionally specify the thread's name/description. +the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. + **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -446,6 +480,9 @@ If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be automatically freed. +Optionally specify the thread's name/description. +the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. + **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -499,6 +536,9 @@ If `self_cleanup` is specified, after the thread finishes the execution of the `fn` procedure, the resources associated with the thread are going to be automatically freed. +Optionally specify the thread's name/description. +the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. + **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -572,4 +612,4 @@ _maybe_destroy_default_temp_allocator :: proc(init_context: Maybe(runtime.Contex if context.temp_allocator.procedure == runtime.default_temp_allocator_proc { runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data) } -} \ No newline at end of file +} From 5ab3c1794e976914e48c25ad2e1c493db229bb30 Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Wed, 20 Aug 2025 21:51:56 +0700 Subject: [PATCH 23/90] fixed windows api and free GetThreadName's allocation --- core/thread/thread_windows.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index e535b1adf..a0320890b 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -169,6 +169,7 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So buf_16: win32.PWSTR win32.GetThreadDescription(t_handle, &buf_16) name = win32.wstring_to_utf8(buf_16, -1, allocator) or_return + win32.LocalFree(rawptr(buf_16)) return } From e4cc58d992959e5844aeff63ce845a8db94ed4a8 Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Thu, 28 Aug 2025 09:50:25 +0700 Subject: [PATCH 24/90] thread.name = name in unix --- core/thread/thread_unix.odin | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index ab7ee9a0e..218535e56 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -58,9 +58,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority, name: Maybe(s runtime.run_thread_local_cleaners() } - when ODIN_OS != .Haiku { - _set_name(t) - } + _set_name(t) t.procedure(t) } @@ -130,6 +128,9 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority, name: Maybe(s free(thread, thread.creation_allocator) return nil } + + thread.name = name + return thread } @@ -246,4 +247,4 @@ _set_name :: proc(thread: ^Thread) { pthread_setname_np(tid, raw_data(buf[:])) } -} \ No newline at end of file +} From 704a917355599494eec7cbf9791a5673a890e5a3 Mon Sep 17 00:00:00 2001 From: "WP. Yingamphol" Date: Thu, 28 Aug 2025 09:53:53 +0700 Subject: [PATCH 25/90] re-added Haiku os _set_name guard --- core/thread/thread_unix.odin | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 218535e56..8a3aa4152 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -58,7 +58,9 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority, name: Maybe(s runtime.run_thread_local_cleaners() } - _set_name(t) + when ODIN_OS != .Haiku { + _set_name(t) + } t.procedure(t) } From ed75ab49bbbf6e3aa0f7b0aa979c5eaebaf575b3 Mon Sep 17 00:00:00 2001 From: "WP. Yingamphol" Date: Sun, 14 Sep 2025 13:57:49 +0700 Subject: [PATCH 26/90] Change windows Thread_Description_Length to 128 --- core/thread/thread_windows.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index a0320890b..2f5d19b2e 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -10,7 +10,7 @@ import "core:unicode/utf16" _IS_SUPPORTED :: true //NOTE(peperronii): not sure about the exact length but there must be a limit -_THREAD_DESCRIPTION_LENGTH :: 64 +_THREAD_DESCRIPTION_LENGTH :: 128 Thread_Os_Specific :: struct { win32_thread: win32.HANDLE, From f873231f2efb85d6f0875792acd67dda78840997 Mon Sep 17 00:00:00 2001 From: "WP. Yingamphol" Date: Thu, 20 Nov 2025 19:18:55 +0700 Subject: [PATCH 27/90] Update thread name/description truncation limits --- core/thread/thread.odin | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index 53f9b3f62..d32e6ed41 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -106,7 +106,7 @@ To start the thread, call `start()`. Also the `create_and_start()` procedure can be called to create and start the thread immediately. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. +the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. */ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal, name: Maybe(string) = nil) -> ^Thread { @@ -183,7 +183,7 @@ specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. +the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` @@ -201,7 +201,7 @@ specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. +the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` @@ -219,7 +219,7 @@ specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. +the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` @@ -238,7 +238,7 @@ specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. +the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` @@ -257,7 +257,7 @@ specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. +the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` @@ -276,7 +276,7 @@ specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. +the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` @@ -299,7 +299,7 @@ If `self_cleanup` is specified, after the thread finishes the execution of the automatically freed. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. +the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -337,7 +337,7 @@ If `self_cleanup` is specified, after the thread finishes the execution of the automatically freed. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. +the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -379,7 +379,7 @@ If `self_cleanup` is specified, after the thread finishes the execution of the automatically freed. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. +the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -427,7 +427,7 @@ If `self_cleanup` is specified, after the thread finishes the execution of the automatically freed. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. +the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -481,7 +481,7 @@ If `self_cleanup` is specified, after the thread finishes the execution of the automatically freed. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. +the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -537,7 +537,7 @@ If `self_cleanup` is specified, after the thread finishes the execution of the automatically freed. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 63 bytes on Windows. +the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. From f736047df437d6fd38292e66fff8e0904895dc2b Mon Sep 17 00:00:00 2001 From: "WP. Yingamphol" Date: Sun, 7 Dec 2025 22:09:49 +0700 Subject: [PATCH 28/90] Make compatible with the new win32.SetThreadDescription The second argument is changed to a cstring16. --- core/thread/thread_windows.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 2f5d19b2e..b8fd2bc48 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -185,6 +185,6 @@ _set_name :: proc(thread: ^Thread) { buf: [_THREAD_DESCRIPTION_LENGTH]u16 // _THREAD_DESCRIPTION_LENGTH includes terminating null utf16.encode_string(buf[:len(buf) - 1], name) - win32.SetThreadDescription(t_handle, raw_data(buf[:])) + win32.SetThreadDescription(t_handle, cstring16(buf)) } From 9529ebf8b7128b961a6bae8fe63edb0d30d79be9 Mon Sep 17 00:00:00 2001 From: "WP. Yingamphol" Date: Sun, 7 Dec 2025 22:52:03 +0700 Subject: [PATCH 29/90] forgot to add raw_data --- core/thread/thread_windows.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index b8fd2bc48..b797c059c 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -185,6 +185,6 @@ _set_name :: proc(thread: ^Thread) { buf: [_THREAD_DESCRIPTION_LENGTH]u16 // _THREAD_DESCRIPTION_LENGTH includes terminating null utf16.encode_string(buf[:len(buf) - 1], name) - win32.SetThreadDescription(t_handle, cstring16(buf)) + win32.SetThreadDescription(t_handle, cstring16(raw_data(buf[:]))) } From c9064cf532d3523ca9fa64aca0af0fe1bd362edf Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Mon, 23 Mar 2026 22:51:16 +0700 Subject: [PATCH 30/90] more concise null termination --- core/thread/thread_unix.odin | 4 ++-- core/thread/thread_windows.odin | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 8b08d8c19..dc1d13020 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -231,10 +231,10 @@ _set_name :: proc(thread: ^Thread) { } buf: [_MAX_PTHREAD_NAME_LENGTH]u8 - copy(buf[:], name) // _MAX_PTHREAD_NAME_LENGTH includes terminating null - buf[len(buf) - 1] = 0 + copy(buf[:len(buf) - 1], name) + when ODIN_OS == .Darwin { pthread_setname_np(raw_data(buf[:])) diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index b797c059c..4695a5b84 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -167,9 +167,13 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So } buf_16: win32.PWSTR - win32.GetThreadDescription(t_handle, &buf_16) + + hr := win32.GetThreadDescription(t_handle, buf_16) + defer if win32.SUCCEEDED(hr) { + win32.LocalFree(rawptr(buf_16)) + } + name = win32.wstring_to_utf8(buf_16, -1, allocator) or_return - win32.LocalFree(rawptr(buf_16)) return } From d83d47b19d5bed31d907e630fa1f8a00d1a5937c Mon Sep 17 00:00:00 2001 From: PePerRoNii Date: Wed, 25 Mar 2026 09:10:58 +0700 Subject: [PATCH 31/90] more sensible allocation and fix comment --- core/thread/thread.odin | 3 ++- core/thread/thread_unix.odin | 10 ++++------ core/thread/thread_windows.odin | 13 ++++++------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index 859b6597a..e5b4908e7 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -170,8 +170,9 @@ yield :: proc() { Get thread's name/description. This procedure returns the name of the given thread. If `thread` is `nil`, this procedure returns the name of the calling thread. +OS level errors are silently ignored. -**Note(linux, bsd)**: Because the thread name is stored in as the `cmdline`, if the thread name was not set, the command that has been used to create the process as the name of the thread. +**Note(linux, bsd)**: Because the thread name is stored in as the `cmdline`, if the thread name was not set, the command that has been used to create the process will be used as the name of the thread. allocates memory for the returned string using provided allocator. */ diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index dc1d13020..13b6fa264 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -206,17 +206,15 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So tid = thread.unix_thread } - buf := make([]u8, _MAX_PTHREAD_NAME_LENGTH, allocator, loc) or_return + buf : [_MAX_PTHREAD_NAME_LENGTH]u8 when ODIN_OS == .Darwin || ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { - pthread_getname_np(tid, raw_data(buf), len(buf)) + pthread_getname_np(tid, raw_data(buf[:]), len(buf)) } else when ODIN_OS == .OpenBSD { - pthread_get_name_np(tid, raw_data(buf), len(buf)) + pthread_get_name_np(tid, raw_data(buf[:]), len(buf)) } - name = transmute(string)buf - name = strings.truncate_to_byte(name, 0) - + name, err = strings.clone_from_cstring(cstring(raw_data(buf[:])), allocator, loc) return } diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 4695a5b84..0b5824f64 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -166,15 +166,14 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So t_handle = thread.win32_thread } - buf_16: win32.PWSTR + buf: win32.PWSTR - hr := win32.GetThreadDescription(t_handle, buf_16) - defer if win32.SUCCEEDED(hr) { - win32.LocalFree(rawptr(buf_16)) + hr := win32.GetThreadDescription(t_handle, &buf) + + if win32.SUCCEEDED(hr) { + defer win32.LocalFree(rawptr(buf)) + name, err = win32.wstring_to_utf8(buf, -1, allocator) } - - name = win32.wstring_to_utf8(buf_16, -1, allocator) or_return - return } From 0b5d3b0dd849450f800995fa5c7d2ac0f77ae9f2 Mon Sep 17 00:00:00 2001 From: Wachiraphol Yingamphol Date: Mon, 6 Apr 2026 10:01:34 +0700 Subject: [PATCH 32/90] nil as default thread arg --- core/thread/thread.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index e5b4908e7..a81fb3fba 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -176,7 +176,7 @@ OS level errors are silently ignored. allocates memory for the returned string using provided allocator. */ -get_name :: proc(thread: ^Thread, allocator := context.temp_allocator, loc := #caller_location) -> (string, runtime.Allocator_Error) { +get_name :: proc(thread: ^Thread = nil, allocator := context.temp_allocator, loc := #caller_location) -> (string, runtime.Allocator_Error) { return _get_name(thread, allocator, loc) } From 99442fa39034d5a3b4e10464e0284ac6f169371b Mon Sep 17 00:00:00 2001 From: Wachiraphol Yingamphol Date: Mon, 6 Apr 2026 15:06:02 +0700 Subject: [PATCH 33/90] add proper test for thread names --- tests/core/thread/test_core_thread.odin | 38 ++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/tests/core/thread/test_core_thread.odin b/tests/core/thread/test_core_thread.odin index 0b77ad511..87c3be942 100644 --- a/tests/core/thread/test_core_thread.odin +++ b/tests/core/thread/test_core_thread.odin @@ -49,4 +49,40 @@ poly_data_test :: proc(_t: ^testing.T) { defer free(t4) thread.join_multiple(t1, t2, t3, t4) -} \ No newline at end of file +} + +@(test) +name_test :: proc(_t: ^testing.T) { + @static name_test_t: ^testing.T + @static main_wait := true + @static child_wait := true + name_test_t = _t + + t := thread.create_and_start(name = "test_name", fn = proc() { + main_wait = false + + for (child_wait) {} + + n, err := thread.get_name() + defer if err != nil { + delete(n) + } + testing.expect(name_test_t, err == nil, "thread name allocation failed") + testing.expect(name_test_t, n == "test_name","thread name on self did not match") + + }) + defer free(t) + + for (main_wait) {} + + n, err := thread.get_name(t) + defer if err != nil { + delete(n) + } + testing.expect(name_test_t, err == nil, "thread name allocation failed") + testing.expect(name_test_t, n == "test_name","thread name on main did not match") + + child_wait = false + + thread.join(t) +} From ece54a4d05ba00ec482c1db0a24eb99d622cef4d Mon Sep 17 00:00:00 2001 From: "WP. Yingamphol" Date: Tue, 7 Apr 2026 12:56:42 +0700 Subject: [PATCH 34/90] moved Haiku OS check inside _set_name --- core/thread/thread_unix.odin | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 13b6fa264..2720808fa 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -51,9 +51,7 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority, name: Maybe(s runtime.run_thread_local_cleaners() } - when ODIN_OS != .Haiku { - _set_name(t) - } + _set_name(t) t.procedure(t) } @@ -219,6 +217,10 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So } _set_name :: proc(thread: ^Thread) { + when ODIN_OS == .Haiku { + return + } + name, ok := thread.name.? if !ok { return From ecd8b89daf7a4cf6912cc89327127f7e73896e2f Mon Sep 17 00:00:00 2001 From: Pung Date: Fri, 1 May 2026 13:12:48 +0700 Subject: [PATCH 35/90] better error message --- tests/core/thread/test_core_thread.odin | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/core/thread/test_core_thread.odin b/tests/core/thread/test_core_thread.odin index 87c3be942..0c7d956e4 100644 --- a/tests/core/thread/test_core_thread.odin +++ b/tests/core/thread/test_core_thread.odin @@ -3,6 +3,7 @@ package test_core_thread import "core:testing" import "core:thread" import "base:intrinsics" +import "core:fmt" @(test) poly_data_test :: proc(_t: ^testing.T) { @@ -68,7 +69,7 @@ name_test :: proc(_t: ^testing.T) { delete(n) } testing.expect(name_test_t, err == nil, "thread name allocation failed") - testing.expect(name_test_t, n == "test_name","thread name on self did not match") + testing.expectf(name_test_t, n == "test_name","thread name on self did not match : got %v", n) }) defer free(t) @@ -80,7 +81,7 @@ name_test :: proc(_t: ^testing.T) { delete(n) } testing.expect(name_test_t, err == nil, "thread name allocation failed") - testing.expect(name_test_t, n == "test_name","thread name on main did not match") + testing.expectf(name_test_t, n == "test_name","thread name on main did not match : got %v", n) child_wait = false From 7005cf7cf05a41cd68208ad4829d645f6012f2cc Mon Sep 17 00:00:00 2001 From: Pung Date: Fri, 1 May 2026 13:13:23 +0700 Subject: [PATCH 36/90] update OS limit to its proper value --- core/thread/thread.odin | 27 ++++++++++++++------------- core/thread/thread_name_darwin.odin | 2 ++ core/thread/thread_name_freebsd.odin | 2 ++ core/thread/thread_name_linux.odin | 2 ++ core/thread/thread_name_netbsd.odin | 2 ++ core/thread/thread_name_openbsd.odin | 2 ++ core/thread/thread_unix.odin | 8 +++----- core/thread/thread_windows.odin | 9 ++++++--- 8 files changed, 33 insertions(+), 21 deletions(-) diff --git a/core/thread/thread.odin b/core/thread/thread.odin index a81fb3fba..14e266b20 100644 --- a/core/thread/thread.odin +++ b/core/thread/thread.odin @@ -53,6 +53,7 @@ Thread :: struct { // it is started. data: rawptr, // Thread's Name/Description that will get set during thread creation + // it will be set using init_context's allocator to allocate and free a cstring buffer // for thread's creation only : do not refer to it, use thread.get_name instead name: Maybe(string), // User-supplied integer, that will be available to the thread once it is @@ -110,7 +111,7 @@ To start the thread, call `start()`. Also the `create_and_start()` procedure can be called to create and start the thread immediately. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. +the name/description will be truncated to fit the OS's limit. */ create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal, name: Maybe(string) = nil) -> ^Thread { @@ -188,7 +189,7 @@ specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. +the name/description will be truncated to fit the OS's limit. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` @@ -206,7 +207,7 @@ specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. +the name/description will be truncated to fit the OS's limit. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` @@ -224,7 +225,7 @@ specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. +the name/description will be truncated to fit the OS's limit. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` @@ -243,7 +244,7 @@ specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. +the name/description will be truncated to fit the OS's limit. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` @@ -262,7 +263,7 @@ specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. +the name/description will be truncated to fit the OS's limit. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` @@ -281,7 +282,7 @@ specified by `init_context` will be used as the context in which `fn` is going to execute. The thread will have priority specified by the `priority` parameter. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. +the name/description will be truncated to fit the OS's limit. **IMPORTANT**: If `init_context` is specified and the default temporary allocator is used, the thread procedure needs to call `runtime.default_temp_allocator_destroy()` @@ -304,7 +305,7 @@ If `self_cleanup` is specified, after the thread finishes the execution of the automatically freed. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. +the name/description will be truncated to fit the OS's limit. **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -342,7 +343,7 @@ If `self_cleanup` is specified, after the thread finishes the execution of the automatically freed. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. +the name/description will be truncated to fit the OS's limit. **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -384,7 +385,7 @@ If `self_cleanup` is specified, after the thread finishes the execution of the automatically freed. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. +the name/description will be truncated to fit the OS's limit. **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -432,7 +433,7 @@ If `self_cleanup` is specified, after the thread finishes the execution of the automatically freed. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. +the name/description will be truncated to fit the OS's limit. **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -486,7 +487,7 @@ If `self_cleanup` is specified, after the thread finishes the execution of the automatically freed. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. +the name/description will be truncated to fit the OS's limit. **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. @@ -542,7 +543,7 @@ If `self_cleanup` is specified, after the thread finishes the execution of the automatically freed. Optionally specify the thread's name/description. -the name/description will be truncated to 15 bytes on Unix and 127 bytes on Windows. +the name/description will be truncated to fit the OS's limit. **Do not** dereference the `^Thread` pointer, if this flag is specified. That includes calling `join`, which needs to dereference ^Thread`. diff --git a/core/thread/thread_name_darwin.odin b/core/thread/thread_name_darwin.odin index 866f608a3..7a01e0371 100644 --- a/core/thread/thread_name_darwin.odin +++ b/core/thread/thread_name_darwin.odin @@ -5,6 +5,8 @@ package thread import "core:sys/posix" import "core:c" +_MAX_PTHREAD_NAME_LENGTH :: 64 + foreign import pthread "system:System.framework" foreign pthread { diff --git a/core/thread/thread_name_freebsd.odin b/core/thread/thread_name_freebsd.odin index 9a36a54d2..5b79728a2 100644 --- a/core/thread/thread_name_freebsd.odin +++ b/core/thread/thread_name_freebsd.odin @@ -5,6 +5,8 @@ package thread import "core:sys/posix" import "core:c" +_MAX_PTHREAD_NAME_LENGTH :: 20 + foreign import pthread "system:pthread" foreign pthread { diff --git a/core/thread/thread_name_linux.odin b/core/thread/thread_name_linux.odin index 67d72e5c9..b84bf999c 100644 --- a/core/thread/thread_name_linux.odin +++ b/core/thread/thread_name_linux.odin @@ -5,6 +5,8 @@ package thread import "core:sys/posix" import "core:c" +_MAX_PTHREAD_NAME_LENGTH :: 16 + foreign import pthread "system:pthread" foreign pthread { diff --git a/core/thread/thread_name_netbsd.odin b/core/thread/thread_name_netbsd.odin index 12f31f760..c62c8c8f4 100644 --- a/core/thread/thread_name_netbsd.odin +++ b/core/thread/thread_name_netbsd.odin @@ -5,6 +5,8 @@ package thread import "core:sys/posix" import "core:c" +_MAX_PTHREAD_NAME_LENGTH :: 32 + foreign import pthread "system:pthread" foreign pthread { diff --git a/core/thread/thread_name_openbsd.odin b/core/thread/thread_name_openbsd.odin index e91970649..b349c2566 100644 --- a/core/thread/thread_name_openbsd.odin +++ b/core/thread/thread_name_openbsd.odin @@ -7,6 +7,8 @@ import "core:c" foreign import pthread "system:pthread" +_MAX_PTHREAD_NAME_LENGTH :: 20 + foreign pthread { pthread_get_name_np :: proc(thread: posix.pthread_t, name: [^]u8, len: c.size_t) --- pthread_set_name_np :: proc(thread: posix.pthread_t, name: [^]u8) --- diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 2720808fa..0ab54cea2 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -8,7 +8,6 @@ import "core:sys/posix" import "core:strings" _IS_SUPPORTED :: true -_MAX_PTHREAD_NAME_LENGTH :: 16 // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. // Also see core/sys/darwin/mach_darwin.odin/semaphore_t. Thread_Os_Specific :: struct #align(16) { @@ -230,12 +229,12 @@ _set_name :: proc(thread: ^Thread) { tid := thread.unix_thread } - buf: [_MAX_PTHREAD_NAME_LENGTH]u8 - // _MAX_PTHREAD_NAME_LENGTH includes terminating null + buflen := len(name) + 1 < _MAX_PTHREAD_NAME_LENGTH ? len(name) + 1 : _MAX_PTHREAD_NAME_LENGTH + buf := make([]u8, buflen) + defer delete(buf) copy(buf[:len(buf) - 1], name) - when ODIN_OS == .Darwin { pthread_setname_np(raw_data(buf[:])) } else when ODIN_OS == .OpenBSD { @@ -245,5 +244,4 @@ _set_name :: proc(thread: ^Thread) { } else { pthread_setname_np(tid, raw_data(buf[:])) } - } diff --git a/core/thread/thread_windows.odin b/core/thread/thread_windows.odin index 0b5824f64..f13d550f7 100644 --- a/core/thread/thread_windows.odin +++ b/core/thread/thread_windows.odin @@ -9,8 +9,9 @@ import win32 "core:sys/windows" import "core:unicode/utf16" _IS_SUPPORTED :: true -//NOTE(peperronii): not sure about the exact length but there must be a limit -_THREAD_DESCRIPTION_LENGTH :: 128 + +//NOTE(peperronii): this is the system limit for windows api call, not specific to thread description +_THREAD_DESCRIPTION_LENGTH :: 32_767 Thread_Os_Specific :: struct { win32_thread: win32.HANDLE, @@ -185,8 +186,10 @@ _set_name :: proc(thread: ^Thread) { t_handle := thread.win32_thread - buf: [_THREAD_DESCRIPTION_LENGTH]u16 // _THREAD_DESCRIPTION_LENGTH includes terminating null + buflen := len(name) + 1 < _THREAD_DESCRIPTION_LENGTH ? len(name) + 1 : _THREAD_DESCRIPTION_LENGTH + buf := make([]u16, buflen) + defer delete(buf) utf16.encode_string(buf[:len(buf) - 1], name) win32.SetThreadDescription(t_handle, cstring16(raw_data(buf[:]))) } From 6a01e3fbf97dde95c33ada21b2e04e57b690d533 Mon Sep 17 00:00:00 2001 From: Pung Date: Fri, 1 May 2026 13:41:13 +0700 Subject: [PATCH 37/90] removed unused fmt --- tests/core/thread/test_core_thread.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/core/thread/test_core_thread.odin b/tests/core/thread/test_core_thread.odin index 0c7d956e4..e06fcc3cf 100644 --- a/tests/core/thread/test_core_thread.odin +++ b/tests/core/thread/test_core_thread.odin @@ -3,7 +3,6 @@ package test_core_thread import "core:testing" import "core:thread" import "base:intrinsics" -import "core:fmt" @(test) poly_data_test :: proc(_t: ^testing.T) { From 2e5af1a40c36b559036d631bdeeac7622b337db6 Mon Sep 17 00:00:00 2001 From: peperronii Date: Fri, 17 Jul 2026 23:17:23 +0700 Subject: [PATCH 38/90] Removed Haiku --- core/thread/thread_unix.odin | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index 70a534db5..fef0db9f8 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -191,11 +191,6 @@ _yield :: proc() { } _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.Source_Code_Location) -> (name: string, err: runtime.Allocator_Error) { - // Haiku doesn't have pthread_getname yet - when ODIN_OS == .Haiku { - unimplemented("core:thread get_name for haiku is not yet supported") - } - tid: posix.pthread_t if thread == nil { tid = posix.pthread_self() @@ -205,10 +200,10 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So buf : [_MAX_PTHREAD_NAME_LENGTH]u8 - when ODIN_OS == .Darwin || ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .NetBSD { - pthread_getname_np(tid, raw_data(buf[:]), len(buf)) - } else when ODIN_OS == .OpenBSD { + when ODIN_OS == .OpenBSD { pthread_get_name_np(tid, raw_data(buf[:]), len(buf)) + } else { + pthread_getname_np(tid, raw_data(buf[:]), len(buf)) } name, err = strings.clone_from_cstring(cstring(raw_data(buf[:])), allocator, loc) @@ -216,10 +211,6 @@ _get_name :: proc(thread: ^Thread, allocator: runtime.Allocator, loc: runtime.So } _set_name :: proc(thread: ^Thread) { - when ODIN_OS == .Haiku { - return - } - name, ok := thread.name.? if !ok { return From cf2d4000bd877ed296c59c9464f1ad4a4b8542a8 Mon Sep 17 00:00:00 2001 From: John Werner Date: Thu, 23 Jul 2026 21:32:22 -0700 Subject: [PATCH 39/90] Allow #caller-expression on constant parameters which are procedures --- src/check_builtin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index e87c75e88..a41448973 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -2447,7 +2447,7 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o } else { Operand o = {}; Entity *e = check_ident(c, &o, arg, nullptr, nullptr, true); - if (e == nullptr || (e->flags & EntityFlag_Param) == 0) { + if (e == nullptr || (e->kind != Entity_Procedure && (e->flags & EntityFlag_Param) == 0)) { error(arg, "'#caller_expression' expected a valid earlier parameter name"); } arg->Ident.entity = e; From 853134048e73a799bc92503f74b10531e2840b8a Mon Sep 17 00:00:00 2001 From: Chaosus Date: Sat, 25 Jul 2026 17:35:32 +0300 Subject: [PATCH 40/90] Fix error hint for vet tag --- src/parser.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/parser.cpp b/src/parser.cpp index 64112e085..e2879ee9e 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6586,7 +6586,8 @@ gb_internal u64 parse_vet_tag(Token token_for_pos, String s, u64 base_vet_flags) error_line("\tusing-stmt\n"); error_line("\tusing-param\n"); error_line("\tstyle\n"); - error_line("\textra\n"); + error_line("\tsemicolon\n"); + error_line("\tdeprecated\n"); error_line("\tcast\n"); error_line("\ttabs\n"); error_line("\texplicit-allocators\n"); From c1e327c5662b0e752c0a404f447848ca438743da Mon Sep 17 00:00:00 2001 From: bingis-khan <52740618+bingis-khan@users.noreply.github.com> Date: Sun, 26 Jul 2026 11:11:36 +0200 Subject: [PATCH 41/90] Fix LLVM codegen for matching on `nil` for `union{}` --- src/llvm_backend_stmt.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 54447820a..1d42b497a 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -2201,7 +2201,9 @@ gb_internal void lb_build_type_switch_stmt(lbProcedure *p, AstTypeSwitchStmt *ss LLVMValueRef switch_instr = nullptr; if (type_size_of(parent_base_type) == 0) { GB_ASSERT(tag.value == nullptr); - switch_instr = LLVMBuildSwitch(p->builder, lb_const_bool(p->module, t_llvm_bool, false).value, else_block->block, cast(unsigned)num_cases); + GB_ASSERT(switch_kind == TypeSwitch_Union); // zero size only possible for union{} + Type *ut = type_deref(parent.type); + switch_instr = LLVMBuildSwitch(p->builder, lb_const_int(m, union_tag_type(ut), 0).value, else_block->block, cast(unsigned)num_cases); } else { GB_ASSERT(tag.value != nullptr); switch_instr = LLVMBuildSwitch(p->builder, tag.value, else_block->block, cast(unsigned)num_cases); From 33247c8ca9719919516e90f4b146e1a9960a4e17 Mon Sep 17 00:00:00 2001 From: Chaosus Date: Sun, 26 Jul 2026 19:49:57 +0300 Subject: [PATCH 42/90] Bring back "extra" error line --- src/parser.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/parser.cpp b/src/parser.cpp index e2879ee9e..a527bc7ec 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6586,6 +6586,7 @@ gb_internal u64 parse_vet_tag(Token token_for_pos, String s, u64 base_vet_flags) error_line("\tusing-stmt\n"); error_line("\tusing-param\n"); error_line("\tstyle\n"); + error_line("\textra\n"); error_line("\tsemicolon\n"); error_line("\tdeprecated\n"); error_line("\tcast\n"); From 75335afd042484da1e6bdabedd7cdb00da454205 Mon Sep 17 00:00:00 2001 From: corley <28909106+corleypc@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:00:29 +0300 Subject: [PATCH 43/90] #no_alias added to the array parameter of dynamic array builtins --- base/runtime/core_builtin.odin | 74 +++++++++++++++++----------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index e65cf56f0..4913b53d9 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -128,7 +128,7 @@ copy :: proc{copy_slice, copy_from_string, copy_from_string16} // Note: If you want the elements to remain in their order, use `ordered_remove`. // Note: If the index is out of bounds, this procedure will panic. @builtin -unordered_remove_dynamic_array :: proc(array: ^$D/[dynamic]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { +unordered_remove_dynamic_array :: proc(#no_alias array: ^$D/[dynamic]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) n := len(array)-1 if index != n { @@ -142,7 +142,7 @@ unordered_remove_dynamic_array :: proc(array: ^$D/[dynamic]$T, #any_int index: i // Note: If the elements do not have to remain in their order, prefer `unordered_remove`. // Note: If the index is out of bounds, this procedure will panic. @builtin -ordered_remove_dynamic_array :: proc(array: ^$D/[dynamic]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { +ordered_remove_dynamic_array :: proc(#no_alias array: ^$D/[dynamic]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) if index+1 < len(array) { copy(array[index:], array[index+1:]) @@ -155,7 +155,7 @@ ordered_remove_dynamic_array :: proc(array: ^$D/[dynamic]$T, #any_int index: int // Note: This is an O(N) operation. // Note: If the range is out of bounds, this procedure will panic. @builtin -remove_range_dynamic_array :: proc(array: ^$D/[dynamic]$T, #any_int lo, hi: int, loc := #caller_location) #no_bounds_check { +remove_range_dynamic_array :: proc(#no_alias array: ^$D/[dynamic]$T, #any_int lo, hi: int, loc := #caller_location) #no_bounds_check { slice_expr_error_lo_hi_loc(loc, lo, hi, len(array)) n := max(hi-lo, 0) if n > 0 { @@ -236,13 +236,13 @@ remove_range :: proc{ // // Note: If the dynamic array has no elements (`len(array) == 0`), this procedure will panic. @builtin -pop_dynamic_array :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { +pop_dynamic_array :: proc(#no_alias array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { assert(len(array) > 0, loc=loc) _pop_dynamic_array_type_erased(&res, (^Raw_Dynamic_Array)(array), size_of(E)) return res } -_pop_dynamic_array_type_erased :: proc(res: rawptr, array: ^Raw_Dynamic_Array, elem_size: int) { +_pop_dynamic_array_type_erased :: proc(res: rawptr, #no_alias array: ^Raw_Dynamic_Array, elem_size: int) { end := rawptr(uintptr(array.data) + uintptr(elem_size*(array.len-1))) intrinsics.mem_copy_non_overlapping(res, end, elem_size) array.len -= 1 @@ -276,7 +276,7 @@ pop :: proc{ // `pop_safe_dynamic_array` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. // If the operation is not possible, it will return false. @builtin -pop_safe_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { +pop_safe_dynamic_array :: proc "contextless" (#no_alias array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { if len(array) == 0 { return } @@ -310,7 +310,7 @@ pop_safe :: proc{ // // Note: If the dynamic array as no elements (`len(array) == 0`), this procedure will panic. @builtin -pop_front_dynamic_array :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { +pop_front_dynamic_array :: proc(#no_alias array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { assert(len(array) > 0, loc=loc) res = array[0] if len(array) > 1 { @@ -348,7 +348,7 @@ pop_front :: proc{ // `pop_front_safe_dynamic_array` trys to return and remove the first value of dynamic array `array` and reduces the length of `array` by 1. // If the operation is not possible, it will return false. @builtin -pop_front_safe_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { +pop_front_safe_dynamic_array :: proc "contextless" (#no_alias array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { if len(array) == 0 { return } @@ -590,7 +590,7 @@ make_dynamic_array_len_cap :: proc($T: typeid/[dynamic]$E, #any_int len: int, #a } @(require_results) -_make_dynamic_array_len_cap :: proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (err: Allocator_Error) { +_make_dynamic_array_len_cap :: proc(#no_alias array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, #any_int len: int, #any_int cap: int, allocator := context.allocator, loc := #caller_location) -> (err: Allocator_Error) { make_dynamic_array_error_loc(loc, len, cap) array.allocator = allocator // initialize allocator before just in case it fails to allocate any memory data := mem_alloc_bytes(size_of_elem*cap, align_of_elem, allocator, loc) or_return @@ -716,7 +716,7 @@ when MAP_ENABLED { } } -_append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, arg_ptr: rawptr, should_zero: bool, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { +_append_elem :: #force_no_inline proc(#no_alias array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, arg_ptr: rawptr, should_zero: bool, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { if array == nil { return } @@ -739,7 +739,7 @@ _append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, a return } -_append_elem_ptr :: #force_no_inline proc(array: ^Raw_Dynamic_Array, arg: rawptr, should_zero: bool, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { +_append_elem_ptr :: #force_no_inline proc(#no_alias array: ^Raw_Dynamic_Array, arg: rawptr, should_zero: bool, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { if array == nil { return } @@ -764,7 +764,7 @@ _append_elem_ptr :: #force_no_inline proc(array: ^Raw_Dynamic_Array, arg: rawptr // `append_elem` appends an element to the end of a dynamic array. @builtin -append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { +append_elem :: proc(#no_alias array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { when size_of(E) == 0 { if array == nil { return @@ -806,7 +806,7 @@ append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller // // Note: Prefer using the procedure group `non_zero_append @builtin -non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { +non_zero_append_elem :: proc(#no_alias array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { when size_of(E) == 0 { (^Raw_Dynamic_Array)(array).len += 1 return 1, nil @@ -841,7 +841,7 @@ non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc : } } -_append_elems :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, should_zero: bool, loc := #caller_location, args: rawptr, arg_len: int) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { +_append_elems :: #force_no_inline proc(#no_alias array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, should_zero: bool, loc := #caller_location, args: rawptr, arg_len: int) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { if array == nil { return 0, nil } @@ -872,7 +872,7 @@ _append_elems :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, // // Note: Prefer using the procedure group `append`. @builtin -append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { +append_elems :: proc(#no_alias array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { when size_of(E) == 0 { a := (^Raw_Dynamic_Array)(array) a.len += len(args) @@ -886,7 +886,7 @@ append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #ca // // Note: Prefer using the procedure group `non_zero_append @builtin -non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { +non_zero_append_elems :: proc(#no_alias array: ^$T/[dynamic]$E, #no_broadcast args: ..E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { when size_of(E) == 0 { a := (^Raw_Dynamic_Array)(array) a.len += len(args) @@ -897,7 +897,7 @@ non_zero_append_elems :: proc(array: ^$T/[dynamic]$E, #no_broadcast args: ..E, l } // The append_string built-in procedure appends a string to the end of a [dynamic]u8 like type -_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { +_append_elem_string :: proc(#no_alias array: ^$T/[dynamic]$E/u8, arg: $A/string, should_zero: bool, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { return _append_elems((^Raw_Dynamic_Array)(array), 1, 1, should_zero, loc, raw_data(arg), len(arg)) } @@ -905,14 +905,14 @@ _append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, should_ze // // Note: Prefer using the procedure group `append`. @builtin -append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { +append_elem_string :: proc(#no_alias array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { return _append_elem_string(array, arg, true, loc) } // `non_zero_append_elem_string` appends a string to the end of a dynamic array of bytes, without zeroing any reserved memory // // Note: Prefer using the procedure group `non_zero_append`. @builtin -non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { +non_zero_append_elem_string :: proc(#no_alias array: ^$T/[dynamic]$E/u8, arg: $A/string, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { return _append_elem_string(array, arg, false, loc) } @@ -930,7 +930,7 @@ non_zero_append_elem_fixed_capacity_string :: proc "contextless" (array: ^$T/[dy // // Note: Prefer using the procedure group `append`. @builtin -append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { +append_string :: proc(#no_alias array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { n_arg: int for arg in args { n_arg, err = append(array, ..transmute([]E)(arg), loc=loc) @@ -1026,7 +1026,7 @@ non_zero_append :: proc{ // `append_nothing` appends an empty value to a dynamic array. It returns `1, nil` if successful, and `0, err` when it was not possible, // whatever `err` happens to be. @builtin -append_nothing_dynamic_array :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +append_nothing_dynamic_array :: proc(#no_alias array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { return 0, nil } @@ -1059,7 +1059,7 @@ append_nothing :: proc{ // `inject_at_elem` injects an element in a dynamic array at a specified index and moves the previous elements after that index "across" @builtin -inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +inject_at_elem :: proc(#no_alias array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { when !ODIN_NO_BOUNDS_CHECK { ensure(index >= 0, "Index must be positive.", loc) } @@ -1081,7 +1081,7 @@ inject_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcas // `inject_at_elems` injects multiple elements in a dynamic array at a specified index and moves the previous elements after that index "across" @builtin -inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +inject_at_elems :: proc(#no_alias array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { when !ODIN_NO_BOUNDS_CHECK { ensure(index >= 0, "Index must be positive.", loc) } @@ -1108,7 +1108,7 @@ inject_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadca // `inject_at_elem_string` injects a string into a dynamic array at a specified index and moves the previous elements after that index "across" @builtin -inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +inject_at_elem_string :: proc(#no_alias array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { when !ODIN_NO_BOUNDS_CHECK { ensure(index >= 0, "Index must be positive.", loc) } @@ -1219,7 +1219,7 @@ inject_at :: proc{ // `assign_at_elem` assigns a value at a given index. If the requested index is past the end of the current // size of the dynamic array, it will attempt to `resize` the a new length of `index+1` and then assign as `index`. @builtin -assign_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +assign_at_elem :: proc(#no_alias array: ^$T/[dynamic]$E, #any_int index: int, arg: E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { if index < len(array) { array[index] = arg ok = true @@ -1235,7 +1235,7 @@ assign_at_elem :: proc(array: ^$T/[dynamic]$E, #any_int index: int, arg: E, loc // `assign_at_elems` assigns a values at a given index. If the requested index is past the end of the current // size of the dynamic array, it will attempt to `resize` the a new length of `index+len(args)` and then assign as `index`. @builtin -assign_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +assign_at_elems :: proc(#no_alias array: ^$T/[dynamic]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { new_size := index + len(args) if len(args) == 0 { ok = true @@ -1253,7 +1253,7 @@ assign_at_elems :: proc(array: ^$T/[dynamic]$E, #any_int index: int, #no_broadca // `assign_at_elem_string` assigns a string at a given index. If the requested index is past the end of the current // size of the dynamic array, it will attempt to `resize` the a new length of `index+len(arg)` and then assign as `index`. @builtin -assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { +assign_at_elem_string :: proc(#no_alias array: ^$T/[dynamic]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool, err: Allocator_Error) #no_bounds_check #optional_allocator_error { new_size := index + len(arg) if len(arg) == 0 { ok = true @@ -1341,7 +1341,7 @@ assign_at :: proc{ // // Note: Prefer the procedure group `clear`. @builtin -clear_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) { +clear_dynamic_array :: proc "contextless" (#no_alias array: ^$T/[dynamic]$E) { if array != nil { (^Raw_Dynamic_Array)(array).len = 0 } @@ -1362,7 +1362,7 @@ clear_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic; $ // When a memory resize allocation is required, the memory will be asked to be zeroed (i.e. it calls `mem_resize`). // // Note: Prefer the procedure group `reserve`. -_reserve_dynamic_array :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { +_reserve_dynamic_array :: #force_no_inline proc(#no_alias a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { if a == nil { return nil } @@ -1395,7 +1395,7 @@ _reserve_dynamic_array :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_e return nil } -_reserve_dynamic_array_unsafe :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { +_reserve_dynamic_array_unsafe :: #force_no_inline proc(#no_alias a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { if capacity <= a.cap { return nil } @@ -1430,7 +1430,7 @@ _reserve_dynamic_array_unsafe :: #force_no_inline proc(a: ^Raw_Dynamic_Array, si // // Note: Prefer the procedure group `reserve`. @builtin -reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { +reserve_dynamic_array :: proc(#no_alias array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, true, loc) } @@ -1440,12 +1440,12 @@ reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, lo // // Note: Prefer the procedure group `non_zero_reserve`. @builtin -non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { +non_zero_reserve_dynamic_array :: proc(#no_alias array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { return _reserve_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), capacity, false, loc) } -_resize_dynamic_array :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { +_resize_dynamic_array :: #force_no_inline proc(#no_alias a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { if a == nil { return nil } @@ -1491,7 +1491,7 @@ _resize_dynamic_array :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_el // // Note: Prefer the procedure group `resize` @builtin -resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { +resize_dynamic_array :: proc(#no_alias array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, true, loc=loc) } @@ -1501,7 +1501,7 @@ resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc : // // Note: Prefer the procedure group `non_zero_resize` @builtin -non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { +non_zero_resize_dynamic_array :: proc(#no_alias array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { return _resize_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), length, false, loc=loc) } @@ -1552,11 +1552,11 @@ non_zero_resize_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[ // // Note: Prefer the procedure group `shrink` @builtin -shrink_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { +shrink_dynamic_array :: proc(#no_alias array: ^$T/[dynamic]$E, #any_int new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { return _shrink_dynamic_array((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), new_cap, loc) } -_shrink_dynamic_array :: proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { +_shrink_dynamic_array :: proc(#no_alias a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, new_cap := -1, loc := #caller_location) -> (did_shrink: bool, err: Allocator_Error) { if a == nil { return } From 17e23a8bcf70d7ac146154fc0bbb5c52310a35fd Mon Sep 17 00:00:00 2001 From: Sylphrena Date: Wed, 29 Jul 2026 14:28:09 +0200 Subject: [PATCH 44/90] Shrink map to minimum capacity with `shrink` --- base/runtime/dynamic_map_internal.odin | 35 ++++++++++++++---------- tests/internal/test_map.odin | 37 +++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/base/runtime/dynamic_map_internal.odin b/base/runtime/dynamic_map_internal.odin index 06509c1e8..56297c440 100644 --- a/base/runtime/dynamic_map_internal.odin +++ b/base/runtime/dynamic_map_internal.odin @@ -524,15 +524,6 @@ map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Inf @(require_results) map_reserve_dynamic :: #force_no_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uintptr, loc := #caller_location) -> Allocator_Error { - @(require_results) - ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr { - z := intrinsics.count_leading_zeros(x) - if z > 0 && x & (x-1) != 0 { - z -= 1 - } - return size_of(uintptr)*8 - 1 - z - } - if m.allocator.procedure == nil { m.allocator = context.allocator } @@ -545,7 +536,7 @@ map_reserve_dynamic :: #force_no_inline proc "odin" (#no_alias m: ^Raw_Map, #no_ } // ceiling nearest power of two - log2_new_capacity := ceil_log2(new_capacity) + log2_new_capacity := __ceil_log2(new_capacity) log2_min_cap := max(MAP_MIN_LOG2_CAPACITY, log2_new_capacity) @@ -592,19 +583,23 @@ map_shrink_dynamic :: #force_no_inline proc "odin" (#no_alias m: ^Raw_Map, #no_a m.allocator = context.allocator } - log2_capacity := map_log2_cap(m^) // Don't shrink below the minimum. + log2_capacity := map_log2_cap(m^) if log2_capacity <= MAP_MIN_LOG2_CAPACITY { return false, nil } + // Cannot shrink the capacity if the number of items in the map would exceed // one minus the current log2 capacity's resize threshold. That is the shrunk // map needs to be within the max load factor. - if uintptr(m.len) >= map_load_factor(log2_capacity - 1) { + load_factor := map_load_factor(log2_capacity - 1) + if m.len >= load_factor { return false, nil } - shrunk := map_alloc_dynamic(info, log2_capacity - 1, m.allocator) or_return + log2_capacity = max(__ceil_log2(m.len), MAP_MIN_LOG2_CAPACITY) + + shrunk := map_alloc_dynamic(info, log2_capacity, m.allocator) or_return capacity := uintptr(1) << log2_capacity @@ -914,7 +909,6 @@ __dynamic_map_entry :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_ return } - // IMPORTANT: USED WITHIN THE COMPILER @(private) __dynamic_map_reserve :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uint, loc := #caller_location) -> Allocator_Error { @@ -924,7 +918,20 @@ __dynamic_map_reserve :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Ma return map_reserve_dynamic(m, info, uintptr(new_capacity), loc) } +@(require_results, private) +__ceil_log2 :: #force_inline proc "contextless" (x: uintptr) -> uintptr { + // NOTE(barney): log2(0) is undefined, but 0 is a reasonable return value. + // Alternatively, 8 could be considered as well. + if x == 0 { + return 0 + } + z := intrinsics.count_leading_zeros(x) + if z > 0 && x & (x-1) != 0 { + z -= 1 + } + return size_of(uintptr)*8 - 1 - z +} // NOTE: the default hashing algorithm derives from fnv64a, with some minor modifications to work for `map` type: // diff --git a/tests/internal/test_map.odin b/tests/internal/test_map.odin index 4d305024e..52421d7fc 100644 --- a/tests/internal/test_map.odin +++ b/tests/internal/test_map.odin @@ -1,7 +1,7 @@ package test_internal import "core:log" -import "base:intrinsics" +import "base:runtime" import "core:math/rand" import "core:testing" @@ -201,6 +201,41 @@ map_delete_random_key_value :: proc(t: ^testing.T) { } } +@test +map_shrink :: proc(t: ^testing.T) { + m: map[int]int + defer delete(m) + + { + reserve(&m, 8) + did_shrink, err := shrink(&m) + testing.expect_value(t, did_shrink, false) + testing.expect_value(t, err, runtime.Allocator_Error.None) + testing.expect_value(t, cap(m), 8) + } + + { + reserve(&m, 64) + did_shrink, err := shrink(&m) + testing.expect_value(t, did_shrink, true) + testing.expect_value(t, err, runtime.Allocator_Error.None) + testing.expect_value(t, cap(m), 8) + } + + { + reserve(&m, 128) + + for i in 0 ..< 50 { + m[i] = i + } + + did_shrink, err := shrink(&m) + testing.expect_value(t, did_shrink, false) + testing.expect_value(t, err, runtime.Allocator_Error.None) + testing.expect_value(t, cap(m), 128) + } +} + @test set_insert_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) From 629676217f2a0daeff34e034b9a13992fe49ed1d Mon Sep 17 00:00:00 2001 From: mo Date: Thu, 30 Jul 2026 11:39:56 +1200 Subject: [PATCH 45/90] Respect TERM=dumb in test runner to prevent ANSI sequences, color Fixes: #7137 --- core/testing/runner.odin | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 9033d078e..ef5d03864 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -281,6 +281,15 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { global_log_colors_disabled = !terminal.color_enabled || !terminal.is_terminal(os.stderr) global_ansi_disabled = !terminal.is_terminal(os.stdout) + buf: [128]u8 + if term, err := os.lookup_env(buf[:], "TERM"); err == nil { + // "dumb" terminal overrides all other color and ansi capabilities logic + if term == "dumb" { + global_ansi_disabled = true + global_log_colors_disabled = true + } + } + should_show_animations := FANCY_OUTPUT && terminal.color_enabled && !global_ansi_disabled // -- Parse CLI options From 70507796430f3d48f2bebe3556dcfa351787ce6c Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 Jul 2026 11:46:06 +0200 Subject: [PATCH 46/90] Move TERM == "dumb" test to `core:terminal` --- core/terminal/internal_os.odin | 9 +++++++++ core/terminal/terminal.odin | 5 +++++ core/terminal/terminal_js.odin | 1 + core/terminal/terminal_posix.odin | 1 + core/terminal/terminal_windows.odin | 2 ++ core/testing/runner.odin | 14 ++------------ 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/core/terminal/internal_os.odin b/core/terminal/internal_os.odin index 127cbae54..dc466e7b0 100644 --- a/core/terminal/internal_os.odin +++ b/core/terminal/internal_os.odin @@ -21,6 +21,15 @@ get_no_color :: proc() -> bool { return false } +get_is_dumb :: proc() -> bool { + buf: [128]u8 + if term, err := os.lookup_env(buf[:], "TERM"); err == nil { + // "dumb" terminal overrides all other color and ansi capabilities logic + return term == "dumb" + } + return false +} + get_environment_color :: proc() -> Color_Depth { buf: [128]u8 // `COLORTERM` is non-standard but widespread and unambiguous. diff --git a/core/terminal/terminal.odin b/core/terminal/terminal.odin index 86d928649..0f882b296 100644 --- a/core/terminal/terminal.odin +++ b/core/terminal/terminal.odin @@ -23,6 +23,11 @@ is_terminal :: proc(f: $T) -> bool { return _is_terminal(f) } +/* +This is true if the terminal is dumb, i.e. cannot process ANSI control sequences. +*/ +is_dumb: bool + /* This is true if the terminal is accepting any form of colored text output. */ diff --git a/core/terminal/terminal_js.odin b/core/terminal/terminal_js.odin index 78c6c240f..aafb3223a 100644 --- a/core/terminal/terminal_js.odin +++ b/core/terminal/terminal_js.odin @@ -8,6 +8,7 @@ _is_terminal :: proc "contextless" (handle: any) -> bool { _init_terminal :: proc "contextless" () { color_depth = .None + is_dumb = true } _fini_terminal :: proc "contextless" () { } \ No newline at end of file diff --git a/core/terminal/terminal_posix.odin b/core/terminal/terminal_posix.odin index 62a79797b..3b6701722 100644 --- a/core/terminal/terminal_posix.odin +++ b/core/terminal/terminal_posix.odin @@ -12,6 +12,7 @@ _is_terminal :: proc "contextless" (f: ^os.File) -> bool { _init_terminal :: proc "contextless" () { context = runtime.default_context() color_depth = get_environment_color() + is_dumb = get_is_dumb() } _fini_terminal :: proc "contextless" () { } diff --git a/core/terminal/terminal_windows.odin b/core/terminal/terminal_windows.odin index 64244e1ac..f5d19e3b3 100644 --- a/core/terminal/terminal_windows.odin +++ b/core/terminal/terminal_windows.odin @@ -46,6 +46,8 @@ _init_terminal :: proc "contextless" () { // The user may be on a non-default terminal emulator. color_depth = get_environment_color() } + + is_dumb = get_is_dumb() } _fini_terminal :: proc "contextless" () { diff --git a/core/testing/runner.odin b/core/testing/runner.odin index ef5d03864..e298cf47c 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -278,18 +278,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { // The animations are only ever shown through STDOUT; // STDERR is used exclusively for logging regardless of error level. - global_log_colors_disabled = !terminal.color_enabled || !terminal.is_terminal(os.stderr) - global_ansi_disabled = !terminal.is_terminal(os.stdout) - - buf: [128]u8 - if term, err := os.lookup_env(buf[:], "TERM"); err == nil { - // "dumb" terminal overrides all other color and ansi capabilities logic - if term == "dumb" { - global_ansi_disabled = true - global_log_colors_disabled = true - } - } - + global_log_colors_disabled = terminal.is_dumb || !terminal.color_enabled || !terminal.is_terminal(os.stderr) + global_ansi_disabled = terminal.is_dumb || !terminal.is_terminal(os.stdout) should_show_animations := FANCY_OUTPUT && terminal.color_enabled && !global_ansi_disabled // -- Parse CLI options From d272bfb9cac5f581dd77ad7c40ec7e3b46c59452 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 Jul 2026 12:03:08 +0200 Subject: [PATCH 47/90] context --- core/terminal/internal_os.odin | 4 ++-- core/terminal/terminal_windows.odin | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/terminal/internal_os.odin b/core/terminal/internal_os.odin index dc466e7b0..f4be4480b 100644 --- a/core/terminal/internal_os.odin +++ b/core/terminal/internal_os.odin @@ -72,10 +72,10 @@ get_environment_color :: proc() -> Color_Depth { @(init) init_terminal :: proc "contextless" () { - _init_terminal() - context = runtime.default_context() + _init_terminal() + // We respect `NO_COLOR` specifically as a color-disabler but not as a // blanket ban on any terminal manipulation codes, hence why this comes // after `_init_terminal` which will allow Windows to enable Virtual diff --git a/core/terminal/terminal_windows.odin b/core/terminal/terminal_windows.odin index f5d19e3b3..aff71b362 100644 --- a/core/terminal/terminal_windows.odin +++ b/core/terminal/terminal_windows.odin @@ -18,6 +18,8 @@ old_modes: [2]struct{ } _init_terminal :: proc "contextless" () { + context = runtime.default_context() + vtp_enabled: bool for &v in old_modes { @@ -41,8 +43,6 @@ _init_terminal :: proc "contextless" () { // This color depth is available on Windows 10 since build 10586. color_depth = .Four_Bit } else { - context = runtime.default_context() - // The user may be on a non-default terminal emulator. color_depth = get_environment_color() } From 894d44346e31ec43f2bd473e35a8453d325e3edc Mon Sep 17 00:00:00 2001 From: David Bader <65665893+BunterSchatten@users.noreply.github.com> Date: Thu, 30 Jul 2026 09:10:27 +0200 Subject: [PATCH 48/90] run_args_start_idx is identical to double_dash_pos, no need to check again --- src/main.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index c4b4441d8..ddfa2e3ff 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3811,23 +3811,16 @@ int main(int arg_count, char const **arg_ptr) { build_context.command_kind = Command_test; } - isize run_args_start_idx = -1; - for_array(i, args) { - if (args[i] == "--") { - run_args_start_idx = i; - break; - } - } - if (run_args_start_idx != -1) { - last_non_run_arg = run_args_start_idx; + if (double_dash_pos != -1) { + last_non_run_arg = double_dash_pos; - if (run_args_start_idx == 2) { + if (double_dash_pos == 2) { // missing src path on argv[2], invocation: odin [run|test] -- usage(args[0]); return 1; } - for(isize i = run_args_start_idx+1; i < args.count; ++i) { + for(isize i = double_dash_pos+1; i < args.count; ++i) { array_add(&run_args, args[i]); } } From 16ee03828bc345b4c45239ca93ffce2a6a9636d5 Mon Sep 17 00:00:00 2001 From: David Bader <65665893+BunterSchatten@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:31:20 +0200 Subject: [PATCH 49/90] handle spaces in args for 'odin run . -- ' on Windows --- src/common.cpp | 27 +++++++++++++++++--- src/main.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 84 insertions(+), 12 deletions(-) diff --git a/src/common.cpp b/src/common.cpp index 89964309b..133b1888a 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -640,7 +640,7 @@ gb_internal gb_inline f64 gb_sqrt(f64 x) { #if defined(GB_SYSTEM_WINDOWS) -gb_internal wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc) { +gb_internal wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc, wchar_t **_after_double_dash_raw) { u32 i, j; u32 len = cast(u32)string16_len(cast(u16 *)cmd_line); @@ -649,6 +649,8 @@ gb_internal wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc) { wchar_t **argv = cast(wchar_t **)GlobalAlloc(GMEM_FIXED, i + (len+2)*gb_size_of(wchar_t)); wchar_t *_argv = cast(wchar_t *)((cast(u8 *)argv)+i); + wchar_t *after_double_dash_raw = nullptr; + u32 argc = 0; argv[argc] = _argv; bool in_quote = false; @@ -657,6 +659,17 @@ gb_internal wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc) { i = 0; j = 0; + auto const check_double_dash = [&]() { + if (!after_double_dash_raw && + argc >= 1 && + argv[argc - 1][0] == '-' && + argv[argc - 1][1] == '-' && + argv[argc - 1][2] == '\0') { + + after_double_dash_raw = cmd_line + i; + } + }; + for (;;) { wchar_t a = cmd_line[i]; if (a == 0) { @@ -673,7 +686,10 @@ gb_internal wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc) { case '\"': in_quote = true; in_text = true; - if (in_space) argv[argc++] = _argv+j; + if (in_space) { + check_double_dash(); + argv[argc++] = _argv + j; + } in_space = false; break; case ' ': @@ -686,7 +702,10 @@ gb_internal wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc) { break; default: in_text = true; - if (in_space) argv[argc++] = _argv+j; + if (in_space) { + check_double_dash(); + argv[argc++] = _argv + j; + } _argv[j++] = a; in_space = false; break; @@ -696,8 +715,10 @@ gb_internal wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc) { } _argv[j] = '\0'; argv[argc] = nullptr; + check_double_dash(); if (_argc) *_argc = argc; + if (_after_double_dash_raw) *_after_double_dash_raw = after_double_dash_raw; return argv; } diff --git a/src/main.cpp b/src/main.cpp index ddfa2e3ff..a176ace00 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -178,17 +178,63 @@ gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, return exit_code; } -#if defined(GB_SYSTEM_WINDOWS) -#include -#else +#if !defined(GB_SYSTEM_WINDOWS) #include extern char **environ; #endif -int run_subprocess(const char *name, const char **args) { #if defined(GB_SYSTEM_WINDOWS) - return (int)_spawnv(_P_WAIT, name, args); +int run_subprocess(String const &exe_name, wchar_t *after_double_dash_raw) { + gbAllocator a = heap_allocator(); + + String16 wexe_name = string_to_string16(a, exe_name); + defer (gb_free(a, wexe_name.text)); + + isize args_len = 0; + if (after_double_dash_raw) { + args_len = string16_len(cast(u16 *)after_double_dash_raw); + } + + isize cmd_len = wexe_name.len + 2; + if (args_len > 0) cmd_len += args_len + 1; + + wchar_t *cmd_line = gb_alloc_array(a, wchar_t, cmd_len + 1); + defer (gb_free(a, cmd_line)); + + isize n = 0; + cmd_line[n++] = '"'; + gb_memmove(cmd_line + n, wexe_name.text, wexe_name.len * gb_size_of(wchar_t)); + n += wexe_name.len; + cmd_line[n++] = '"'; + if (args_len > 0) { + cmd_line[n++] = ' '; + gb_memmove(cmd_line + n, after_double_dash_raw, args_len * gb_size_of(wchar_t)); + n += args_len; + } + cmd_line[n] = '\0'; + + STARTUPINFOW start_info = {gb_size_of(STARTUPINFOW)}; + PROCESS_INFORMATION pi = {0}; + int exit_code = 0; + + if (CreateProcessW(nullptr, cmd_line, + nullptr, nullptr, true, 0, nullptr, nullptr, + &start_info, &pi)) { + WaitForSingleObject(pi.hProcess, INFINITE); + GetExitCodeProcess(pi.hProcess, cast(DWORD *)&exit_code); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } else { + String cmd_line_utf8 = string16_to_string(a, make_string16(cast(u16 *)cmd_line, n)); + gb_printf_err("Failed to execute command:\n\t%.*s\n", LIT(cmd_line_utf8)); + gb_free(a, cmd_line_utf8.text); + exit_code = -1; + } + return exit_code; +} #else +int run_subprocess(const char *name, const char **args) { pid_t pid; int status; status = posix_spawn(&pid, name, NULL, NULL, (char *const *)args, environ); @@ -215,8 +261,8 @@ int run_subprocess(const char *name, const char **args) { } } GB_PANIC("Subprocess failure"); -#endif } +#endif #if defined(GB_SYSTEM_WINDOWS) #define popen _popen @@ -250,12 +296,12 @@ gb_internal bool system_exec_command_line_app_output(char const *command, gbStri return true; } -gb_internal Array setup_args(int argc, char const **argv) { +gb_internal Array setup_args(int argc, char const **argv, wchar_t **after_double_dash_raw) { gbAllocator a = heap_allocator(); #if defined(GB_SYSTEM_WINDOWS) int wargc = 0; - wchar_t **wargv = command_line_to_wargv(GetCommandLineW(), &wargc); + wchar_t **wargv = command_line_to_wargv(GetCommandLineW(), &wargc, after_double_dash_raw); auto args = array_make(a, 0, wargc); for (isize i = 0; i < wargc; i++) { u16 *warg = cast(u16 *)wargv[i]; @@ -3757,7 +3803,8 @@ int main(int arg_count, char const **arg_ptr) { init_build_context_error_pos_style(); - Array args = setup_args(arg_count, arg_ptr); + wchar_t *after_double_dash_raw = nullptr; + Array args = setup_args(arg_count, arg_ptr, &after_double_dash_raw); Array run_args = array_make(heap_allocator(), 0, arg_count); defer (array_free(&run_args)); @@ -4415,6 +4462,9 @@ end_of_code_gen:; String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); defer (gb_free(heap_allocator(), exe_name.text)); +#if defined(GB_SYSTEM_WINDOWS) + int subprocess_res = run_subprocess(exe_name, after_double_dash_raw); +#else const char* exe_name_cstring = alloc_cstring(heap_allocator(), exe_name); Array run_args_cstring = array_make(heap_allocator(), 0, run_args.count); defer({ @@ -4429,6 +4479,7 @@ end_of_code_gen:; array_add(&run_args_cstring, NULL); int subprocess_res = run_subprocess(exe_name_cstring, run_args_cstring.data); +#endif if (subprocess_res) { gb_exit(subprocess_res); } From 7130f042dcc40a46975c8d99701f13a3d3942e70 Mon Sep 17 00:00:00 2001 From: David Bader <65665893+BunterSchatten@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:32:02 +0200 Subject: [PATCH 50/90] define out unused run_args on Windows --- src/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index a176ace00..ef304cd16 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3805,8 +3805,10 @@ int main(int arg_count, char const **arg_ptr) { wchar_t *after_double_dash_raw = nullptr; Array args = setup_args(arg_count, arg_ptr, &after_double_dash_raw); +#if !defined(GB_SYSTEM_WINDOWS) Array run_args = array_make(heap_allocator(), 0, arg_count); defer (array_free(&run_args)); +#endif String command = args[1]; String init_filename = {}; @@ -3867,9 +3869,11 @@ int main(int arg_count, char const **arg_ptr) { return 1; } +#if !defined(GB_SYSTEM_WINDOWS) for(isize i = double_dash_pos+1; i < args.count; ++i) { array_add(&run_args, args[i]); } +#endif } args = array_slice(args, 0, last_non_run_arg); From edc522a05c2e99d93948f5596fb6383cf66ab50d Mon Sep 17 00:00:00 2001 From: David Bader <65665893+BunterSchatten@users.noreply.github.com> Date: Thu, 30 Jul 2026 12:31:20 +0200 Subject: [PATCH 51/90] add assert to make sure old and new double_dash_pos are the same --- src/common.cpp | 7 +++++-- src/main.cpp | 13 +++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/common.cpp b/src/common.cpp index 133b1888a..76ace2755 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -640,7 +640,7 @@ gb_internal gb_inline f64 gb_sqrt(f64 x) { #if defined(GB_SYSTEM_WINDOWS) -gb_internal wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc, wchar_t **_after_double_dash_raw) { +gb_internal wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc, isize *_double_dash_pos, wchar_t **_after_double_dash_raw) { u32 i, j; u32 len = cast(u32)string16_len(cast(u16 *)cmd_line); @@ -650,6 +650,7 @@ gb_internal wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc, wchar wchar_t *_argv = cast(wchar_t *)((cast(u8 *)argv)+i); wchar_t *after_double_dash_raw = nullptr; + isize double_dash_pos = -1; u32 argc = 0; argv[argc] = _argv; @@ -660,12 +661,13 @@ gb_internal wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc, wchar j = 0; auto const check_double_dash = [&]() { - if (!after_double_dash_raw && + if (double_dash_pos == -1 && argc >= 1 && argv[argc - 1][0] == '-' && argv[argc - 1][1] == '-' && argv[argc - 1][2] == '\0') { + double_dash_pos = argc - 1; after_double_dash_raw = cmd_line + i; } }; @@ -718,6 +720,7 @@ gb_internal wchar_t **command_line_to_wargv(wchar_t *cmd_line, int *_argc, wchar check_double_dash(); if (_argc) *_argc = argc; + if (_double_dash_pos) *_double_dash_pos = double_dash_pos; if (_after_double_dash_raw) *_after_double_dash_raw = after_double_dash_raw; return argv; } diff --git a/src/main.cpp b/src/main.cpp index ef304cd16..de4c219e2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -296,12 +296,12 @@ gb_internal bool system_exec_command_line_app_output(char const *command, gbStri return true; } -gb_internal Array setup_args(int argc, char const **argv, wchar_t **after_double_dash_raw) { +gb_internal Array setup_args(int argc, char const **argv, isize *double_dash_pos, wchar_t **after_double_dash_raw) { gbAllocator a = heap_allocator(); #if defined(GB_SYSTEM_WINDOWS) int wargc = 0; - wchar_t **wargv = command_line_to_wargv(GetCommandLineW(), &wargc, after_double_dash_raw); + wchar_t **wargv = command_line_to_wargv(GetCommandLineW(), &wargc, double_dash_pos, after_double_dash_raw); auto args = array_make(a, 0, wargc); for (isize i = 0; i < wargc; i++) { u16 *warg = cast(u16 *)wargv[i]; @@ -310,6 +310,8 @@ gb_internal Array setup_args(int argc, char const **argv, wchar_t **afte String arg = string16_to_string(a, wstr); if (arg.len > 0) { array_add(&args, arg); + } else if (double_dash_pos && *double_dash_pos > 0 && args.count < *double_dash_pos) { + *double_dash_pos -= 1; } } return args; @@ -3803,8 +3805,9 @@ int main(int arg_count, char const **arg_ptr) { init_build_context_error_pos_style(); + isize double_dash_pos = -1; wchar_t *after_double_dash_raw = nullptr; - Array args = setup_args(arg_count, arg_ptr, &after_double_dash_raw); + Array args = setup_args(arg_count, arg_ptr, &double_dash_pos, &after_double_dash_raw); #if !defined(GB_SYSTEM_WINDOWS) Array run_args = array_make(heap_allocator(), 0, arg_count); defer (array_free(&run_args)); @@ -3814,9 +3817,11 @@ int main(int arg_count, char const **arg_ptr) { String init_filename = {}; isize last_non_run_arg = args.count; - isize double_dash_pos = -1; for_array(i, args) { if (args[i] == "--") { +#if defined(GB_SYSTEM_WINDOWS) + GB_ASSERT(double_dash_pos == i); +#endif double_dash_pos = i; break; } From ff949ac6fd04d9cc9f2c5b97ea7a52cbc8efcdbb Mon Sep 17 00:00:00 2001 From: jerksto <49298362+jerksto@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:59:10 -0400 Subject: [PATCH 52/90] Don't assert on -define: values with an exponent --- src/big_int.cpp | 24 ++++++++++++++++++++++-- src/main.cpp | 23 ++++++++++++++++++++++- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/big_int.cpp b/src/big_int.cpp index f59dccf24..ffb7a6ad6 100644 --- a/src/big_int.cpp +++ b/src/big_int.cpp @@ -249,13 +249,28 @@ gb_internal void big_int_from_string(BigInt *dst, String const &s, bool *success } if (i < len && (text[i] == 'e' || text[i] == 'E')) { i += 1; - GB_ASSERT(base == 10); - GB_ASSERT(text[i] != '-'); + if (base != 10) { + // An exponent is only meaningful for a base 10 literal. + *success = false; + return; + } + if (i >= len) { + // Nothing follows the exponent marker. + *success = false; + return; + } + if (text[i] == '-') { + // A negative exponent is never an integer. + // The caller is expected to parse the value as a float instead. + *success = false; + return; + } if (text[i] == '+') { i += 1; } u64 exp = 0; + isize exp_digits = 0; for (; i < len; i++) { char r = cast(char)text[i]; if (r == '_') { @@ -270,6 +285,11 @@ gb_internal void big_int_from_string(BigInt *dst, String const &s, bool *success } exp *= 10; exp += v; + exp_digits += 1; + } + if (exp_digits == 0) { + *success = false; + return; } // NOTE(Jeroen): A valid integer can never have an exponent larger than 308 (per `max(f64)`). diff --git a/src/main.cpp b/src/main.cpp index de4c219e2..74900587f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -537,6 +537,27 @@ gb_internal void add_flag(Array *build_flags, BuildFlagKind kind, Str array_add(build_flags, flag); } +// A value such as `1e-5` is a float, not an integer. Base prefixed literals are excluded because +// `e` is a valid digit in bases 16 and above, e.g. `0x1e`. +gb_internal bool build_param_looks_like_float(String const ¶m) { + if (string_contains_char(param, '.')) { + return true; + } + isize i = 0; + if (param.len > 0 && (param[0] == '-' || param[0] == '+')) { + i = 1; + } + if (param.len > i+1 && param[i] == '0' && !gb_char_is_digit(cast(char)param[i+1])) { + return false; + } + for (; i < param.len; i++) { + if (param[i] == 'e' || param[i] == 'E') { + return true; + } + } + return false; +} + gb_internal ExactValue build_param_to_exact_value(String name, String param) { ExactValue value = {}; @@ -562,7 +583,7 @@ gb_internal ExactValue build_param_to_exact_value(String name, String param) { Try to parse as an integer or float */ if (param[0] == '-' || param[0] == '+' || gb_is_between(param[0], '0', '9')) { - if (string_contains_char(param, '.')) { + if (build_param_looks_like_float(param)) { value = exact_value_float_from_string(param); } else { value = exact_value_integer_from_string(param); From 6a14e5e0683a24c0fe2e6fc826fa04d9b109bc70 Mon Sep 17 00:00:00 2001 From: George Potoshin Date: Thu, 30 Jul 2026 19:24:36 +0200 Subject: [PATCH 53/90] #7153 --- core/text/scanner/scanner.odin | 7 ++++++- .../text/scanner/test_core_text_scanner.odin | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 tests/core/text/scanner/test_core_text_scanner.odin diff --git a/core/text/scanner/scanner.odin b/core/text/scanner/scanner.odin index 9445df58c..77e3a1b84 100644 --- a/core/text/scanner/scanner.odin +++ b/core/text/scanner/scanner.odin @@ -377,8 +377,13 @@ scan_number :: proc(s: ^Scanner, ch: rune, seen_dot: bool) -> (rune, rune) { ch, ds = digits(s, ch, base, &invalid) digsep |= ds if ch == '.' && .Scan_Floats in s.flags { + prev_s := s^ ch = advance(s) - seen_dot = true + if ch == '.' { + s^ = prev_s + } else { + seen_dot = true + } } } diff --git a/tests/core/text/scanner/test_core_text_scanner.odin b/tests/core/text/scanner/test_core_text_scanner.odin new file mode 100644 index 000000000..320eb86b7 --- /dev/null +++ b/tests/core/text/scanner/test_core_text_scanner.odin @@ -0,0 +1,18 @@ +package test_core_text_scanner + +import "core:testing" +import s "core:text/scanner" + +@test +range_operator :: proc(t: ^testing.T) { + data := "0x00..=0xff" + h: s.Scanner + s.init(&h, data, "string") + h.flags = s.Odin_Like_Tokens - {.Skip_Comments} + testing.expect(t, s.Int == s.scan(&h), "token should be Int") + testing.expect(t, '.' == s.scan(&h), "token should be '.'") + testing.expect(t, '.' == s.scan(&h), "token should be '.'") + testing.expect(t, '=' == s.scan(&h), "token should be '='") + testing.expect(t, s.Int == s.scan(&h), "token should be Int") + testing.expect(t, s.EOF == s.scan(&h), "token should be EOF") +} From a4e22fbd3142274bbc60e39c52d2513e27966851 Mon Sep 17 00:00:00 2001 From: Thomas Countz Date: Thu, 30 Jul 2026 20:38:24 +0200 Subject: [PATCH 54/90] Fix typo in raymath subtract deprecation --- vendor/raylib/raymath.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/raylib/raymath.odin b/vendor/raylib/raymath.odin index 98eb5c03e..c5df7cb7b 100644 --- a/vendor/raylib/raymath.odin +++ b/vendor/raylib/raymath.odin @@ -80,7 +80,7 @@ Vector2Subtract :: proc "c" (a, b: Vector2) -> Vector2 { return a - b } // Subtract vector by float value -@(require_results, deprecated="Prefer v + value") +@(require_results, deprecated="Prefer v - value") Vector2SubtractValue :: proc "c" (v: Vector2, value: f32) -> Vector2 { return v - value } @@ -270,7 +270,7 @@ Vector3Subtract :: proc "c" (a, b: Vector3) -> Vector3 { return a - b } // Subtract vector by float value -@(require_results, deprecated="Prefer v + value") +@(require_results, deprecated="Prefer v - value") Vector3SubtractValue :: proc "c" (v: Vector3, value: f32) -> Vector3 { return v - value } From 0f472409c404426ca15159f209726a9aabd87859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C4=81vius?= Date: Thu, 30 Jul 2026 15:04:59 -0400 Subject: [PATCH 55/90] rexcode/x86: label addressing for RIP-relative disp and movabs imm Add mem_rip_label(label_id) so a RIP-relative memory operand can reference a label: the encoder writes a placeholder disp32 and emits a REL32 relocation (addend 0) at the field's byte offset, mirroring the existing .RELATIVE jump/call path. This expresses lea reg, [rip +