From eab3e2be2212ec11c568f4fcb5e74e30b379e37f Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 20 May 2025 19:47:48 +0200 Subject: [PATCH 01/34] os2: remove libc use on Linux --- core/os/os2/path_linux.odin | 18 ++++++++++++++++++ core/os/os2/path_posix.odin | 17 +++++++++++++++++ core/os/os2/path_posixfs.odin | 21 --------------------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index 64f415187..8b185f419 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -207,3 +207,21 @@ _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: } return } + +_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { + rel := path + if rel == "" { + rel = "." + } + + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + + fd, errno := linux.open(clone_to_cstring(path, temp_allocator) or_return, {}) + if errno != nil { + err = _get_platform_error(errno) + return + } + defer linux.close(fd) + + return _get_full_path(fd, allocator) +} diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin index e59567240..f22cd446b 100644 --- a/core/os/os2/path_posix.odin +++ b/core/os/os2/path_posix.odin @@ -123,3 +123,20 @@ _set_working_directory :: proc(dir: string) -> (err: Error) { } return } + +_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { + rel := path + if rel == "" { + rel = "." + } + temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) + rel_cstr := clone_to_cstring(rel, temp_allocator) or_return + path_ptr := posix.realpath(rel_cstr, nil) + if path_ptr == nil { + return "", Platform_Error(posix.errno()) + } + defer posix.free(path_ptr) + + path_str := clone_string(string(path_ptr), allocator) or_return + return path_str, nil +} diff --git a/core/os/os2/path_posixfs.odin b/core/os/os2/path_posixfs.odin index 4102d71c1..0736e73d1 100644 --- a/core/os/os2/path_posixfs.odin +++ b/core/os/os2/path_posixfs.odin @@ -4,10 +4,6 @@ package os2 // This implementation is for all systems that have POSIX-compliant filesystem paths. -import "base:runtime" -import "core:strings" -import "core:sys/posix" - _are_paths_identical :: proc(a, b: string) -> (identical: bool) { return a == b } @@ -26,23 +22,6 @@ _is_absolute_path :: proc(path: string) -> bool { return len(path) > 0 && _is_path_separator(path[0]) } -_get_absolute_path :: proc(path: string, allocator: runtime.Allocator) -> (absolute_path: string, err: Error) { - rel := path - if rel == "" { - rel = "." - } - temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) - rel_cstr := strings.clone_to_cstring(rel, temp_allocator) - path_ptr := posix.realpath(rel_cstr, nil) - if path_ptr == nil { - return "", Platform_Error(posix.errno()) - } - defer posix.free(path_ptr) - - path_str := strings.clone(string(path_ptr), allocator) - return path_str, nil -} - _get_relative_path_handle_start :: proc(base, target: string) -> bool { base_rooted := len(base) > 0 && _is_path_separator(base[0]) target_rooted := len(target) > 0 && _is_path_separator(target[0]) From c090a28b9d390ccd5352af2b2443491be806e464 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 20 May 2025 18:56:18 -0400 Subject: [PATCH 02/34] Add `/usr/local/lib` to FreeBSD linker path --- src/linker.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/linker.cpp b/src/linker.cpp index 087bb49f1..41d4a13a1 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -801,6 +801,9 @@ try_cross_linking:; // This points the linker to where the entry point is link_settings = gb_string_appendc(link_settings, "-e _main "); } + } else if (build_context.metrics.os == TargetOs_freebsd) { + // FreeBSD pkg installs third-party shared libraries in /usr/local/lib. + platform_lib_str = gb_string_appendc(platform_lib_str, "-Wl,-L/usr/local/lib "); } else if (build_context.metrics.os == TargetOs_openbsd) { // OpenBSD ports install shared libraries in /usr/local/lib. Also, we must explicitly link libpthread. platform_lib_str = gb_string_appendc(platform_lib_str, "-lpthread -Wl,-L/usr/local/lib "); From 30c1b887414197268bc866aec770786023bd457e Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 20 May 2025 15:32:01 -0400 Subject: [PATCH 03/34] Add `core:terminal` --- core/terminal/doc.odin | 4 ++ core/terminal/terminal.odin | 104 ++++++++++++++++++++++++++++ core/terminal/terminal_posix.odin | 9 +++ core/terminal/terminal_windows.odin | 9 +++ examples/all/all_main.odin | 3 + 5 files changed, 129 insertions(+) create mode 100644 core/terminal/doc.odin create mode 100644 core/terminal/terminal.odin create mode 100644 core/terminal/terminal_posix.odin create mode 100644 core/terminal/terminal_windows.odin diff --git a/core/terminal/doc.odin b/core/terminal/doc.odin new file mode 100644 index 000000000..490e9d398 --- /dev/null +++ b/core/terminal/doc.odin @@ -0,0 +1,4 @@ +/* +This package is for interacting with the command line interface of the system. +*/ +package terminal diff --git a/core/terminal/terminal.odin b/core/terminal/terminal.odin new file mode 100644 index 000000000..fae6f880a --- /dev/null +++ b/core/terminal/terminal.odin @@ -0,0 +1,104 @@ +package terminal + +import "core:os" +import "core:strings" + +/* +This describes the range of colors that a terminal is capable of supporting. +*/ +Color_Depth :: enum { + None, // No color support + Three_Bit, // 8 colors + Four_Bit, // 16 colors + Eight_Bit, // 256 colors + True_Color, // 24-bit true color +} + +/* +Returns true if the file `handle` is attached to a terminal. + +This is normally true for `os.stdout` and `os.stderr` unless they are +redirected to a file. +*/ +@(require_results) +is_terminal :: proc(handle: os.Handle) -> bool { + return _is_terminal(handle) +} + +/* +Get the color depth support for the terminal. +*/ +@(require_results) +get_color_depth :: proc() -> Color_Depth { + // Reference documentation: + // + // - [[ https://no-color.org/ ]] + // - [[ https://github.com/termstandard/colors ]] + // - [[ https://invisible-island.net/ncurses/terminfo.src.html ]] + + // Respect `NO_COLOR` above all. + if no_color, ok := os.lookup_env("NO_COLOR"); ok { + defer delete(no_color) + if no_color != "" { + return .None + } + } + + // `COLORTERM` is non-standard but widespread and unambiguous. + if colorterm, ok := os.lookup_env("COLORTERM"); ok { + defer delete(colorterm) + // These are the only values that are typically advertised that have + // anything to do with color depth. + if colorterm == "truecolor" || colorterm == "24bit" { + return .True_Color + } + } + + if term, ok := os.lookup_env("TERM"); ok { + defer delete(term) + if strings.contains(term, "-truecolor") { + return .True_Color + } + if strings.contains(term, "-256color") { + return .Eight_Bit + } + if strings.contains(term, "-16color") { + return .Four_Bit + } + + // The `terminfo` database, which is stored in binary on *nix + // platforms, has an undocumented format that is not guaranteed to be + // portable, so beyond this point, we can only make safe assumptions. + // + // This section should only be necessary for terminals that do not + // define any of the previous environment values. + // + // Only a small sampling of some common values are checked here. + switch term { + case "ansi": fallthrough + case "konsole": fallthrough + case "putty": fallthrough + case "rxvt": fallthrough + case "rxvt-color": fallthrough + case "screen": fallthrough + case "st": fallthrough + case "tmux": fallthrough + case "vte": fallthrough + case "xterm": fallthrough + case "xterm-color": + return .Three_Bit + } + } + + return .None +} + +/* +This is true if the terminal is accepting any form of colored text output. +*/ +color_enabled: bool + +@(init, private) +init_terminal_status :: proc() { + color_enabled = get_color_depth() > .None +} diff --git a/core/terminal/terminal_posix.odin b/core/terminal/terminal_posix.odin new file mode 100644 index 000000000..adfb6a0da --- /dev/null +++ b/core/terminal/terminal_posix.odin @@ -0,0 +1,9 @@ +#+build linux, darwin, netbsd, openbsd, freebsd, haiku +package terminal + +import "core:os" +import "core:sys/posix" + +_is_terminal :: proc(handle: os.Handle) -> bool { + return bool(posix.isatty(posix.FD(handle))) +} diff --git a/core/terminal/terminal_windows.odin b/core/terminal/terminal_windows.odin new file mode 100644 index 000000000..caab87cc7 --- /dev/null +++ b/core/terminal/terminal_windows.odin @@ -0,0 +1,9 @@ +package terminal + +import "core:os" +import "core:sys/windows" + +_is_terminal :: proc(handle: os.Handle) -> bool { + mode: windows.DWORD + return bool(windows.GetConsoleMode(windows.HANDLE(handle), &mode)) +} diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 0a17227b8..97ecfee45 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -129,6 +129,8 @@ import strings "core:strings" import sync "core:sync" import testing "core:testing" +import terminal "core:terminal" + import edit "core:text/edit" import i18n "core:text/i18n" import match "core:text/match" @@ -257,6 +259,7 @@ _ :: strconv _ :: strings _ :: sync _ :: testing +_ :: terminal _ :: scanner _ :: i18n _ :: match From df5e64beebd02971292838acc4150471620bae24 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 20 May 2025 15:35:11 -0400 Subject: [PATCH 04/34] Add terminal color detection to `core:log` --- core/log/file_console_logger.odin | 62 ++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index 6d93fb879..f807f321f 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -2,10 +2,12 @@ #+build !orca package log +import "base:runtime" import "core:encoding/ansi" import "core:fmt" import "core:strings" import "core:os" +import "core:terminal" import "core:time" Level_Headers := [?]string{ @@ -37,11 +39,36 @@ File_Console_Logger_Data :: struct { ident: string, } +@(private) global_subtract_stdout_options: Options +@(private) global_subtract_stderr_options: Options + +@(init, private) +init_standard_stream_status :: proc() { + // NOTE(Feoramund): While it is technically possible for these streams to + // be redirected during the runtime of the program, the cost of checking on + // every single log message is not worth it to support such an + // uncommonly-used feature. + if terminal.color_enabled { + // This is done this way because it's possible that only one of these + // streams could be redirected to a file. + if !terminal.is_terminal(os.stdout) { + global_subtract_stdout_options = {.Terminal_Color} + } + if !terminal.is_terminal(os.stderr) { + global_subtract_stderr_options = {.Terminal_Color} + } + } else { + // Override any terminal coloring. + global_subtract_stdout_options = {.Terminal_Color} + global_subtract_stderr_options = {.Terminal_Color} + } +} + create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger { data := new(File_Console_Logger_Data, allocator) data.file_handle = h data.ident = ident - return Logger{file_console_logger_proc, data, lowest, opt} + return Logger{file_logger_proc, data, lowest, opt} } destroy_file_logger :: proc(log: Logger, allocator := context.allocator) { @@ -56,19 +83,15 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg data := new(File_Console_Logger_Data, allocator) data.file_handle = os.INVALID_HANDLE data.ident = ident - return Logger{file_console_logger_proc, data, lowest, opt} + return Logger{console_logger_proc, data, lowest, opt} } destroy_console_logger :: proc(log: Logger, allocator := context.allocator) { free(log.data, allocator) } -file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { - data := cast(^File_Console_Logger_Data)logger_data - h: os.Handle = os.stdout if level <= Level.Error else os.stderr - if data.file_handle != os.INVALID_HANDLE { - h = data.file_handle - } +@(private) +_file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) { backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths. buf := strings.builder_from_bytes(backing[:]) @@ -86,13 +109,32 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string fmt.sbprintf(&buf, "[{}] ", os.current_thread_id()) } - if data.ident != "" { - fmt.sbprintf(&buf, "[%s] ", data.ident) + if ident != "" { + fmt.sbprintf(&buf, "[%s] ", ident) } //TODO(Hoej): When we have better atomics and such, make this thread-safe fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text) } +file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { + data := cast(^File_Console_Logger_Data)logger_data + _file_console_logger_proc(data.file_handle, data.ident, level, text, options, location) +} + +console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { + options := options + data := cast(^File_Console_Logger_Data)logger_data + h: os.Handle = --- + if level < Level.Error { + h = os.stdout + options -= global_subtract_stdout_options + } else { + h = os.stderr + options -= global_subtract_stderr_options + } + _file_console_logger_proc(h, data.ident, level, text, options, location) +} + do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) { RESET :: ansi.CSI + ansi.RESET + ansi.SGR From a9df1b1cde1037d030f4e823ce576dfd9bcf9c97 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 20 May 2025 15:41:35 -0400 Subject: [PATCH 05/34] Rename `core:encoding/ansi` to `core:terminal/ansi` --- core/log/file_console_logger.odin | 2 +- core/{encoding => terminal}/ansi/ansi.odin | 0 core/{encoding => terminal}/ansi/doc.odin | 0 core/testing/reporting.odin | 2 +- core/testing/runner.odin | 2 +- core/testing/signal_handler_libc.odin | 4 ++-- examples/all/all_main.odin | 4 ++-- 7 files changed, 7 insertions(+), 7 deletions(-) rename core/{encoding => terminal}/ansi/ansi.odin (100%) rename core/{encoding => terminal}/ansi/doc.odin (100%) diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index f807f321f..0fe5c3477 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -3,11 +3,11 @@ package log import "base:runtime" -import "core:encoding/ansi" import "core:fmt" import "core:strings" import "core:os" import "core:terminal" +import "core:terminal/ansi" import "core:time" Level_Headers := [?]string{ diff --git a/core/encoding/ansi/ansi.odin b/core/terminal/ansi/ansi.odin similarity index 100% rename from core/encoding/ansi/ansi.odin rename to core/terminal/ansi/ansi.odin diff --git a/core/encoding/ansi/doc.odin b/core/terminal/ansi/doc.odin similarity index 100% rename from core/encoding/ansi/doc.odin rename to core/terminal/ansi/doc.odin diff --git a/core/testing/reporting.odin b/core/testing/reporting.odin index 6752cd79b..7c7eb7b2d 100644 --- a/core/testing/reporting.odin +++ b/core/testing/reporting.odin @@ -10,12 +10,12 @@ package testing */ import "base:runtime" -import "core:encoding/ansi" import "core:fmt" import "core:io" import "core:mem" import "core:path/filepath" import "core:strings" +import "core:terminal/ansi" // Definitions of colors for use in the test runner. SGR_RESET :: ansi.CSI + ansi.RESET + ansi.SGR diff --git a/core/testing/runner.odin b/core/testing/runner.odin index db0587370..c81d07109 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -13,7 +13,6 @@ package testing import "base:intrinsics" import "base:runtime" import "core:bytes" -import "core:encoding/ansi" @require import "core:encoding/base64" @require import "core:encoding/json" import "core:fmt" @@ -25,6 +24,7 @@ import "core:os" import "core:slice" @require import "core:strings" import "core:sync/chan" +import "core:terminal/ansi" import "core:thread" import "core:time" diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index 281fbde40..d17a6d6dc 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -12,9 +12,9 @@ package testing import "base:intrinsics" import "core:c/libc" -import "core:encoding/ansi" -import "core:sync" import "core:os" +import "core:sync" +import "core:terminal/ansi" @(private="file") stop_runner_flag: libc.sig_atomic_t diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 97ecfee45..de037f6cd 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -58,7 +58,6 @@ import trace "core:debug/trace" import dynlib "core:dynlib" import net "core:net" -import ansi "core:encoding/ansi" import base32 "core:encoding/base32" import base64 "core:encoding/base64" import cbor "core:encoding/cbor" @@ -130,6 +129,7 @@ import sync "core:sync" import testing "core:testing" import terminal "core:terminal" +import ansi "core:terminal/ansi" import edit "core:text/edit" import i18n "core:text/i18n" @@ -203,7 +203,6 @@ _ :: pe _ :: trace _ :: dynlib _ :: net -_ :: ansi _ :: base32 _ :: base64 _ :: csv @@ -260,6 +259,7 @@ _ :: strings _ :: sync _ :: testing _ :: terminal +_ :: ansi _ :: scanner _ :: i18n _ :: match From 3c40a54dcd9ff8e1b608fac5c91ed0f1d0ed7d00 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 20 May 2025 15:43:47 -0400 Subject: [PATCH 06/34] Add terminal color detection to logging in `core:testing` --- core/testing/runner.odin | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index c81d07109..ff8ca00b9 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -24,6 +24,7 @@ import "core:os" import "core:slice" @require import "core:strings" import "core:sync/chan" +import "core:terminal" import "core:terminal/ansi" import "core:thread" import "core:time" @@ -70,6 +71,8 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level { } } +@(private) global_log_colors_disabled: bool + JSON :: struct { total: int, success: int, @@ -129,11 +132,16 @@ run_test_task :: proc(task: thread.Task) { context.assertion_failure_proc = test_assertion_failure_proc + logger_options := Default_Test_Logger_Opts + if global_log_colors_disabled { + logger_options -= {.Terminal_Color} + } + context.logger = { procedure = test_logger_proc, data = &data.t, lowest_level = get_log_level(), - options = Default_Test_Logger_Opts, + options = logger_options, } random_generator_state: runtime.Default_Random_State @@ -211,6 +219,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { stdout := io.to_writer(os.stream_from_handle(os.stdout)) stderr := io.to_writer(os.stream_from_handle(os.stderr)) + global_log_colors_disabled = !terminal.color_enabled || !terminal.is_terminal(os.stderr) + // -- Prepare test data. alloc_error: mem.Allocator_Error @@ -442,11 +452,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { // digging through the source to divine everywhere it is used for that. shared_log_allocator := context.allocator + logger_options := Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure} + if global_log_colors_disabled { + logger_options -= {.Terminal_Color} + } + context.logger = { procedure = runner_logger_proc, data = &log_messages, lowest_level = get_log_level(), - options = Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}, + options = logger_options, } run_index: int From 1b407ef20789b1d04d61b0a53ca8c98fe6621db2 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 20 May 2025 16:17:14 -0400 Subject: [PATCH 07/34] Add animation detection support to test runner --- core/testing/runner.odin | 133 +++++++++++++++----------- core/testing/signal_handler_libc.odin | 8 +- 2 files changed, 80 insertions(+), 61 deletions(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index ff8ca00b9..a184eb28c 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -45,6 +45,7 @@ PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_S // The format is: `package.test_name,test_name_only,...` TEST_NAMES : string : #config(ODIN_TEST_NAMES, "") // Show the fancy animated progress report. +// This requires terminal color support, as well as STDOUT to not be redirected to a file. FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true) // Copy failed tests to the clipboard when done. USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false) @@ -72,6 +73,7 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level { } @(private) global_log_colors_disabled: bool +@(private) global_ansi_disabled: bool JSON :: struct { total: int, @@ -219,7 +221,12 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { stdout := io.to_writer(os.stream_from_handle(os.stdout)) stderr := io.to_writer(os.stream_from_handle(os.stderr)) + // 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) + + should_show_animations := FANCY_OUTPUT && terminal.color_enabled && !global_ansi_disabled // -- Prepare test data. @@ -278,12 +285,12 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { total_done_count := 0 total_test_count := len(internal_tests) - when !FANCY_OUTPUT { - // This is strictly for updating the window title when the progress - // report is disabled. We're otherwise able to depend on the call to - // `needs_to_redraw`. - last_done_count := -1 - } + + // This is strictly for updating the window title when the progress + // report is disabled. We're otherwise able to depend on the call to + // `needs_to_redraw`. + last_done_count := -1 + if total_test_count == 0 { // Exit early. @@ -352,31 +359,31 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { fmt.assertf(alloc_error == nil, "Error allocating memory for test report: %v", alloc_error) defer destroy_report(&report) - when FANCY_OUTPUT { - // We cannot make use of the ANSI save/restore cursor codes, because they - // work by absolute screen coordinates. This will cause unnecessary - // scrollback if we print at the bottom of someone's terminal. - ansi_redraw_string := fmt.aprintf( - // ANSI for "go up N lines then erase the screen from the cursor forward." - ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED + - // We'll combine this with the window title format string, since it - // can be printed at the same time. - "%s", - // 1 extra line for the status bar. - 1 + len(report.packages), OSC_WINDOW_TITLE) - assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.") - defer delete(ansi_redraw_string) - thread_count_status_string: string = --- - { - PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH + // We cannot make use of the ANSI save/restore cursor codes, because they + // work by absolute screen coordinates. This will cause unnecessary + // scrollback if we print at the bottom of someone's terminal. + ansi_redraw_string := fmt.aprintf( + // ANSI for "go up N lines then erase the screen from the cursor forward." + ansi.CSI + "%i" + ansi.CPL + ansi.CSI + ansi.ED + + // We'll combine this with the window title format string, since it + // can be printed at the same time. + "%s", + // 1 extra line for the status bar. + 1 + len(report.packages), OSC_WINDOW_TITLE) + assert(len(ansi_redraw_string) > 0, "Error allocating ANSI redraw string.") + defer delete(ansi_redraw_string) - unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s") - thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING) - assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.") - } - defer delete(thread_count_status_string) + thread_count_status_string: string = --- + { + PADDING :: PROGRESS_COLUMN_SPACING + PROGRESS_WIDTH + + unpadded := fmt.tprintf("%i thread%s", thread_count, "" if thread_count == 1 else "s") + thread_count_status_string = fmt.aprintf("%- *[1]s", unpadded, report.pkg_column_len + PADDING) + assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.") } + defer delete(thread_count_status_string) + task_data_slots: []Task_Data = --- task_data_slots, alloc_error = make([]Task_Data, thread_count) @@ -496,11 +503,13 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { setup_signal_handler() - fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE) + if !global_ansi_disabled { + fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE) + } - when FANCY_OUTPUT { - signals_were_raised := false + signals_were_raised := false + if should_show_animations { redraw_report(stdout, report) draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count) } @@ -718,22 +727,22 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { break main_loop } - when FANCY_OUTPUT { - // Because the bounds checking procs send directly to STDERR with - // no way to redirect or handle them, we need to at least try to - // let the user see those messages when using the animated progress - // report. This flag may be set by the block of code below if a - // signal is raised. - // - // It'll be purely by luck if the output is interleaved properly, - // given the nature of non-thread-safe printing. - // - // At worst, if Odin did not print any error for this signal, we'll - // just re-display the progress report. The fatal log error message - // should be enough to clue the user in that something dire has - // occurred. - bypass_progress_overwrite := false - } + + // Because the bounds checking procs send directly to STDERR with + // no way to redirect or handle them, we need to at least try to + // let the user see those messages when using the animated progress + // report. This flag may be set by the block of code below if a + // signal is raised. + // + // It'll be purely by luck if the output is interleaved properly, + // given the nature of non-thread-safe printing. + // + // At worst, if Odin did not print any error for this signal, we'll + // just re-display the progress report. The fatal log error message + // should be enough to clue the user in that something dire has + // occurred. + bypass_progress_overwrite := false + if test_index, reason, ok := should_stop_test(); ok { #no_bounds_check report.all_test_states[test_index] = .Failed @@ -767,7 +776,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason) } - when FANCY_OUTPUT { + if should_show_animations { bypass_progress_overwrite = true signals_were_raised = true } @@ -781,7 +790,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { // -- Redraw. - when FANCY_OUTPUT { + if should_show_animations { if len(log_messages) == 0 && !needs_to_redraw(report) { continue main_loop } @@ -791,7 +800,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } else { if total_done_count != last_done_count { - fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count) + if !global_ansi_disabled { + fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count) + } last_done_count = total_done_count } @@ -816,7 +827,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { clear(&log_messages) bytes.buffer_reset(&batch_buffer) - when FANCY_OUTPUT { + if should_show_animations { redraw_report(batch_writer, report) draw_status_bar(batch_writer, thread_count_status_string, total_done_count, total_test_count) fmt.wprint(stdout, bytes.buffer_to_string(&batch_buffer)) @@ -837,7 +848,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { finished_in := time.since(start_time) - when !FANCY_OUTPUT { + if !should_show_animations || !terminal.is_terminal(os.stderr) { // One line to space out the results, since we don't have the status // bar in plain mode. fmt.wprintln(batch_writer) @@ -851,24 +862,28 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { if total_done_count != total_test_count { not_run_count := total_test_count - total_done_count + message := " %i %s left undone." if global_log_colors_disabled else " " + SGR_READY + "%i" + SGR_RESET + " %s left undone." fmt.wprintf(batch_writer, - " " + SGR_READY + "%i" + SGR_RESET + " %s left undone.", + message, not_run_count, "test was" if not_run_count == 1 else "tests were") } if total_success_count == total_test_count { + message := " %s successful." if global_log_colors_disabled else " %s " + SGR_SUCCESS + "successful." + SGR_RESET fmt.wprintfln(batch_writer, - " %s " + SGR_SUCCESS + "successful." + SGR_RESET, + message, "The test was" if total_test_count == 1 else "All tests were") } else if total_failure_count > 0 { if total_failure_count == total_test_count { + message := " %s failed." if global_log_colors_disabled else " %s " + SGR_FAILED + "failed." + SGR_RESET fmt.wprintfln(batch_writer, - " %s " + SGR_FAILED + "failed." + SGR_RESET, + message, "The test" if total_test_count == 1 else "All tests") } else { + message := " %i test%s failed." if global_log_colors_disabled else " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed." fmt.wprintfln(batch_writer, - " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed.", + message, total_failure_count, "" if total_failure_count == 1 else "s") } @@ -922,9 +937,11 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } - fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW) + if !global_ansi_disabled { + fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW) + } - when FANCY_OUTPUT { + if should_show_animations { if signals_were_raised { fmt.wprintln(batch_writer, ` Signals were raised during this test run. Log messages are likely to have collided with each other. diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index d17a6d6dc..f9527e22f 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -63,9 +63,11 @@ stop_test_callback :: proc "c" (sig: libc.int) { // NOTE(Feoramund): Using these write calls in a signal handler is // undefined behavior in C99 but possibly tolerated in POSIX 2008. // Either way, we may as well try to salvage what we can. - show_cursor := ansi.CSI + ansi.DECTCEM_SHOW - libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout) - libc.fflush(libc.stdout) + if !global_ansi_disabled { + show_cursor := ansi.CSI + ansi.DECTCEM_SHOW + libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout) + libc.fflush(libc.stdout) + } // This is an attempt at being compliant by avoiding printf. sigbuf: [8]byte From b6f1821bbabbb711724d309e4c62d4c866d44c67 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 21 May 2025 05:20:58 -0400 Subject: [PATCH 08/34] Fix terminal detection on Windows --- core/terminal/terminal_windows.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/terminal/terminal_windows.odin b/core/terminal/terminal_windows.odin index caab87cc7..55a3903fe 100644 --- a/core/terminal/terminal_windows.odin +++ b/core/terminal/terminal_windows.odin @@ -4,6 +4,6 @@ import "core:os" import "core:sys/windows" _is_terminal :: proc(handle: os.Handle) -> bool { - mode: windows.DWORD - return bool(windows.GetConsoleMode(windows.HANDLE(handle), &mode)) + is_tty := windows.GetFileType(windows.HANDLE(handle)) == windows.FILE_TYPE_CHAR + return is_tty } From e659df1a3f1b57ee67600cdacf75e672d8cd3d9b Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 21 May 2025 07:49:08 -0400 Subject: [PATCH 09/34] Restructure `core:terminal` for better Windows support --- core/terminal/internal.odin | 87 +++++++++++++++++++++++++++++ core/terminal/terminal.odin | 78 ++------------------------ core/terminal/terminal_posix.odin | 7 +++ core/terminal/terminal_windows.odin | 51 +++++++++++++++++ core/testing/runner.odin | 8 --- core/testing/runner_windows.odin | 36 ------------ 6 files changed, 150 insertions(+), 117 deletions(-) create mode 100644 core/terminal/internal.odin delete mode 100644 core/testing/runner_windows.odin diff --git a/core/terminal/internal.odin b/core/terminal/internal.odin new file mode 100644 index 000000000..485f6868d --- /dev/null +++ b/core/terminal/internal.odin @@ -0,0 +1,87 @@ +#+private +package terminal + +import "core:os" +import "core:strings" + +// Reference documentation: +// +// - [[ https://no-color.org/ ]] +// - [[ https://github.com/termstandard/colors ]] +// - [[ https://invisible-island.net/ncurses/terminfo.src.html ]] + +get_no_color :: proc() -> bool { + if no_color, ok := os.lookup_env("NO_COLOR"); ok { + defer delete(no_color) + return no_color != "" + } + return false +} + +get_environment_color :: proc() -> Color_Depth { + // `COLORTERM` is non-standard but widespread and unambiguous. + if colorterm, ok := os.lookup_env("COLORTERM"); ok { + defer delete(colorterm) + // These are the only values that are typically advertised that have + // anything to do with color depth. + if colorterm == "truecolor" || colorterm == "24bit" { + return .True_Color + } + } + + if term, ok := os.lookup_env("TERM"); ok { + defer delete(term) + if strings.contains(term, "-truecolor") { + return .True_Color + } + if strings.contains(term, "-256color") { + return .Eight_Bit + } + if strings.contains(term, "-16color") { + return .Four_Bit + } + + // The `terminfo` database, which is stored in binary on *nix + // platforms, has an undocumented format that is not guaranteed to be + // portable, so beyond this point, we can only make safe assumptions. + // + // This section should only be necessary for terminals that do not + // define any of the previous environment values. + // + // Only a small sampling of some common values are checked here. + switch term { + case "ansi": fallthrough + case "konsole": fallthrough + case "putty": fallthrough + case "rxvt": fallthrough + case "rxvt-color": fallthrough + case "screen": fallthrough + case "st": fallthrough + case "tmux": fallthrough + case "vte": fallthrough + case "xterm": fallthrough + case "xterm-color": + return .Three_Bit + } + } + + return .None +} + +@(init) +init_terminal :: proc() { + _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 + // Terminal Processing for non-color control sequences. + if !get_no_color() { + color_enabled = color_depth > .None + } +} + +@(fini) +fini_terminal :: proc() { + _fini_terminal() +} diff --git a/core/terminal/terminal.odin b/core/terminal/terminal.odin index fae6f880a..1e5566295 100644 --- a/core/terminal/terminal.odin +++ b/core/terminal/terminal.odin @@ -1,7 +1,6 @@ package terminal import "core:os" -import "core:strings" /* This describes the range of colors that a terminal is capable of supporting. @@ -25,80 +24,13 @@ is_terminal :: proc(handle: os.Handle) -> bool { return _is_terminal(handle) } -/* -Get the color depth support for the terminal. -*/ -@(require_results) -get_color_depth :: proc() -> Color_Depth { - // Reference documentation: - // - // - [[ https://no-color.org/ ]] - // - [[ https://github.com/termstandard/colors ]] - // - [[ https://invisible-island.net/ncurses/terminfo.src.html ]] - - // Respect `NO_COLOR` above all. - if no_color, ok := os.lookup_env("NO_COLOR"); ok { - defer delete(no_color) - if no_color != "" { - return .None - } - } - - // `COLORTERM` is non-standard but widespread and unambiguous. - if colorterm, ok := os.lookup_env("COLORTERM"); ok { - defer delete(colorterm) - // These are the only values that are typically advertised that have - // anything to do with color depth. - if colorterm == "truecolor" || colorterm == "24bit" { - return .True_Color - } - } - - if term, ok := os.lookup_env("TERM"); ok { - defer delete(term) - if strings.contains(term, "-truecolor") { - return .True_Color - } - if strings.contains(term, "-256color") { - return .Eight_Bit - } - if strings.contains(term, "-16color") { - return .Four_Bit - } - - // The `terminfo` database, which is stored in binary on *nix - // platforms, has an undocumented format that is not guaranteed to be - // portable, so beyond this point, we can only make safe assumptions. - // - // This section should only be necessary for terminals that do not - // define any of the previous environment values. - // - // Only a small sampling of some common values are checked here. - switch term { - case "ansi": fallthrough - case "konsole": fallthrough - case "putty": fallthrough - case "rxvt": fallthrough - case "rxvt-color": fallthrough - case "screen": fallthrough - case "st": fallthrough - case "tmux": fallthrough - case "vte": fallthrough - case "xterm": fallthrough - case "xterm-color": - return .Three_Bit - } - } - - return .None -} - /* This is true if the terminal is accepting any form of colored text output. */ color_enabled: bool -@(init, private) -init_terminal_status :: proc() { - color_enabled = get_color_depth() > .None -} +/* +This value reports the color depth support as reported by the terminal at the +start of the program. +*/ +color_depth: Color_Depth diff --git a/core/terminal/terminal_posix.odin b/core/terminal/terminal_posix.odin index adfb6a0da..f578e12c6 100644 --- a/core/terminal/terminal_posix.odin +++ b/core/terminal/terminal_posix.odin @@ -1,3 +1,4 @@ +#+private #+build linux, darwin, netbsd, openbsd, freebsd, haiku package terminal @@ -7,3 +8,9 @@ import "core:sys/posix" _is_terminal :: proc(handle: os.Handle) -> bool { return bool(posix.isatty(posix.FD(handle))) } + +_init_terminal :: proc() { + color_depth = get_environment_color() +} + +_fini_terminal :: proc() { } diff --git a/core/terminal/terminal_windows.odin b/core/terminal/terminal_windows.odin index 55a3903fe..cc28add98 100644 --- a/core/terminal/terminal_windows.odin +++ b/core/terminal/terminal_windows.odin @@ -1,3 +1,4 @@ +#+private package terminal import "core:os" @@ -7,3 +8,53 @@ _is_terminal :: proc(handle: os.Handle) -> bool { is_tty := windows.GetFileType(windows.HANDLE(handle)) == windows.FILE_TYPE_CHAR return is_tty } + +old_modes: [2]struct{ + handle: windows.DWORD, + mode: windows.DWORD, +} = { + {windows.STD_OUTPUT_HANDLE, 0}, + {windows.STD_ERROR_HANDLE, 0}, +} + +@(init) +_init_terminal :: proc() { + vtp_enabled: bool + + for &v in old_modes { + handle := windows.GetStdHandle(v.handle) + if handle == windows.INVALID_HANDLE || handle == nil { + return + } + if windows.GetConsoleMode(handle, &v.mode) { + windows.SetConsoleMode(handle, v.mode | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) + + new_mode: windows.DWORD + windows.GetConsoleMode(handle, &new_mode) + + if new_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0 { + vtp_enabled = true + } + } + } + + if vtp_enabled { + // This color depth is available on Windows 10 since build 10586. + color_depth = .Four_Bit + } else { + // The user may be on a non-default terminal emulator. + color_depth = get_environment_color() + } +} + +@(fini) +_fini_terminal :: proc() { + for v in old_modes { + handle := windows.GetStdHandle(v.handle) + if handle == windows.INVALID_HANDLE || handle == nil { + return + } + + windows.SetConsoleMode(handle, v.mode) + } +} diff --git a/core/testing/runner.odin b/core/testing/runner.odin index a184eb28c..56d561d3d 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -214,10 +214,6 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } - when ODIN_OS == .Windows { - console_ansi_init() - } - stdout := io.to_writer(os.stream_from_handle(os.stdout)) stderr := io.to_writer(os.stream_from_handle(os.stderr)) @@ -981,9 +977,5 @@ To partly mitigate this, redirect STDERR to a file or use the -define:ODIN_TEST_ fmt.assertf(err == nil, "Error writing JSON report: %v", err) } - when ODIN_OS == .Windows { - console_ansi_fini() - } - return total_success_count == total_test_count } diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin deleted file mode 100644 index b35914c72..000000000 --- a/core/testing/runner_windows.odin +++ /dev/null @@ -1,36 +0,0 @@ -#+private -package testing - -import win32 "core:sys/windows" - -old_stdout_mode: u32 -old_stderr_mode: u32 - -console_ansi_init :: proc() { - stdout := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE) - if stdout != win32.INVALID_HANDLE && stdout != nil { - if win32.GetConsoleMode(stdout, &old_stdout_mode) { - win32.SetConsoleMode(stdout, old_stdout_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING) - } - } - - stderr := win32.GetStdHandle(win32.STD_ERROR_HANDLE) - if stderr != win32.INVALID_HANDLE && stderr != nil { - if win32.GetConsoleMode(stderr, &old_stderr_mode) { - win32.SetConsoleMode(stderr, old_stderr_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING) - } - } -} - -// Restore the cursor on exit -console_ansi_fini :: proc() { - stdout := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE) - if stdout != win32.INVALID_HANDLE && stdout != nil { - win32.SetConsoleMode(stdout, old_stdout_mode) - } - - stderr := win32.GetStdHandle(win32.STD_ERROR_HANDLE) - if stderr != win32.INVALID_HANDLE && stderr != nil { - win32.SetConsoleMode(stderr, old_stderr_mode) - } -} \ No newline at end of file From 899cfe9c3786f8b4807a415d0d13abe9e5e6ad96 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 21 May 2025 08:40:19 -0400 Subject: [PATCH 10/34] Also use `ENABLE_PROCESSED_OUTPUT` on Windows terminals This is specified to be necessary when using `ENABLE_VIRTUAL_TERMINAL_PROCESSING`. --- core/terminal/terminal_windows.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/terminal/terminal_windows.odin b/core/terminal/terminal_windows.odin index cc28add98..18ec98332 100644 --- a/core/terminal/terminal_windows.odin +++ b/core/terminal/terminal_windows.odin @@ -27,12 +27,12 @@ _init_terminal :: proc() { return } if windows.GetConsoleMode(handle, &v.mode) { - windows.SetConsoleMode(handle, v.mode | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) + windows.SetConsoleMode(handle, v.mode | windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) new_mode: windows.DWORD windows.GetConsoleMode(handle, &new_mode) - if new_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0 { + if new_mode & (windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0 { vtp_enabled = true } } From 1662ab10af9187a91a8b26867074a797237aa28a Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 21 May 2025 09:26:24 -0400 Subject: [PATCH 11/34] Fix off-by-one error in `priority_queue.remove` --- core/container/priority_queue/priority_queue.odin | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/core/container/priority_queue/priority_queue.odin b/core/container/priority_queue/priority_queue.odin index 7387a8d09..c62a821f4 100644 --- a/core/container/priority_queue/priority_queue.odin +++ b/core/container/priority_queue/priority_queue.odin @@ -133,12 +133,10 @@ pop_safe :: proc(pq: ^$Q/Priority_Queue($T), loc := #caller_location) -> (value: remove :: proc(pq: ^$Q/Priority_Queue($T), i: int) -> (value: T, ok: bool) { n := builtin.len(pq.queue) if 0 <= i && i < n { - if n != i { - pq.swap(pq.queue[:], i, n) - _shift_down(pq, i, n) - _shift_up(pq, i) - } - value, ok = builtin.pop_safe(&pq.queue) + pq.swap(pq.queue[:], i, n-1) + _shift_down(pq, i, n-1) + _shift_up(pq, i) + value, ok = builtin.pop(&pq.queue), true } return } From 96fd07e0eec5e75ab746efc9b0fe2d46caa52950 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 21 May 2025 19:20:58 +0200 Subject: [PATCH 12/34] Fix #5177 - Tweak error messages. --- src/check_expr.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 167052772..8721aab5d 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -5461,6 +5461,16 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod } } + if (operand->type && is_type_simd_vector(type_deref(operand->type))) { + String field_name = selector->Ident.token.string; + if (field_name.len == 1) { + error(op_expr, "Extracting an element from a #simd array using .%.*s syntax is disallowed, prefer `simd.extract`", LIT(field_name)); + } else { + error(op_expr, "Extracting elements from a #simd array using .%.*s syntax is disallowed, prefer `swizzle`", LIT(field_name)); + } + return nullptr; + } + if (entity == nullptr && selector->kind == Ast_Ident && operand->type != nullptr && (is_type_array(type_deref(operand->type)) || is_type_simd_vector(type_deref(operand->type)))) { String field_name = selector->Ident.token.string; From 95183e4b9ce7f5cecf652e129ace6b7b15362167 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 21 May 2025 19:37:09 +0200 Subject: [PATCH 13/34] Remove now unnecessary checks. --- src/check_expr.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 8721aab5d..95c898adf 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -5472,7 +5472,7 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod } if (entity == nullptr && selector->kind == Ast_Ident && operand->type != nullptr && - (is_type_array(type_deref(operand->type)) || is_type_simd_vector(type_deref(operand->type)))) { + (is_type_array(type_deref(operand->type)))) { String field_name = selector->Ident.token.string; if (1 < field_name.len && field_name.len <= 4) { u8 swizzles_xyzw[4] = {'x', 'y', 'z', 'w'}; @@ -5527,7 +5527,7 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod Type *original_type = operand->type; Type *array_type = base_type(type_deref(original_type)); - GB_ASSERT(array_type->kind == Type_Array || array_type->kind == Type_SimdVector); + GB_ASSERT(array_type->kind == Type_Array); i64 array_count = get_array_type_count(array_type); @@ -5568,10 +5568,6 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod break; } - if (array_type->kind == Type_SimdVector) { - operand->mode = Addressing_Value; - } - Entity *swizzle_entity = alloc_entity_variable(nullptr, make_token_ident(field_name), operand->type, EntityState_Resolved); add_type_and_value(c, operand->expr, operand->mode, operand->type, operand->value); return swizzle_entity; From c32b7ba593f78469d12a3ffdf333e7fb54116316 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 21 May 2025 20:24:27 +0200 Subject: [PATCH 14/34] List -subtarget in `odin help build` --- src/main.cpp | 115 +++++++++++++++++++++++++++++---------------------- 1 file changed, 65 insertions(+), 50 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 3692e4f06..bc57c677e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2364,20 +2364,20 @@ gb_internal int print_show_help(String const arg0, String command, String option if (print_flag("-build-mode:")) { print_usage_line(2, "Sets the build mode."); print_usage_line(2, "Available options:"); - print_usage_line(3, "-build-mode:exe Builds as an executable."); - print_usage_line(3, "-build-mode:test Builds as an executable that executes tests."); - print_usage_line(3, "-build-mode:dll Builds as a dynamically linked library."); - print_usage_line(3, "-build-mode:shared Builds as a dynamically linked library."); - print_usage_line(3, "-build-mode:dynamic Builds as a dynamically linked library."); - print_usage_line(3, "-build-mode:lib Builds as a statically linked library."); - print_usage_line(3, "-build-mode:static Builds as a statically linked library."); - print_usage_line(3, "-build-mode:obj Builds as an object file."); - print_usage_line(3, "-build-mode:object Builds as an object file."); - print_usage_line(3, "-build-mode:assembly Builds as an assembly file."); - print_usage_line(3, "-build-mode:assembler Builds as an assembly file."); - print_usage_line(3, "-build-mode:asm Builds as an assembly file."); - print_usage_line(3, "-build-mode:llvm-ir Builds as an LLVM IR file."); - print_usage_line(3, "-build-mode:llvm Builds as an LLVM IR file."); + print_usage_line(3, "-build-mode:exe Builds as an executable."); + print_usage_line(3, "-build-mode:test Builds as an executable that executes tests."); + print_usage_line(3, "-build-mode:dll Builds as a dynamically linked library."); + print_usage_line(3, "-build-mode:shared Builds as a dynamically linked library."); + print_usage_line(3, "-build-mode:dynamic Builds as a dynamically linked library."); + print_usage_line(3, "-build-mode:lib Builds as a statically linked library."); + print_usage_line(3, "-build-mode:static Builds as a statically linked library."); + print_usage_line(3, "-build-mode:obj Builds as an object file."); + print_usage_line(3, "-build-mode:object Builds as an object file."); + print_usage_line(3, "-build-mode:assembly Builds as an assembly file."); + print_usage_line(3, "-build-mode:assembler Builds as an assembly file."); + print_usage_line(3, "-build-mode:asm Builds as an assembly file."); + print_usage_line(3, "-build-mode:llvm-ir Builds as an LLVM IR file."); + print_usage_line(3, "-build-mode:llvm Builds as an LLVM IR file."); } } @@ -2386,16 +2386,16 @@ gb_internal int print_show_help(String const arg0, String command, String option print_usage_line(2, "Defines a library collection used for imports."); print_usage_line(2, "Example: -collection:shared=dir/to/shared"); print_usage_line(2, "Usage in Code:"); - print_usage_line(3, "import \"shared:foo\""); + print_usage_line(3, "import \"shared:foo\""); } if (print_flag("-custom-attribute:")) { print_usage_line(2, "Add a custom attribute which will be ignored if it is unknown."); print_usage_line(2, "This can be used with metaprogramming tools."); print_usage_line(2, "Examples:"); - print_usage_line(3, "-custom-attribute:my_tag"); - print_usage_line(3, "-custom-attribute:my_tag,the_other_thing"); - print_usage_line(3, "-custom-attribute:my_tag -custom-attribute:the_other_thing"); + print_usage_line(3, "-custom-attribute:my_tag"); + print_usage_line(3, "-custom-attribute:my_tag,the_other_thing"); + print_usage_line(3, "-custom-attribute:my_tag -custom-attribute:the_other_thing"); } } @@ -2418,7 +2418,7 @@ gb_internal int print_show_help(String const arg0, String command, String option print_usage_line(2, "Defines a scalar boolean, integer or string as global constant."); print_usage_line(2, "Example: -define:SPAM=123"); print_usage_line(2, "Usage in code:"); - print_usage_line(3, "#config(SPAM, default_value)"); + print_usage_line(3, "#config(SPAM, default_value)"); } } @@ -2453,9 +2453,9 @@ gb_internal int print_show_help(String const arg0, String command, String option if (check) { if (print_flag("-error-pos-style:")) { print_usage_line(2, "Available options:"); - print_usage_line(3, "-error-pos-style:unix file/path:45:3:"); - print_usage_line(3, "-error-pos-style:odin file/path(45:3)"); - print_usage_line(3, "-error-pos-style:default (Defaults to 'odin'.)"); + print_usage_line(3, "-error-pos-style:unix file/path:45:3:"); + print_usage_line(3, "-error-pos-style:odin file/path(45:3)"); + print_usage_line(3, "-error-pos-style:default (Defaults to 'odin'.)"); } if (print_flag("-export-defineables:")) { @@ -2466,8 +2466,8 @@ gb_internal int print_show_help(String const arg0, String command, String option if (print_flag("-export-dependencies:")) { print_usage_line(2, "Exports dependencies to one of a few formats. Requires `-export-dependencies-file`."); print_usage_line(2, "Available options:"); - print_usage_line(3, "-export-dependencies:make Exports in Makefile format"); - print_usage_line(3, "-export-dependencies:json Exports in JSON format"); + print_usage_line(3, "-export-dependencies:make Exports in Makefile format"); + print_usage_line(3, "-export-dependencies:json Exports in JSON format"); } if (print_flag("-export-dependencies-file:")) { @@ -2478,8 +2478,8 @@ gb_internal int print_show_help(String const arg0, String command, String option if (print_flag("-export-timings:")) { print_usage_line(2, "Exports timings to one of a few formats. Requires `-show-timings` or `-show-more-timings`."); print_usage_line(2, "Available options:"); - print_usage_line(3, "-export-timings:json Exports compile time stats to JSON."); - print_usage_line(3, "-export-timings:csv Exports compile time stats to CSV."); + print_usage_line(3, "-export-timings:json Exports compile time stats to JSON."); + print_usage_line(3, "-export-timings:csv Exports compile time stats to CSV."); } if (print_flag("-export-timings-file:")) { @@ -2569,9 +2569,9 @@ gb_internal int print_show_help(String const arg0, String command, String option if (print_flag("-microarch:")) { print_usage_line(2, "Specifies the specific micro-architecture for the build in a string."); print_usage_line(2, "Examples:"); - print_usage_line(3, "-microarch:sandybridge"); - print_usage_line(3, "-microarch:native"); - print_usage_line(3, "-microarch:\"?\" for a list"); + print_usage_line(3, "-microarch:sandybridge"); + print_usage_line(3, "-microarch:native"); + print_usage_line(3, "-microarch:\"?\" for a list"); } } @@ -2628,10 +2628,10 @@ gb_internal int print_show_help(String const arg0, String command, String option if (print_flag("-o:")) { print_usage_line(2, "Sets the optimization mode for compilation."); print_usage_line(2, "Available options:"); - print_usage_line(3, "-o:none"); - print_usage_line(3, "-o:minimal"); - print_usage_line(3, "-o:size"); - print_usage_line(3, "-o:speed"); + print_usage_line(3, "-o:none"); + print_usage_line(3, "-o:minimal"); + print_usage_line(3, "-o:size"); + print_usage_line(3, "-o:speed"); if (LB_USE_NEW_PASS_SYSTEM) { print_usage_line(3, "-o:aggressive (use this with caution)"); } @@ -2682,10 +2682,10 @@ gb_internal int print_show_help(String const arg0, String command, String option if (print_flag("-reloc-mode:")) { print_usage_line(2, "Specifies the reloc mode."); print_usage_line(2, "Available options:"); - print_usage_line(3, "-reloc-mode:default"); - print_usage_line(3, "-reloc-mode:static"); - print_usage_line(3, "-reloc-mode:pic"); - print_usage_line(3, "-reloc-mode:dynamic-no-pic"); + print_usage_line(3, "-reloc-mode:default"); + print_usage_line(3, "-reloc-mode:static"); + print_usage_line(3, "-reloc-mode:pic"); + print_usage_line(3, "-reloc-mode:dynamic-no-pic"); } #if defined(GB_SYSTEM_WINDOWS) @@ -2700,9 +2700,9 @@ gb_internal int print_show_help(String const arg0, String command, String option if (print_flag("-sanitize:")) { print_usage_line(2, "Enables sanitization analysis."); print_usage_line(2, "Available options:"); - print_usage_line(3, "-sanitize:address"); - print_usage_line(3, "-sanitize:memory"); - print_usage_line(3, "-sanitize:thread"); + print_usage_line(3, "-sanitize:address"); + print_usage_line(3, "-sanitize:memory"); + print_usage_line(3, "-sanitize:thread"); print_usage_line(2, "NOTE: This flag can be used multiple times."); } } @@ -2763,17 +2763,32 @@ gb_internal int print_show_help(String const arg0, String command, String option print_usage_line(2, "[Windows only]"); print_usage_line(2, "Defines the subsystem for the application."); print_usage_line(2, "Available options:"); - print_usage_line(3, "-subsystem:console"); - print_usage_line(3, "-subsystem:windows"); + print_usage_line(3, "-subsystem:console"); + print_usage_line(3, "-subsystem:windows"); } #endif + } + if (build) { + if (print_flag("-subtarget")) { + print_usage_line(2, "[Darwin and Linux only]"); + print_usage_line(2, "Available subtargets:"); + String prefix = str_lit("-subtarget:"); + for (u32 i = 1; i < Subtarget_COUNT; i++) { + String name = subtarget_strings[i]; + String help_string = concatenate_strings(temporary_allocator(), prefix, name); + print_usage_line(3, (const char *)help_string.text); + } + } + } + + if (run_or_build) { if (print_flag("-target-features:")) { print_usage_line(2, "Specifies CPU features to enable on top of the enabled features implied by -microarch."); print_usage_line(2, "Examples:"); - print_usage_line(3, "-target-features:atomics"); - print_usage_line(3, "-target-features:\"sse2,aes\""); - print_usage_line(3, "-target-features:\"?\" for a list"); + print_usage_line(3, "-target-features:atomics"); + print_usage_line(3, "-target-features:\"sse2,aes\""); + print_usage_line(3, "-target-features:\"?\" for a list"); } } @@ -2810,11 +2825,11 @@ gb_internal int print_show_help(String const arg0, String command, String option if (print_flag("-vet")) { print_usage_line(2, "Does extra checks on the code."); print_usage_line(2, "Extra checks include:"); - print_usage_line(3, "-vet-unused"); - print_usage_line(3, "-vet-unused-variables"); - print_usage_line(3, "-vet-unused-imports"); - print_usage_line(3, "-vet-shadowing"); - print_usage_line(3, "-vet-using-stmt"); + print_usage_line(3, "-vet-unused"); + print_usage_line(3, "-vet-unused-variables"); + print_usage_line(3, "-vet-unused-imports"); + print_usage_line(3, "-vet-shadowing"); + print_usage_line(3, "-vet-using-stmt"); } if (print_flag("-vet-cast")) { From f8bbeb54d4a6ce1e2c17cec68bd6fbeb5e628121 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 21 May 2025 20:28:21 +0200 Subject: [PATCH 15/34] Slight tweak. --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index bc57c677e..90f2aad7a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2770,7 +2770,7 @@ gb_internal int print_show_help(String const arg0, String command, String option } if (build) { - if (print_flag("-subtarget")) { + if (print_flag("-subtarget:")) { print_usage_line(2, "[Darwin and Linux only]"); print_usage_line(2, "Available subtargets:"); String prefix = str_lit("-subtarget:"); From e35e1dcc7b4814a063c94b9bb02f1e371ae251a5 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 22 May 2025 08:08:53 -0400 Subject: [PATCH 16/34] Only trim `.odin` from build filenames --- src/build_settings.cpp | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 8364bbfbe..b3bbf726b 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -2209,11 +2209,34 @@ gb_internal bool init_build_paths(String init_filename) { while (output_name.len > 0 && (output_name[output_name.len-1] == '/' || output_name[output_name.len-1] == '\\')) { output_name.len -= 1; } + // Only trim the extension if it's an Odin source file. + // This lets people build folders with extensions or files beginning with dots. + if (path_extension(output_name) == ".odin" && !path_is_directory(output_name)) { + output_name = remove_extension_from_path(output_name); + } output_name = remove_directory_from_path(output_name); - output_name = remove_extension_from_path(output_name); output_name = copy_string(ha, string_trim_whitespace(output_name)); - output_path = path_from_string(ha, output_name); - + // This is `path_from_string` without the extension trimming. + Path res = {}; + if (output_name.len > 0) { + String fullpath = path_to_full_path(ha, output_name); + defer (gb_free(ha, fullpath.text)); + + res.basename = directory_from_path(fullpath); + res.basename = copy_string(ha, res.basename); + + if (path_is_directory(fullpath)) { + if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') { + res.basename.len--; + } + } else { + isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len; + res.name = substring(fullpath, name_start, fullpath.len); + res.name = copy_string(ha, res.name); + } + } + output_path = res; + // Note(Dragos): This is a fix for empty filenames // Turn the trailing folder into the file name if (output_path.name.len == 0) { From 713360a792ea54211624f7ac18e68c4c4a16aeb1 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 17 May 2025 18:15:08 -0400 Subject: [PATCH 17/34] Keep shared libraries from calling main program's startup/cleanup procs on Linux --- src/llvm_backend.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 7de147058..395238753 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1907,6 +1907,10 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc lb_add_attribute_to_proc(p->module, p->value, "optnone"); lb_add_attribute_to_proc(p->module, p->value, "noinline"); + // Make sure shared libraries call their own runtime startup on Linux. + LLVMSetVisibility(p->value, LLVMHiddenVisibility); + LLVMSetLinkage(p->value, LLVMWeakAnyLinkage); + lb_begin_procedure_body(p); lb_setup_type_info_data(main_module); @@ -2016,6 +2020,10 @@ gb_internal lbProcedure *lb_create_cleanup_runtime(lbModule *main_module) { // C lb_add_attribute_to_proc(p->module, p->value, "optnone"); lb_add_attribute_to_proc(p->module, p->value, "noinline"); + // Make sure shared libraries call their own runtime cleanup on Linux. + LLVMSetVisibility(p->value, LLVMHiddenVisibility); + LLVMSetLinkage(p->value, LLVMWeakAnyLinkage); + lb_begin_procedure_body(p); CheckerInfo *info = main_module->gen->info; From affced2d02090762dff8360d9357bfbbc8e18938 Mon Sep 17 00:00:00 2001 From: Hector Date: Thu, 22 May 2025 15:35:09 +0100 Subject: [PATCH 18/34] Added Semaphore API to the SDL3 vendor bindings --- vendor/sdl3/sdl3_mutex.odin | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/vendor/sdl3/sdl3_mutex.odin b/vendor/sdl3/sdl3_mutex.odin index ada8006bc..8067473f3 100644 --- a/vendor/sdl3/sdl3_mutex.odin +++ b/vendor/sdl3/sdl3_mutex.odin @@ -1,8 +1,8 @@ package sdl3 -Mutex :: struct {} -RWLock :: struct {} - +Mutex :: struct {} +RWLock :: struct {} +Semaphore :: struct {} @(default_calling_convention="c", link_prefix="SDL_", require_results) foreign lib { @@ -19,4 +19,12 @@ foreign lib { TryLockRWLockForWriting :: proc(rwlock: ^RWLock) -> bool --- UnlockRWLock :: proc(rwlock: ^RWLock) --- DestroyRWLock :: proc(rwlock: ^RWLock) --- -} \ No newline at end of file + + CreateSemaphore :: proc(initial_value: Uint32) -> ^Semaphore --- + DestroySemaphore :: proc(sem: ^Semaphore) --- + GetSemaphoreValue :: proc(sem: ^Semaphore) -> Uint32 --- + SignalSemaphore :: proc(sem: ^Semaphore) --- + TryWaitSemaphore :: proc(sem: ^Semaphore) -> bool --- + WaitSemaphore :: proc(sem: ^Semaphore) --- + WaitSemaphoreTimeout :: proc(sem: ^Semaphore, timeout_ms: Sint32) --- +} From 0536f8626833e6b2938cbedf84b2cf06c95c0ae0 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 22 May 2025 17:33:24 -0400 Subject: [PATCH 19/34] Add `-build-only` for `odin test` command This allows test executables to be only built, not run too. --- src/build_settings.cpp | 1 + src/main.cpp | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index b3bbf726b..ae6fa3463 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -441,6 +441,7 @@ struct BuildContext { String extra_assembler_flags; String microarch; BuildModeKind build_mode; + bool build_only; bool generate_docs; bool custom_optimization_level; i32 optimization_level; diff --git a/src/main.cpp b/src/main.cpp index 90f2aad7a..5aaadc0d2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -312,6 +312,7 @@ enum BuildFlagKind { BuildFlag_Collection, BuildFlag_Define, BuildFlag_BuildMode, + BuildFlag_BuildOnly, BuildFlag_Target, BuildFlag_Subtarget, BuildFlag_Debug, @@ -531,6 +532,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_Collection, str_lit("collection"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_Define, str_lit("define"), BuildFlagParam_String, Command__does_check, true); add_flag(&build_flags, BuildFlag_BuildMode, str_lit("build-mode"), BuildFlagParam_String, Command__does_build); // Commands_build is not used to allow for a better error message + add_flag(&build_flags, BuildFlag_BuildOnly, str_lit("build-only"), BuildFlagParam_None, Command_test); add_flag(&build_flags, BuildFlag_Target, str_lit("target"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_Subtarget, str_lit("subtarget"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_Debug, str_lit("debug"), BuildFlagParam_None, Command__does_check); @@ -1193,6 +1195,9 @@ gb_internal bool parse_build_flags(Array args) { break; } + case BuildFlag_BuildOnly: + build_context.build_only = true; + break; case BuildFlag_Debug: build_context.ODIN_DEBUG = true; @@ -2381,6 +2386,12 @@ gb_internal int print_show_help(String const arg0, String command, String option } } + if (test_only) { + if (print_flag("-build-only")) { + print_usage_line(2, "Only builds the test executable; does not automatically run it afterwards."); + } + } + if (check) { if (print_flag("-collection:=")) { print_usage_line(2, "Defines a library collection used for imports."); @@ -3861,7 +3872,7 @@ end_of_code_gen:; show_timings(checker, &global_timings); } - if (run_output) { + if (!build_context.build_only && run_output) { String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); defer (gb_free(heap_allocator(), exe_name.text)); From 5b5822effce461e3ecfdc3b57009736208999959 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 22 May 2025 17:54:15 -0400 Subject: [PATCH 20/34] Delete test executable after running, add `-keep-test-executable` --- src/build_settings.cpp | 1 + src/main.cpp | 30 +++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index ae6fa3463..4f573a8ca 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -442,6 +442,7 @@ struct BuildContext { String microarch; BuildModeKind build_mode; bool build_only; + bool keep_test_executable; bool generate_docs; bool custom_optimization_level; i32 optimization_level; diff --git a/src/main.cpp b/src/main.cpp index 5aaadc0d2..faedb9074 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -313,6 +313,7 @@ enum BuildFlagKind { BuildFlag_Define, BuildFlag_BuildMode, BuildFlag_BuildOnly, + BuildFlag_KeepTestExecutable, BuildFlag_Target, BuildFlag_Subtarget, BuildFlag_Debug, @@ -533,6 +534,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_Define, str_lit("define"), BuildFlagParam_String, Command__does_check, true); add_flag(&build_flags, BuildFlag_BuildMode, str_lit("build-mode"), BuildFlagParam_String, Command__does_build); // Commands_build is not used to allow for a better error message add_flag(&build_flags, BuildFlag_BuildOnly, str_lit("build-only"), BuildFlagParam_None, Command_test); + add_flag(&build_flags, BuildFlag_KeepTestExecutable, str_lit("keep-test-executable"), BuildFlagParam_None, Command_test); add_flag(&build_flags, BuildFlag_Target, str_lit("target"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_Subtarget, str_lit("subtarget"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_Debug, str_lit("debug"), BuildFlagParam_None, Command__does_check); @@ -1196,7 +1198,22 @@ gb_internal bool parse_build_flags(Array args) { break; } case BuildFlag_BuildOnly: - build_context.build_only = true; + if (build_context.keep_test_executable) { + gb_printf_err("`-keep-test-executable` is mutually exclusive with `-build-only`.\n"); + gb_printf_err("We either only build or run the test and optionally keep the executable.\n"); + bad_flags = true; + } else { + build_context.build_only = true; + } + break; + case BuildFlag_KeepTestExecutable: + if (build_context.build_only) { + gb_printf_err("`-build-only` is mutually exclusive with `-keep-test-executable`.\n"); + gb_printf_err("We either only build or run the test and optionally keep the executable.\n"); + bad_flags = true; + } else { + build_context.keep_test_executable = true; + } break; case BuildFlag_Debug: @@ -2554,6 +2571,12 @@ gb_internal int print_show_help(String const arg0, String command, String option } } + if (test_only) { + if (print_flag("-keep-test-executable")) { + print_usage_line(2, "Keep the test executable after running it instead of deleting it normally."); + } + } + if (run_or_build) { if (print_flag("-linker:")) { print_usage_line(2, "Specify the linker to use."); @@ -3877,6 +3900,11 @@ end_of_code_gen:; defer (gb_free(heap_allocator(), exe_name.text)); system_must_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string)); + + if (build_context.command_kind == Command_test && !build_context.keep_test_executable) { + char const *filename = cast(char const *)exe_name.text; + gb_file_remove(filename); + } } return 0; } From 433a21b6ffbdc2065b1568c2b6927fd7fc5d058d Mon Sep 17 00:00:00 2001 From: Gaia <83567777+mothfuzz@users.noreply.github.com> Date: Thu, 22 May 2025 19:46:31 -0500 Subject: [PATCH 21/34] Update wgpu.js mipmapFilter is being ignored and defaulting to .Nearest on web platforms due to incorrect capitalization of the field name --- vendor/wgpu/wgpu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/wgpu/wgpu.js b/vendor/wgpu/wgpu.js index 578c1aefc..6104417df 100644 --- a/vendor/wgpu/wgpu.js +++ b/vendor/wgpu/wgpu.js @@ -2033,7 +2033,7 @@ class WebGPUInterface { addressModeW: this.enumeration("AddressMode", off(4)), magFilter: this.enumeration("FilterMode", off(4)), minFilter: this.enumeration("FilterMode", off(4)), - mipMapFilter: this.enumeration("MipmapFilterMode", off(4)), + mipmapFilter: this.enumeration("MipmapFilterMode", off(4)), lodMinClamp: this.mem.loadF32(off(4)), lodMaxClamp: this.mem.loadF32(off(4)), compare: this.enumeration("CompareFunction", off(4)), From 6c5b96948e827a0b22435c1bb367414c8a187666 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 22 May 2025 20:47:10 -0400 Subject: [PATCH 22/34] Enable all sanitizers on FreeBSD --- src/build_settings.cpp | 12 ++++++------ src/linker.cpp | 12 ++++++++++++ src/llvm_backend.cpp | 20 ++++++++++++++++---- 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 4f573a8ca..9c530df19 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -2303,9 +2303,10 @@ gb_internal bool init_build_paths(String init_filename) { case TargetOs_windows: case TargetOs_linux: case TargetOs_darwin: + case TargetOs_freebsd: break; default: - gb_printf_err("-sanitize:address is only supported on windows, linux, and darwin\n"); + gb_printf_err("-sanitize:address is only supported on Windows, Linux, Darwin, and FreeBSD\n"); return false; } } @@ -2313,12 +2314,10 @@ gb_internal bool init_build_paths(String init_filename) { if (build_context.sanitizer_flags & SanitizerFlag_Memory) { switch (build_context.metrics.os) { case TargetOs_linux: + case TargetOs_freebsd: break; default: - gb_printf_err("-sanitize:memory is only supported on linux\n"); - return false; - } - if (build_context.metrics.os != TargetOs_linux) { + gb_printf_err("-sanitize:memory is only supported on Linux and FreeBSD\n"); return false; } } @@ -2327,9 +2326,10 @@ gb_internal bool init_build_paths(String init_filename) { switch (build_context.metrics.os) { case TargetOs_linux: case TargetOs_darwin: + case TargetOs_freebsd: break; default: - gb_printf_err("-sanitize:thread is only supported on linux and darwin\n"); + gb_printf_err("-sanitize:thread is only supported on Linux, Darwin, and FreeBSD\n"); return false; } } diff --git a/src/linker.cpp b/src/linker.cpp index 41d4a13a1..447d66d0a 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -802,6 +802,18 @@ try_cross_linking:; link_settings = gb_string_appendc(link_settings, "-e _main "); } } else if (build_context.metrics.os == TargetOs_freebsd) { + if (build_context.sanitizer_flags & (SanitizerFlag_Address | SanitizerFlag_Memory)) { + // It's imperative that `pthread` is linked before `libc`, + // otherwise ASan/MSan will be unable to call `pthread_key_create` + // because FreeBSD's `libthr` implementation of `pthread` + // needs to replace the relevant stubs first. + // + // (Presumably TSan implements its own `pthread` interface, + // which is why it isn't required.) + // + // See: https://reviews.llvm.org/D39254 + platform_lib_str = gb_string_appendc(platform_lib_str, "-lpthread "); + } // FreeBSD pkg installs third-party shared libraries in /usr/local/lib. platform_lib_str = gb_string_appendc(platform_lib_str, "-Wl,-L/usr/local/lib "); } else if (build_context.metrics.os == TargetOs_openbsd) { diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index f0bbe5473..90e7b2793 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3425,36 +3425,48 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { if (build_context.sanitizer_flags & SanitizerFlag_Address) { - if (build_context.metrics.os == TargetOs_windows) { + switch (build_context.metrics.os) { + case TargetOs_windows: { auto paths = array_make(heap_allocator(), 0, 1); String path = concatenate_strings(permanent_allocator(), build_context.ODIN_ROOT, str_lit("\\bin\\llvm\\windows\\clang_rt.asan-x86_64.lib")); array_add(&paths, path); Entity *lib = alloc_entity_library_name(nullptr, make_token_ident("asan_lib"), nullptr, slice_from_array(paths), str_lit("asan_lib")); array_add(&gen->foreign_libraries, lib); - } else if (build_context.metrics.os == TargetOs_darwin || build_context.metrics.os == TargetOs_linux) { + } break; + case TargetOs_darwin: + case TargetOs_linux: + case TargetOs_freebsd: if (!build_context.extra_linker_flags.text) { build_context.extra_linker_flags = str_lit("-fsanitize=address"); } else { build_context.extra_linker_flags = concatenate_strings(permanent_allocator(), build_context.extra_linker_flags, str_lit(" -fsanitize=address")); } + break; } } if (build_context.sanitizer_flags & SanitizerFlag_Memory) { - if (build_context.metrics.os == TargetOs_darwin || build_context.metrics.os == TargetOs_linux) { + switch (build_context.metrics.os) { + case TargetOs_linux: + case TargetOs_freebsd: if (!build_context.extra_linker_flags.text) { build_context.extra_linker_flags = str_lit("-fsanitize=memory"); } else { build_context.extra_linker_flags = concatenate_strings(permanent_allocator(), build_context.extra_linker_flags, str_lit(" -fsanitize=memory")); } + break; } } if (build_context.sanitizer_flags & SanitizerFlag_Thread) { - if (build_context.metrics.os == TargetOs_darwin || build_context.metrics.os == TargetOs_linux) { + switch (build_context.metrics.os) { + case TargetOs_darwin: + case TargetOs_linux: + case TargetOs_freebsd: if (!build_context.extra_linker_flags.text) { build_context.extra_linker_flags = str_lit("-fsanitize=thread"); } else { build_context.extra_linker_flags = concatenate_strings(permanent_allocator(), build_context.extra_linker_flags, str_lit(" -fsanitize=thread")); } + break; } } From 12167bace05915abda62c93881d711e993e062cd Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 23 May 2025 08:28:27 +0200 Subject: [PATCH 23/34] Tweak #5202 Back out the new `-build-only` for tests in favor of the more established `-build-mode:test`, but retain the new `-keep-test-executable` option and default cleanup of test executables. --- src/build_settings.cpp | 1 - src/main.cpp | 25 +++++-------------------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 9c530df19..ecbe0a45a 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -441,7 +441,6 @@ struct BuildContext { String extra_assembler_flags; String microarch; BuildModeKind build_mode; - bool build_only; bool keep_test_executable; bool generate_docs; bool custom_optimization_level; diff --git a/src/main.cpp b/src/main.cpp index faedb9074..e905d291d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -312,7 +312,6 @@ enum BuildFlagKind { BuildFlag_Collection, BuildFlag_Define, BuildFlag_BuildMode, - BuildFlag_BuildOnly, BuildFlag_KeepTestExecutable, BuildFlag_Target, BuildFlag_Subtarget, @@ -533,7 +532,6 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_Collection, str_lit("collection"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_Define, str_lit("define"), BuildFlagParam_String, Command__does_check, true); add_flag(&build_flags, BuildFlag_BuildMode, str_lit("build-mode"), BuildFlagParam_String, Command__does_build); // Commands_build is not used to allow for a better error message - add_flag(&build_flags, BuildFlag_BuildOnly, str_lit("build-only"), BuildFlagParam_None, Command_test); add_flag(&build_flags, BuildFlag_KeepTestExecutable, str_lit("keep-test-executable"), BuildFlagParam_None, Command_test); add_flag(&build_flags, BuildFlag_Target, str_lit("target"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_Subtarget, str_lit("subtarget"), BuildFlagParam_String, Command__does_check); @@ -1197,23 +1195,8 @@ gb_internal bool parse_build_flags(Array args) { break; } - case BuildFlag_BuildOnly: - if (build_context.keep_test_executable) { - gb_printf_err("`-keep-test-executable` is mutually exclusive with `-build-only`.\n"); - gb_printf_err("We either only build or run the test and optionally keep the executable.\n"); - bad_flags = true; - } else { - build_context.build_only = true; - } - break; case BuildFlag_KeepTestExecutable: - if (build_context.build_only) { - gb_printf_err("`-build-only` is mutually exclusive with `-keep-test-executable`.\n"); - gb_printf_err("We either only build or run the test and optionally keep the executable.\n"); - bad_flags = true; - } else { - build_context.keep_test_executable = true; - } + build_context.keep_test_executable = true; break; case BuildFlag_Debug: @@ -2573,7 +2556,9 @@ gb_internal int print_show_help(String const arg0, String command, String option if (test_only) { if (print_flag("-keep-test-executable")) { - print_usage_line(2, "Keep the test executable after running it instead of deleting it normally."); + print_usage_line(2, "Keep the test executable after running it with `odin test`, instead of deleting it after the test completes."); + print_usage_line(2, "If you build your your tests using `odin build -build-mode:test`, the compiler does not execute"); + print_usage_line(2, "the resulting test program, and this option is not applicable."); } } @@ -3895,7 +3880,7 @@ end_of_code_gen:; show_timings(checker, &global_timings); } - if (!build_context.build_only && run_output) { + if (run_output) { String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); defer (gb_free(heap_allocator(), exe_name.text)); From f716d4c88fcb1c073cb1a1d9d32f35cc2bcc4a77 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 23 May 2025 08:32:16 +0200 Subject: [PATCH 24/34] your your --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index e905d291d..d28faf77c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2557,7 +2557,7 @@ gb_internal int print_show_help(String const arg0, String command, String option if (test_only) { if (print_flag("-keep-test-executable")) { print_usage_line(2, "Keep the test executable after running it with `odin test`, instead of deleting it after the test completes."); - print_usage_line(2, "If you build your your tests using `odin build -build-mode:test`, the compiler does not execute"); + print_usage_line(2, "If you build your tests using `odin build -build-mode:test`, the compiler does not execute"); print_usage_line(2, "the resulting test program, and this option is not applicable."); } } From 84b140f963126391faba0a5e873f119803d80c3c Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 23 May 2025 08:47:48 +0200 Subject: [PATCH 25/34] Rename -keep-test-executable to -keep-executable --- src/build_settings.cpp | 2 +- src/main.cpp | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index ecbe0a45a..00594c1b4 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -441,7 +441,7 @@ struct BuildContext { String extra_assembler_flags; String microarch; BuildModeKind build_mode; - bool keep_test_executable; + bool keep_executable; bool generate_docs; bool custom_optimization_level; i32 optimization_level; diff --git a/src/main.cpp b/src/main.cpp index d28faf77c..1ffdd0dba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -312,7 +312,7 @@ enum BuildFlagKind { BuildFlag_Collection, BuildFlag_Define, BuildFlag_BuildMode, - BuildFlag_KeepTestExecutable, + BuildFlag_KeepExecutable, BuildFlag_Target, BuildFlag_Subtarget, BuildFlag_Debug, @@ -532,7 +532,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_Collection, str_lit("collection"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_Define, str_lit("define"), BuildFlagParam_String, Command__does_check, true); add_flag(&build_flags, BuildFlag_BuildMode, str_lit("build-mode"), BuildFlagParam_String, Command__does_build); // Commands_build is not used to allow for a better error message - add_flag(&build_flags, BuildFlag_KeepTestExecutable, str_lit("keep-test-executable"), BuildFlagParam_None, Command_test); + add_flag(&build_flags, BuildFlag_KeepExecutable, str_lit("keep-executable"), BuildFlagParam_None, Command__does_build | Command_test); add_flag(&build_flags, BuildFlag_Target, str_lit("target"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_Subtarget, str_lit("subtarget"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_Debug, str_lit("debug"), BuildFlagParam_None, Command__does_check); @@ -1195,8 +1195,8 @@ gb_internal bool parse_build_flags(Array args) { break; } - case BuildFlag_KeepTestExecutable: - build_context.keep_test_executable = true; + case BuildFlag_KeepExecutable: + build_context.keep_executable = true; break; case BuildFlag_Debug: @@ -2554,11 +2554,11 @@ gb_internal int print_show_help(String const arg0, String command, String option } } - if (test_only) { - if (print_flag("-keep-test-executable")) { - print_usage_line(2, "Keep the test executable after running it with `odin test`, instead of deleting it after the test completes."); - print_usage_line(2, "If you build your tests using `odin build -build-mode:test`, the compiler does not execute"); - print_usage_line(2, "the resulting test program, and this option is not applicable."); + if (test_only || run_or_build) { + if (print_flag("-keep-executable")) { + print_usage_line(2, "Keep the executable generated by `odin test` or `odin run` after running it. We clean it up by default."); + print_usage_line(2, "If you build your program or test using `odin build`, the compiler does not automatically execute"); + print_usage_line(2, "the resulting program, and this option is not applicable."); } } @@ -3886,7 +3886,7 @@ end_of_code_gen:; system_must_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string)); - if (build_context.command_kind == Command_test && !build_context.keep_test_executable) { + if (!build_context.keep_executable) { char const *filename = cast(char const *)exe_name.text; gb_file_remove(filename); } From 020dd57b06c37e443d109022f01bc5dbcb3501e5 Mon Sep 17 00:00:00 2001 From: HeHHeyboi <157625347+HeHHeyboi@users.noreply.github.com> Date: Fri, 23 May 2025 15:29:08 +0700 Subject: [PATCH 26/34] Correct RWwrite signature --- vendor/sdl2/sdl_rwops.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/sdl2/sdl_rwops.odin b/vendor/sdl2/sdl_rwops.odin index ca7fa0bea..d3986f4b3 100644 --- a/vendor/sdl2/sdl_rwops.odin +++ b/vendor/sdl2/sdl_rwops.odin @@ -87,7 +87,7 @@ foreign lib { RWseek :: proc(ctx: ^RWops, offset: i64, whence: c.int) -> i64 --- RWtell :: proc(ctx: ^RWops) -> i64 --- RWread :: proc(ctx: ^RWops, ptr: rawptr, size: c.size_t, maxnum: c.size_t) -> c.size_t --- - RWwrite :: proc(ctx: ^RWops, size: c.size_t, num: c.size_t) -> c.size_t --- + RWwrite :: proc(ctx: ^RWops, ptr: rawptr, size: c.size_t, num: c.size_t) -> c.size_t --- RWclose :: proc(ctx: ^RWops) -> c.int --- LoadFile_RW :: proc(src: ^RWops, datasize: ^c.size_t, freesrc: bool) -> rawptr --- From 3d60b219c115d3792899d46b8baa9f760f5028cd Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 23 May 2025 14:02:49 +0200 Subject: [PATCH 27/34] Allow text/scanner to scan 0h hex floats --- core/text/scanner/scanner.odin | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/text/scanner/scanner.odin b/core/text/scanner/scanner.odin index 24dbcc8a4..f32233396 100644 --- a/core/text/scanner/scanner.odin +++ b/core/text/scanner/scanner.odin @@ -285,6 +285,7 @@ scan_number :: proc(s: ^Scanner, ch: rune, seen_dot: bool) -> (rune, rune) { case 'o': return "octal literal" case 'z': return "dozenal literal" case 'x': return "hexadecimal literal" + case 'h': return "hexadecimal literal" } return "decimal literal" } @@ -360,7 +361,8 @@ scan_number :: proc(s: ^Scanner, ch: rune, seen_dot: bool) -> (rune, rune) { base, prefix = 12, 'z' case 'h': tok = Float - fallthrough + ch = advance(s) + base, prefix = 16, 'h' case 'x': ch = advance(s) base, prefix = 16, 'x' From 8b657379f335a511f35bb5ef551f8b8af49ddd96 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 23 May 2025 17:07:08 +0200 Subject: [PATCH 28/34] Typo fix --- core/text/scanner/scanner.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/text/scanner/scanner.odin b/core/text/scanner/scanner.odin index f32233396..96109f614 100644 --- a/core/text/scanner/scanner.odin +++ b/core/text/scanner/scanner.odin @@ -449,7 +449,7 @@ scan_string :: proc(s: ^Scanner, quote: rune) -> (n: int) { ch := advance(s) for ch != quote { if ch == '\n' || ch < 0 { - error(s, "literal no terminated") + error(s, "literal not terminated") return } if ch == '\\' { From fedb9efb413dda3f2d9d4bbb39050219f8c0f394 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Fri, 23 May 2025 20:20:59 -0400 Subject: [PATCH 29/34] Make RegEx VM restartable and fix iterator infinite loop --- core/text/regex/regex.odin | 67 +++++++++++++++++-- .../virtual_machine/virtual_machine.odin | 6 +- .../core/text/regex/test_core_text_regex.odin | 5 +- 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/core/text/regex/regex.odin b/core/text/regex/regex.odin index c805740f7..90aa34946 100644 --- a/core/text/regex/regex.odin +++ b/core/text/regex/regex.odin @@ -77,6 +77,8 @@ Match_Iterator :: struct { vm: virtual_machine.Machine, idx: int, temp: runtime.Allocator, + threads: int, + done: bool, } /* @@ -101,7 +103,6 @@ create :: proc( permanent_allocator := context.allocator, temporary_allocator := context.temp_allocator, ) -> (result: Regular_Expression, err: Error) { - // For the sake of speed and simplicity, we first run all the intermediate // processes such as parsing and compilation through the temporary // allocator. @@ -294,6 +295,7 @@ create_iterator :: proc( result.temp = temporary_allocator result.vm = virtual_machine.create(result.regex.program, str) result.vm.class_data = result.regex.class_data + result.threads = max(1, virtual_machine.opcode_count(result.vm.code) - 1) return } @@ -457,8 +459,27 @@ match_iterator :: proc(it: ^Match_Iterator) -> (result: Capture, index: int, ok: assert(len(it.capture.pos) >= common.MAX_CAPTURE_GROUPS, "Pre-allocated RegEx capture `pos` must be at least 10 elements long.") + // Guard against situations in which the iterator should finish. + if it.done { + return + } + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + if it.idx > 0 { + // Reset the state needed to `virtual_machine.run` again. + it.vm.top_thread = 0 + it.vm.current_rune = rune(0) + it.vm.current_rune_size = 0 + for i in 0.. (result: Capture, index: int, ok: } } + if !ok { + // Match failed, bail out. + return + } + + if it.vm.string_pointer == sp_before { + // The string pointer did not move, but there was a match. + // + // At this point, the pattern supplied to the iterator will infinitely + // loop if we do not intervene. + it.done = true + } + if it.vm.string_pointer == len(it.vm.memory) { + // The VM hit the end of the string. + // + // We do not check at the start, because a match of pattern `$` + // against string "" is valid and must return a match. + // + // This check prevents a double-match of `$` against a non-empty string. + it.done = true + } + str := string(it.vm.memory) num_groups: int @@ -488,9 +531,7 @@ match_iterator :: proc(it: ^Match_Iterator) -> (result: Capture, index: int, ok: num_groups = n } - defer if ok { - it.idx += 1 - } + defer it.idx += 1 if num_groups > 0 { result = {it.capture.pos[:num_groups], it.capture.groups[:num_groups]} @@ -504,8 +545,24 @@ match :: proc { match_iterator, } +/* +Reset an iterator, allowing it to be run again as if new. + +Inputs: +- it: The iterator to reset. +*/ reset :: proc(it: ^Match_Iterator) { - it.idx = 0 + it.done = false + it.idx = 0 + it.vm.string_pointer = 0 + + it.vm.top_thread = 0 + it.vm.current_rune = rune(0) + it.vm.current_rune_size = 0 + for i in 0.. (saved: ^[2 * common.MAX_CAPTURE_GROUPS]int, ok: bool) #no_bounds_check { when UNICODE_MODE { - vm.next_rune, vm.next_rune_size = utf8.decode_rune_in_string(vm.memory) + vm.next_rune, vm.next_rune_size = utf8.decode_rune_in_string(vm.memory[vm.string_pointer:]) } else { if len(vm.memory) > 0 { - vm.next_rune = cast(rune)vm.memory[0] + vm.next_rune = cast(rune)vm.memory[vm.string_pointer] vm.next_rune_size = 1 } } @@ -652,4 +652,4 @@ destroy :: proc(vm: Machine, allocator := context.allocator) { delete(vm.busy_map) free(vm.threads) free(vm.next_threads) -} \ No newline at end of file +} diff --git a/tests/core/text/regex/test_core_text_regex.odin b/tests/core/text/regex/test_core_text_regex.odin index 913e716e5..8b4e3f997 100644 --- a/tests/core/text/regex/test_core_text_regex.odin +++ b/tests/core/text/regex/test_core_text_regex.odin @@ -1119,7 +1119,7 @@ iterator_vectors := []Iterator_Test{ @test test_match_iterator :: proc(t: ^testing.T) { - for test in iterator_vectors { + vector: for test in iterator_vectors { it, err := regex.create_iterator(test.haystack, test.pattern, test.flags) defer regex.destroy(it) @@ -1128,7 +1128,8 @@ test_match_iterator :: proc(t: ^testing.T) { for capture, idx in regex.match(&it) { if idx >= len(test.expected) { - break + log.errorf("got more than expected number of captures for matching string %q against pattern %q\n\tidx %i = %v", test.haystack, test.pattern, idx, capture) + continue vector } check_capture(t, capture, test.expected[idx]) } From 37d6491300269502341d7f5ea455ae089a36ce06 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 24 May 2025 06:02:50 -0400 Subject: [PATCH 30/34] Remove `Global` RegEx flag, default to unanchored patterns --- core/text/regex/common/common.odin | 3 --- core/text/regex/compiler/compiler.odin | 14 +++++++---- core/text/regex/regex.odin | 4 ---- .../benchmark/text/regex/benchmark_regex.odin | 10 ++++---- .../core/text/regex/test_core_text_regex.odin | 24 +++++++++---------- 5 files changed, 27 insertions(+), 28 deletions(-) diff --git a/core/text/regex/common/common.odin b/core/text/regex/common/common.odin index 4a303e0a3..e60bef58f 100644 --- a/core/text/regex/common/common.odin +++ b/core/text/regex/common/common.odin @@ -15,8 +15,6 @@ MAX_PROGRAM_SIZE :: int(max(i16)) MAX_CLASSES :: int(max(u8)) Flag :: enum u8 { - // Global: try to match the pattern anywhere in the string. - Global, // Multiline: treat `^` and `$` as if they also match newlines. Multiline, // Case Insensitive: treat `a-z` as if it was also `A-Z`. @@ -36,7 +34,6 @@ Flags :: bit_set[Flag; u8] @(rodata) Flag_To_Letter := #sparse[Flag]u8 { - .Global = 'g', .Multiline = 'm', .Case_Insensitive = 'i', .Ignore_Whitespace = 'x', diff --git a/core/text/regex/compiler/compiler.odin b/core/text/regex/compiler/compiler.odin index b3ded0104..07ace7b5d 100644 --- a/core/text/regex/compiler/compiler.odin +++ b/core/text/regex/compiler/compiler.odin @@ -401,7 +401,7 @@ compile :: proc(tree: Node, flags: common.Flags) -> (code: Program, class_data: pc_open := 0 - add_global: if .Global in flags { + optimize_opening: { // Check if the opening to the pattern is predictable. // If so, use one of the optimized Wait opcodes. iter := virtual_machine.Opcode_Iterator{ code[:], 0 } @@ -412,7 +412,7 @@ compile :: proc(tree: Node, flags: common.Flags) -> (code: Program, class_data: pc_open += size_of(Opcode) inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open])) pc_open += size_of(u8) - break add_global + break optimize_opening case .Rune: operand := intrinsics.unaligned_load(cast(^rune)&code[pc+1]) @@ -420,24 +420,28 @@ compile :: proc(tree: Node, flags: common.Flags) -> (code: Program, class_data: pc_open += size_of(Opcode) inject_raw(&code, pc_open, operand) pc_open += size_of(rune) - break add_global + break optimize_opening case .Rune_Class: inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class) pc_open += size_of(Opcode) inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open])) pc_open += size_of(u8) - break add_global + break optimize_opening case .Rune_Class_Negated: inject_at(&code, pc_open, Opcode.Wait_For_Rune_Class_Negated) pc_open += size_of(Opcode) inject_at(&code, pc_open, Opcode(code[pc + size_of(Opcode) + pc_open])) pc_open += size_of(u8) - break add_global + break optimize_opening case .Save: continue + + case .Assert_Start: + break optimize_opening + case: break seek_loop } diff --git a/core/text/regex/regex.odin b/core/text/regex/regex.odin index 90aa34946..94a4b163a 100644 --- a/core/text/regex/regex.odin +++ b/core/text/regex/regex.odin @@ -167,7 +167,6 @@ to escape the delimiter if found in the middle of the string. All runes after the closing delimiter will be parsed as flags: -- 'g': Global - 'm': Multiline - 'i': Case_Insensitive - 'x': Ignore_Whitespace @@ -244,7 +243,6 @@ create_by_user :: proc( // to `end` here. for r in pattern[start + end:] { switch r { - case 'g': flags += { .Global } case 'm': flags += { .Multiline } case 'i': flags += { .Case_Insensitive } case 'x': flags += { .Ignore_Whitespace } @@ -283,8 +281,6 @@ create_iterator :: proc( permanent_allocator := context.allocator, temporary_allocator := context.temp_allocator, ) -> (result: Match_Iterator, err: Error) { - flags := flags - flags += {.Global} // We're iterating over a string, so the next match could start anywhere if .Multiline in flags { return {}, .Unsupported_Flag diff --git a/tests/benchmark/text/regex/benchmark_regex.odin b/tests/benchmark/text/regex/benchmark_regex.odin index 8d29888a3..73d19ad0f 100644 --- a/tests/benchmark/text/regex/benchmark_regex.odin +++ b/tests/benchmark/text/regex/benchmark_regex.odin @@ -103,9 +103,11 @@ expensive_for_backtrackers :: proc(t: ^testing.T) { @test global_capture_end_word :: proc(t: ^testing.T) { + // NOTE: The previous behavior of `.Global`, which was to automatically + // insert `.*?` at the start of the pattern, is now default. EXPR :: `Hellope World!` - rex, err := regex.create(EXPR, { .Global }) + rex, err := regex.create(EXPR, { /*.Global*/ }) if !testing.expect_value(t, err, nil) { return } @@ -145,7 +147,7 @@ global_capture_end_word_unicode :: proc(t: ^testing.T) { EXPR :: `こにちは` needle := string(EXPR) - rex, err := regex.create(EXPR, { .Global, .Unicode }) + rex, err := regex.create(EXPR, { /*.Global,*/ .Unicode }) if !testing.expect_value(t, err, nil) { return } @@ -185,7 +187,7 @@ global_capture_end_word_unicode :: proc(t: ^testing.T) { alternations :: proc(t: ^testing.T) { EXPR :: `a(?:bb|cc|dd|ee|ff)` - rex, err := regex.create(EXPR, { .No_Capture, .Global }) + rex, err := regex.create(EXPR, { .No_Capture, /*.Global*/ }) if !testing.expect_value(t, err, nil) { return } @@ -219,7 +221,7 @@ classes :: proc(t: ^testing.T) { EXPR :: `[\w\d]+` NEEDLE :: "0123456789abcdef" - rex, err := regex.create(EXPR, { .Global }) + rex, err := regex.create(EXPR, { /*.Global*/ }) if !testing.expect_value(t, err, nil) { return } diff --git a/tests/core/text/regex/test_core_text_regex.odin b/tests/core/text/regex/test_core_text_regex.odin index 8b4e3f997..696a2dc48 100644 --- a/tests/core/text/regex/test_core_text_regex.odin +++ b/tests/core/text/regex/test_core_text_regex.odin @@ -51,13 +51,13 @@ check_expression_with_flags :: proc(t: ^testing.T, pattern: string, flags: regex } check_expression :: proc(t: ^testing.T, pattern, haystack: string, needles: ..string, extra_flags := regex.Flags{}, loc := #caller_location) { - check_expression_with_flags(t, pattern, { .Global } + extra_flags, + check_expression_with_flags(t, pattern, extra_flags, haystack, ..needles, loc = loc) - check_expression_with_flags(t, pattern, { .Global, .No_Optimization } + extra_flags, + check_expression_with_flags(t, pattern, { .No_Optimization } + extra_flags, haystack, ..needles, loc = loc) - check_expression_with_flags(t, pattern, { .Global, .Unicode } + extra_flags, + check_expression_with_flags(t, pattern, { .Unicode } + extra_flags, haystack, ..needles, loc = loc) - check_expression_with_flags(t, pattern, { .Global, .Unicode, .No_Optimization } + extra_flags, + check_expression_with_flags(t, pattern, { .Unicode, .No_Optimization } + extra_flags, haystack, ..needles, loc = loc) } @@ -516,7 +516,7 @@ test_pos_index_explicitly :: proc(t: ^testing.T) { STR :: "This is an island." EXPR :: `\bis\b` - rex, err := regex.create(EXPR, { .Global }) + rex, err := regex.create(EXPR) if !testing.expect_value(t, err, nil) { return } @@ -642,9 +642,9 @@ test_unicode_explicitly :: proc(t: ^testing.T) { } { EXPR :: "こにちは!" - check_expression_with_flags(t, EXPR, { .Global, .Unicode }, + check_expression_with_flags(t, EXPR, { .Unicode }, "Hello こにちは!", "こにちは!") - check_expression_with_flags(t, EXPR, { .Global, .Unicode, .No_Optimization }, + check_expression_with_flags(t, EXPR, { .Unicode, .No_Optimization }, "Hello こにちは!", "こにちは!") } } @@ -901,12 +901,12 @@ test_everything_at_once :: proc(t: ^testing.T) { @test test_creation_from_user_string :: proc(t: ^testing.T) { { - USER_EXPR :: `/^hellope$/gmixun-` + USER_EXPR :: `/^hellope$/mixun-` STR :: "hellope" rex, err := regex.create_by_user(USER_EXPR) defer regex.destroy(rex) testing.expect_value(t, err, nil) - testing.expect_value(t, rex.flags, regex.Flags{ .Global, .Multiline, .Case_Insensitive, .Ignore_Whitespace, .Unicode, .No_Capture, .No_Optimization }) + testing.expect_value(t, rex.flags, regex.Flags{ .Multiline, .Case_Insensitive, .Ignore_Whitespace, .Unicode, .No_Capture, .No_Optimization }) _, ok := regex.match(rex, STR) testing.expectf(t, ok, "expected user-provided RegEx %v to match %q", rex, STR) @@ -1102,14 +1102,14 @@ Iterator_Test :: struct { iterator_vectors := []Iterator_Test{ { - `xxab32ab52xx`, `(ab\d{1})`, {}, // {.Global} implicitly added by the iterator + `xxab32ab52xx`, `(ab\d{1})`, {}, { {pos = {{2, 5}, {2, 5}}, groups = {"ab3", "ab3"}}, {pos = {{6, 9}, {6, 9}}, groups = {"ab5", "ab5"}}, }, }, { - `xxfoobarxfoobarxx`, `f(o)ob(ar)`, {.Global}, + `xxfoobarxfoobarxx`, `f(o)ob(ar)`, {}, { {pos = {{2, 8}, {3, 4}, {6, 8}}, groups = {"foobar", "o", "ar"}}, {pos = {{9, 15}, {10, 11}, {13, 15}}, groups = {"foobar", "o", "ar"}}, @@ -1135,4 +1135,4 @@ test_match_iterator :: proc(t: ^testing.T) { } testing.expect_value(t, it.idx, len(test.expected)) } -} \ No newline at end of file +} From 5d01acc04f5f90925c94a64dca0508d104b6241d Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 24 May 2025 06:33:16 -0400 Subject: [PATCH 31/34] Add more RegEx tests --- .../core/text/regex/test_core_text_regex.odin | 166 +++++++++++++++++- 1 file changed, 160 insertions(+), 6 deletions(-) diff --git a/tests/core/text/regex/test_core_text_regex.odin b/tests/core/text/regex/test_core_text_regex.odin index 696a2dc48..aed3091e1 100644 --- a/tests/core/text/regex/test_core_text_regex.odin +++ b/tests/core/text/regex/test_core_text_regex.odin @@ -72,17 +72,18 @@ expect_error :: proc(t: ^testing.T, pattern: string, expected_error: typeid, fla testing.expect_value(t, variant_ti, expected_ti, loc = loc) } -check_capture :: proc(t: ^testing.T, got, expected: regex.Capture, loc := #caller_location) { - testing.expect_value(t, len(got.pos), len(got.groups), loc = loc) - testing.expect_value(t, len(got.pos), len(expected.pos), loc = loc) - testing.expect_value(t, len(got.groups), len(expected.groups), loc = loc) +check_capture :: proc(t: ^testing.T, got, expected: regex.Capture, loc := #caller_location) -> (ok: bool) { + testing.expect_value(t, len(got.pos), len(got.groups), loc = loc) or_return + testing.expect_value(t, len(got.pos), len(expected.pos), loc = loc) or_return + testing.expect_value(t, len(got.groups), len(expected.groups), loc = loc) or_return if len(got.pos) == len(expected.pos) { for i in 0..= len(test.expected) { + log.errorf("got more than expected number of captures for matching string %q against pattern %q\n\tidx %i = %v", test.haystack, test.pattern, idx, capture) + break + } check_capture(t, capture, test.expected[idx]) } testing.expect_value(t, it.idx, len(test.expected)) + + // Do it again. + iterations := 0 + regex.reset(&it) + + // Mind that this loop can do nothing if it wasn't reset but leave us + // with the expected `idx` state. + // + // That's why we count iterations this time around. + for capture, idx in regex.match(&it) { + iterations += 1 + if idx >= len(test.expected) { + log.errorf("got more than expected number of captures for matching string %q against pattern %q\n\tidx %i = %v", test.haystack, test.pattern, idx, capture) + break + } + check_capture(t, capture, test.expected[idx]) + } + testing.expect_value(t, it.idx, len(test.expected)) + testing.expect_value(t, iterations, len(test.expected)) } } From 594f1b30b4a0eadba24a712963da7bd3fc87a8eb Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 24 May 2025 14:18:16 +0100 Subject: [PATCH 32/34] Add `Suggestion: 'context = runtime.default_context()'` --- src/check_expr.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 95c898adf..60a4d3a98 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -8076,7 +8076,9 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c if (pt->kind == Type_Proc && pt->Proc.calling_convention == ProcCC_Odin) { if ((c->scope->flags & ScopeFlag_ContextDefined) == 0) { + ERROR_BLOCK(); error(call, "'context' has not been defined within this scope, but is required for this procedure call"); + error_line("\tSuggestion: 'context = runtime.default_context()'"); } } From d402b7408ddc29ddd3e7fb260e43375df11c65e8 Mon Sep 17 00:00:00 2001 From: Barinzaya Date: Sat, 24 May 2025 11:31:37 -0400 Subject: [PATCH 33/34] Fix a range check in int_atoi in core:math/big. The check seems to have been assuming that rune comparisons are unsigned, but they're signed. This was causing an assertion failure for certain input characters (anything with an ASCII value less than '+'/43). --- core/math/big/radix.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/math/big/radix.odin b/core/math/big/radix.odin index a5100e478..a1c25b55e 100644 --- a/core/math/big/radix.odin +++ b/core/math/big/radix.odin @@ -280,7 +280,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context } pos := ch - '+' - if RADIX_TABLE_REVERSE_SIZE <= pos { + if RADIX_TABLE_REVERSE_SIZE <= u32(pos) { break } y := RADIX_TABLE_REVERSE[pos] From 655fab7227fbd92837c82fdbeea65c9121b0f70b Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 25 May 2025 19:43:10 +0200 Subject: [PATCH 34/34] Add core/hyperthread count for Windows and Linux (#5216) Add core/hyperthread count to `core:sys/info` for Windows and Linux. TODO: Linux RISCV, Linux ARM, Darwin, and the BSDs. --- core/crypto/_aes/hw_intel/api.odin | 2 +- .../_chacha20/simd128/chacha20_simd128.odin | 2 +- .../_chacha20/simd256/chacha20_simd256.odin | 2 +- core/crypto/sha2/sha2_impl_hw_intel.odin | 2 +- core/mem/tlsf/tlsf_internal.odin | 2 +- core/sys/freebsd/syscalls.odin | 24 ++++++------ core/sys/info/cpu_arm.odin | 16 +++++--- core/sys/info/cpu_darwin_arm64.odin | 2 +- core/sys/info/cpu_intel.odin | 28 ++++++++------ core/sys/info/cpu_linux_arm.odin | 2 +- core/sys/info/cpu_linux_intel.odin | 38 +++++++++++++++++++ core/sys/info/cpu_linux_riscv64.odin | 4 +- core/sys/info/cpu_riscv64.odin | 10 +++-- core/sys/info/cpu_windows.odin | 28 ++++++++++++++ core/sys/info/doc.odin | 14 ++++--- core/sys/windows/kernel32.odin | 3 +- 16 files changed, 129 insertions(+), 50 deletions(-) create mode 100644 core/sys/info/cpu_linux_intel.odin create mode 100644 core/sys/info/cpu_windows.odin diff --git a/core/crypto/_aes/hw_intel/api.odin b/core/crypto/_aes/hw_intel/api.odin index 52669cb35..502d56213 100644 --- a/core/crypto/_aes/hw_intel/api.odin +++ b/core/crypto/_aes/hw_intel/api.odin @@ -6,7 +6,7 @@ import "core:sys/info" // is_supported returns true iff hardware accelerated AES // is supported. is_supported :: proc "contextless" () -> bool { - features, ok := info.cpu_features.? + features, ok := info.cpu.features.? if !ok { return false } diff --git a/core/crypto/_chacha20/simd128/chacha20_simd128.odin b/core/crypto/_chacha20/simd128/chacha20_simd128.odin index cf78541d1..6b37b8d61 100644 --- a/core/crypto/_chacha20/simd128/chacha20_simd128.odin +++ b/core/crypto/_chacha20/simd128/chacha20_simd128.odin @@ -227,7 +227,7 @@ is_performant :: proc "contextless" () -> bool { req_features :: info.CPU_Features{.V} } - features, ok := info.cpu_features.? + features, ok := info.cpu.features.? if !ok { return false } diff --git a/core/crypto/_chacha20/simd256/chacha20_simd256.odin b/core/crypto/_chacha20/simd256/chacha20_simd256.odin index ccb02a947..12fffeb2f 100644 --- a/core/crypto/_chacha20/simd256/chacha20_simd256.odin +++ b/core/crypto/_chacha20/simd256/chacha20_simd256.odin @@ -41,7 +41,7 @@ _VEC_TWO: simd.u64x4 : {2, 0, 2, 0} is_performant :: proc "contextless" () -> bool { req_features :: info.CPU_Features{.avx, .avx2} - features, ok := info.cpu_features.? + features, ok := info.cpu.features.? if !ok { return false } diff --git a/core/crypto/sha2/sha2_impl_hw_intel.odin b/core/crypto/sha2/sha2_impl_hw_intel.odin index f16f353df..cb29a3a20 100644 --- a/core/crypto/sha2/sha2_impl_hw_intel.odin +++ b/core/crypto/sha2/sha2_impl_hw_intel.odin @@ -52,7 +52,7 @@ K_15 :: simd.u64x2{0xa4506ceb90befffa, 0xc67178f2bef9a3f7} // is_hardware_accelerated_256 returns true iff hardware accelerated // SHA-224/SHA-256 is supported. is_hardware_accelerated_256 :: proc "contextless" () -> bool { - features, ok := info.cpu_features.? + features, ok := info.cpu.features.? if !ok { return false } diff --git a/core/mem/tlsf/tlsf_internal.odin b/core/mem/tlsf/tlsf_internal.odin index 89b875679..460cc4fb6 100644 --- a/core/mem/tlsf/tlsf_internal.odin +++ b/core/mem/tlsf/tlsf_internal.odin @@ -477,7 +477,7 @@ block_mark_as_free :: proc(block: ^Block_Header) { } @(private, no_sanitize_address) -block_mark_as_used :: proc(block: ^Block_Header, ) { +block_mark_as_used :: proc(block: ^Block_Header) { next := block_next(block) block_set_prev_used(next) block_set_used(block) diff --git a/core/sys/freebsd/syscalls.odin b/core/sys/freebsd/syscalls.odin index 405d1e47c..96fd9ac3f 100644 --- a/core/sys/freebsd/syscalls.odin +++ b/core/sys/freebsd/syscalls.odin @@ -204,21 +204,21 @@ accept_nil :: proc "contextless" (s: Fd) -> (Fd, Errno) { accept :: proc { accept_T, accept_nil } getsockname_or_peername :: proc "contextless" (s: Fd, sockaddr: ^$T, is_peer: bool) -> Errno { - // sockaddr must contain a valid pointer, or this will segfault because - // we're telling the syscall that there's memory available to write to. - addrlen: socklen_t = size_of(T) + // sockaddr must contain a valid pointer, or this will segfault because + // we're telling the syscall that there's memory available to write to. + addrlen: socklen_t = size_of(T) - result, ok := intrinsics.syscall_bsd( - is_peer ? SYS_getpeername : SYS_getsockname, - cast(uintptr)s, - cast(uintptr)sockaddr, - cast(uintptr)&addrlen) + result, ok := intrinsics.syscall_bsd( + is_peer ? SYS_getpeername : SYS_getsockname, + cast(uintptr)s, + cast(uintptr)sockaddr, + cast(uintptr)&addrlen) - if !ok { - return cast(Errno)result - } + if !ok { + return cast(Errno)result + } - return nil + return nil } // Get name of connected peer diff --git a/core/sys/info/cpu_arm.odin b/core/sys/info/cpu_arm.odin index 960e55a56..8f5143394 100644 --- a/core/sys/info/cpu_arm.odin +++ b/core/sys/info/cpu_arm.odin @@ -40,9 +40,13 @@ CPU_Feature :: enum u64 { } CPU_Features :: distinct bit_set[CPU_Feature; u64] - -cpu_features: Maybe(CPU_Features) -cpu_name: Maybe(string) +CPU :: struct { + name: Maybe(string), + features: Maybe(CPU_Features), + physical_cores: int, + logical_cores: int, +} +cpu: CPU @(private) cpu_name_buf: [128]byte @@ -53,7 +57,7 @@ init_cpu_name :: proc "contextless" () { when ODIN_OS == .Darwin { if unix.sysctlbyname("machdep.cpu.brand_string", &cpu_name_buf) { - cpu_name = string(cstring(rawptr(&cpu_name_buf))) + cpu.name = string(cstring(rawptr(&cpu_name_buf))) generic = false } } @@ -61,10 +65,10 @@ init_cpu_name :: proc "contextless" () { if generic { when ODIN_ARCH == .arm64 { copy(cpu_name_buf[:], "ARM64") - cpu_name = string(cpu_name_buf[:len("ARM64")]) + cpu.name = string(cpu_name_buf[:len("ARM64")]) } else { copy(cpu_name_buf[:], "ARM") - cpu_name = string(cpu_name_buf[:len("ARM")]) + cpu.name = string(cpu_name_buf[:len("ARM")]) } } } diff --git a/core/sys/info/cpu_darwin_arm64.odin b/core/sys/info/cpu_darwin_arm64.odin index ffa60d1cb..aaeef9ad9 100644 --- a/core/sys/info/cpu_darwin_arm64.odin +++ b/core/sys/info/cpu_darwin_arm64.odin @@ -5,7 +5,7 @@ import "core:sys/unix" @(init, private) init_cpu_features :: proc "contextless" () { @(static) features: CPU_Features - defer cpu_features = features + defer cpu.features = features try_set :: proc "contextless" (name: cstring, feature: CPU_Feature) -> (ok: bool) { support: b32 diff --git a/core/sys/info/cpu_intel.odin b/core/sys/info/cpu_intel.odin index c8b8282fe..7c5b38ca4 100644 --- a/core/sys/info/cpu_intel.odin +++ b/core/sys/info/cpu_intel.odin @@ -3,12 +3,6 @@ package sysinfo import "base:intrinsics" -// cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) --- -cpuid :: intrinsics.x86_cpuid - -// xgetbv :: proc(cx: u32) -> (eax, edx: u32) --- -xgetbv :: intrinsics.x86_xgetbv - CPU_Feature :: enum u64 { aes, // AES hardware implementation (AES NI) adx, // Multi-precision add-carry instruction extensions @@ -49,9 +43,13 @@ CPU_Feature :: enum u64 { } CPU_Features :: distinct bit_set[CPU_Feature; u64] - -cpu_features: Maybe(CPU_Features) -cpu_name: Maybe(string) +CPU :: struct { + name: Maybe(string), + features: Maybe(CPU_Features), + physical_cores: int, // Initialized by cpu_.odin + logical_cores: int, // Initialized by cpu_.odin +} +cpu: CPU @(init, private) init_cpu_features :: proc "c" () { @@ -88,7 +86,7 @@ init_cpu_features :: proc "c" () { when ODIN_OS == .FreeBSD || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { // xgetbv is an illegal instruction under FreeBSD 13, OpenBSD 7.1 and NetBSD 10 // return before probing further - cpu_features = set + cpu.features = set return } @@ -151,7 +149,7 @@ init_cpu_features :: proc "c" () { try_set(&set, .rdseed, 18, ebx7) try_set(&set, .adx, 19, ebx7) - cpu_features = set + cpu.features = set } @(private) @@ -179,5 +177,11 @@ init_cpu_name :: proc "c" () { for len(brand) > 0 && brand[len(brand) - 1] == 0 || brand[len(brand) - 1] == ' ' { brand = brand[:len(brand) - 1] } - cpu_name = brand + cpu.name = brand } + +// cpuid :: proc(ax, cx: u32) -> (eax, ebc, ecx, edx: u32) --- +cpuid :: intrinsics.x86_cpuid + +// xgetbv :: proc(cx: u32) -> (eax, edx: u32) --- +xgetbv :: intrinsics.x86_xgetbv \ No newline at end of file diff --git a/core/sys/info/cpu_linux_arm.odin b/core/sys/info/cpu_linux_arm.odin index 6408decb7..cde76a83d 100644 --- a/core/sys/info/cpu_linux_arm.odin +++ b/core/sys/info/cpu_linux_arm.odin @@ -17,7 +17,7 @@ init_cpu_features :: proc() { if rerr != .NONE || n == 0 { return } features: CPU_Features - defer cpu_features = features + defer cpu.features = features str := string(buf[:n]) for line in strings.split_lines_iterator(&str) { diff --git a/core/sys/info/cpu_linux_intel.odin b/core/sys/info/cpu_linux_intel.odin new file mode 100644 index 000000000..e43737475 --- /dev/null +++ b/core/sys/info/cpu_linux_intel.odin @@ -0,0 +1,38 @@ +#+build i386, amd64 +#+build linux +package sysinfo + +import "core:sys/linux" +import "core:strings" +import "core:strconv" + +@(init, private) +init_cpu_core_count :: proc() { + fd, err := linux.open("/proc/cpuinfo", {}) + if err != .NONE { return } + defer linux.close(fd) + + // This is probably enough right? + buf: [4096]byte + n, rerr := linux.read(fd, buf[:]) + if rerr != .NONE || n == 0 { return } + + str := string(buf[:n]) + for line in strings.split_lines_iterator(&str) { + key, _, value := strings.partition(line, ":") + key = strings.trim_space(key) + value = strings.trim_space(value) + + if key == "cpu cores" { + if num_physical_cores, ok := strconv.parse_int(value); ok { + cpu.physical_cores = num_physical_cores + } + } + + if key == "siblings" { + if num_logical_cores, ok := strconv.parse_int(value); ok { + cpu.logical_cores = num_logical_cores + } + } + } +} \ No newline at end of file diff --git a/core/sys/info/cpu_linux_riscv64.odin b/core/sys/info/cpu_linux_riscv64.odin index 84f6134d4..3d36d126d 100644 --- a/core/sys/info/cpu_linux_riscv64.odin +++ b/core/sys/info/cpu_linux_riscv64.odin @@ -9,7 +9,7 @@ import "core:sys/linux" @(init, private) init_cpu_features :: proc() { _features: CPU_Features - defer cpu_features = _features + defer cpu.features = _features HWCAP_Bits :: enum u64 { I = 'I' - 'A', @@ -109,5 +109,5 @@ init_cpu_features :: proc() { @(init, private) init_cpu_name :: proc() { - cpu_name = "RISCV64" + cpu.name = "RISCV64" } diff --git a/core/sys/info/cpu_riscv64.odin b/core/sys/info/cpu_riscv64.odin index c3319c48c..64f2edfd3 100644 --- a/core/sys/info/cpu_riscv64.odin +++ b/core/sys/info/cpu_riscv64.odin @@ -95,6 +95,10 @@ CPU_Feature :: enum u64 { } CPU_Features :: distinct bit_set[CPU_Feature; u64] - -cpu_features: Maybe(CPU_Features) -cpu_name: Maybe(string) +CPU :: struct { + name: Maybe(string), + features: Maybe(CPU_Features), + physical_cores: int, + logical_cores: int, +} +cpu: CPU \ No newline at end of file diff --git a/core/sys/info/cpu_windows.odin b/core/sys/info/cpu_windows.odin new file mode 100644 index 000000000..7dd2d2a8c --- /dev/null +++ b/core/sys/info/cpu_windows.odin @@ -0,0 +1,28 @@ +package sysinfo + +import sys "core:sys/windows" +import "base:intrinsics" + +@(init, private) +init_cpu_core_count :: proc() { + infos: []sys.SYSTEM_LOGICAL_PROCESSOR_INFORMATION + defer delete(infos) + + returned_length: sys.DWORD + // Query for the required buffer size. + if ok := sys.GetLogicalProcessorInformation(raw_data(infos), &returned_length); !ok { + infos = make([]sys.SYSTEM_LOGICAL_PROCESSOR_INFORMATION, returned_length / size_of(sys.SYSTEM_LOGICAL_PROCESSOR_INFORMATION)) + } + + // If it still doesn't work, return + if ok := sys.GetLogicalProcessorInformation(raw_data(infos), &returned_length); !ok { + return + } + + for info in infos { + #partial switch info.Relationship { + case .RelationProcessorCore: cpu.physical_cores += 1 + case .RelationNumaNode: cpu.logical_cores += int(intrinsics.count_ones(info.ProcessorMask)) + } + } +} \ No newline at end of file diff --git a/core/sys/info/doc.odin b/core/sys/info/doc.odin index 2fd34b864..aef444f98 100644 --- a/core/sys/info/doc.odin +++ b/core/sys/info/doc.odin @@ -26,13 +26,15 @@ Example: import si "core:sys/info" main :: proc() { - fmt.printfln("Odin: %v", ODIN_VERSION) - fmt.printfln("OS: %v", si.os_version.as_string) - fmt.printfln("OS: %#v", si.os_version) - fmt.printfln("CPU: %v", si.cpu_name) - fmt.printfln("RAM: %#.1M", si.ram.total_ram) + fmt.printfln("Odin: %v", ODIN_VERSION) + fmt.printfln("OS: %v", si.os_version.as_string) + fmt.printfln("OS: %#v", si.os_version) + fmt.printfln("CPU: %v", si.cpu.name) + fmt.printfln("CPU: %v", si.cpu.name) + fmt.printfln("CPU cores: %vc/%vt", si.cpu.physical_cores, si.cpu.logical_cores) + fmt.printfln("RAM: %#.1M", si.ram.total_ram) - // fmt.printfln("Features: %v", si.cpu_features) + // fmt.printfln("Features: %v", si.cpu.features) // fmt.printfln("MacOS version: %v", si.macos_version) fmt.println() diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 266dcdbf4..ed32c7a9f 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -857,7 +857,6 @@ MEMORY_RESOURCE_NOTIFICATION_TYPE :: enum c_int { LowMemoryResourceNotification :: MEMORY_RESOURCE_NOTIFICATION_TYPE.LowMemoryResourceNotification HighMemoryResourceNotification :: MEMORY_RESOURCE_NOTIFICATION_TYPE.HighMemoryResourceNotification - @(default_calling_convention="system") foreign kernel32 { CreateMemoryResourceNotification :: proc( @@ -1194,7 +1193,7 @@ DUMMYUNIONNAME_u :: struct #raw_union { SYSTEM_LOGICAL_PROCESSOR_INFORMATION :: struct { ProcessorMask: ULONG_PTR, Relationship: LOGICAL_PROCESSOR_RELATIONSHIP, - DummyUnion: DUMMYUNIONNAME_u, + using DummyUnion: DUMMYUNIONNAME_u, } SYSTEM_POWER_STATUS :: struct {