From 3b632b4d9066d9df2e2f8e28d95567c23d5fd1b7 Mon Sep 17 00:00:00 2001 From: alec hodgkinson Date: Mon, 22 Apr 2024 15:13:52 -0700 Subject: [PATCH 001/270] Fixed typo in raylib documentation --- vendor/raylib/raylib.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index b98770271..1d9451b3d 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -951,8 +951,8 @@ foreign lib { SetWindowTitle :: proc(title: cstring) --- // Set title for window (only PLATFORM_DESKTOP and PLATFORM_WEB) SetWindowPosition :: proc(x, y: c.int) --- // Set window position on screen (only PLATFORM_DESKTOP) SetWindowMonitor :: proc(monitor: c.int) --- // Set monitor for the current window - SetWindowMinSize :: proc(width, height: c.int) --- // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) - SetWindowMaxSize :: proc(width, height: c.int) --- // Set window maximum dimensions (for FLAG_WINDOW_RESIZABLE) + SetWindowMinSize :: proc(width, height: c.int) --- // Set window minimum dimensions (for WINDOW_RESIZABLE) + SetWindowMaxSize :: proc(width, height: c.int) --- // Set window maximum dimensions (for WINDOW_RESIZABLE) SetWindowSize :: proc(width, height: c.int) --- // Set window dimensions SetWindowOpacity :: proc(opacity: f32) --- // Set window opacity [0.0f..1.0f] (only PLATFORM_DESKTOP) SetWindowFocused :: proc() --- // Set window focused (only PLATFORM_DESKTOP) From 7934e92d14de6166791e1483f986eb97fe06f595 Mon Sep 17 00:00:00 2001 From: tim4242 Date: Fri, 24 May 2024 01:04:41 +0200 Subject: [PATCH 002/270] Initial dependency file generation --- src/build_settings.cpp | 1 + src/main.cpp | 52 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index d9454ba9b..400e478fe 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -831,6 +831,7 @@ struct BuildContext { bool show_timings; TimingsExportFormat export_timings_format; String export_timings_file; + String export_dependencies_file; bool show_unused; bool show_unused_with_location; bool show_more_timings; diff --git a/src/main.cpp b/src/main.cpp index 4df6f97d5..d21eb21cb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -234,6 +234,7 @@ enum BuildFlagKind { BuildFlag_ShowMoreTimings, BuildFlag_ExportTimings, BuildFlag_ExportTimingsFile, + BuildFlag_ExportDependencies, BuildFlag_ShowSystemCalls, BuildFlag_ThreadCount, BuildFlag_KeepTempFiles, @@ -320,7 +321,6 @@ enum BuildFlagKind { BuildFlag_Subsystem, #endif - BuildFlag_COUNT, }; @@ -427,6 +427,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_ShowMoreTimings, str_lit("show-more-timings"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ExportTimings, str_lit("export-timings"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_ExportTimingsFile, str_lit("export-timings-file"), BuildFlagParam_String, Command__does_check); + add_flag(&build_flags, BuildFlag_ExportDependencies, str_lit("export-dependencies"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_ShowUnused, str_lit("show-unused"), BuildFlagParam_None, Command_check); add_flag(&build_flags, BuildFlag_ShowUnusedWithLocation, str_lit("show-unused-with-location"), BuildFlagParam_None, Command_check); add_flag(&build_flags, BuildFlag_ShowSystemCalls, str_lit("show-system-calls"), BuildFlagParam_None, Command_all); @@ -753,6 +754,19 @@ gb_internal bool parse_build_flags(Array args) { break; } + case BuildFlag_ExportDependencies: { + GB_ASSERT(value.kind == ExactValue_String); + + String export_path = string_trim_whitespace(value.value_string); + if (is_build_flag_path_valid(export_path)) { + build_context.export_dependencies_file = path_to_full_path(heap_allocator(), export_path); + } else { + gb_printf_err("Invalid -export-dependencies path, got %.*s\n", LIT(export_path)); + bad_flags = true; + } + + break; + } case BuildFlag_ShowSystemCalls: { GB_ASSERT(value.kind == ExactValue_Invalid); build_context.show_system_calls = true; @@ -1790,6 +1804,11 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "Example: -export-timings-file:timings.json"); print_usage_line(0, ""); + print_usage_line(1, "-export-dependencies:"); + print_usage_line(2, "Exports all source files used to create the output file in Make format."); + print_usage_line(2, "Example: -export-dependencies:odin.d"); + print_usage_line(0, ""); + print_usage_line(1, "-thread-count:"); print_usage_line(2, "Overrides the number of threads the compiler will use to compile with."); print_usage_line(2, "Example: -thread-count:2"); @@ -2916,10 +2935,35 @@ int main(int arg_count, char const **arg_ptr) { show_timings(checker, &global_timings); } - 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)); + String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); + defer (gb_free(heap_allocator(), exe_name.text)); + if (build_context.export_dependencies_file.len > 0) { + gbFile f = {}; + char * fileName = (char *)build_context.export_dependencies_file.text; + gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, fileName); + if (err != gbFileError_None) { + gb_printf_err("Failed to export dependencies to: %s\n", fileName); + exit_with_errors(); + return 1; + } + defer (gb_file_close(&f)); + + gb_fprintf(&f, "%.*s:", LIT(exe_name)); + + for (isize i = parser->packages.count-1; i >= 0; i--) { + AstPackage *pkg = parser->packages[i]; + for (isize j = pkg->files.count-1; j >= 0; j--) { + AstFile *file = pkg->files[j]; + + gb_fprintf(&f, " \\\n %.*s", LIT(file->fullpath)); + } + } + + gb_fprintf(&f, "\n"); + } + + if (run_output) { return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string)); } return 0; From 8664b88c8fe282aaabed6c3522802b63e39bcf02 Mon Sep 17 00:00:00 2001 From: tim4242 Date: Fri, 24 May 2024 01:20:45 +0200 Subject: [PATCH 003/270] Improved depdendency formatting to support paths containing spaces --- src/main.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index d21eb21cb..575745290 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2951,12 +2951,31 @@ int main(int arg_count, char const **arg_ptr) { gb_fprintf(&f, "%.*s:", LIT(exe_name)); + isize current_line_length = exe_name.len + 1; + for (isize i = parser->packages.count-1; i >= 0; i--) { AstPackage *pkg = parser->packages[i]; for (isize j = pkg->files.count-1; j >= 0; j--) { AstFile *file = pkg->files[j]; - gb_fprintf(&f, " \\\n %.*s", LIT(file->fullpath)); + /* Arbitrary line break value. Maybe make this better? */ + if (current_line_length >= 80-2) { + gb_file_write(&f, " \\\n ", 4); + current_line_length = 1; + } + + gb_file_write(&f, " ", 1); + current_line_length++; + + for (isize k = 0; k < file->fullpath.len; k++) { + char part = file->fullpath.text[k]; + if (part == ' ') { + gb_file_write(&f, "\\", 1); + current_line_length++; + } + gb_file_write(&f, &part, 1); + current_line_length++; + } } } From 0cba33075ff6d2b5059e8f6c6d2f5a7c92f02edf Mon Sep 17 00:00:00 2001 From: tim4242 Date: Fri, 24 May 2024 13:44:38 +0200 Subject: [PATCH 004/270] Add the option to export dependencies as JSON. --- src/build_settings.cpp | 7 ++ src/main.cpp | 164 ++++++++++++++++++++++++++++------------- 2 files changed, 120 insertions(+), 51 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 400e478fe..3bf168177 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -694,6 +694,12 @@ enum TimingsExportFormat : i32 { TimingsExportCSV = 2, }; +enum DependenciesExportFormat : i32 { + DependenciesExportUnspecified = 0, + DependenciesExportMake = 1, + DependenciesExportJson = 2, +}; + enum ErrorPosStyle { ErrorPosStyle_Default, // path(line:column) msg ErrorPosStyle_Unix, // path:line:column: msg @@ -831,6 +837,7 @@ struct BuildContext { bool show_timings; TimingsExportFormat export_timings_format; String export_timings_file; + DependenciesExportFormat export_dependencies_format; String export_dependencies_file; bool show_unused; bool show_unused_with_location; diff --git a/src/main.cpp b/src/main.cpp index 575745290..bceee83da 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -235,6 +235,7 @@ enum BuildFlagKind { BuildFlag_ExportTimings, BuildFlag_ExportTimingsFile, BuildFlag_ExportDependencies, + BuildFlag_ExportDependenciesFile, BuildFlag_ShowSystemCalls, BuildFlag_ThreadCount, BuildFlag_KeepTempFiles, @@ -427,7 +428,8 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_ShowMoreTimings, str_lit("show-more-timings"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ExportTimings, str_lit("export-timings"), BuildFlagParam_String, Command__does_check); add_flag(&build_flags, BuildFlag_ExportTimingsFile, str_lit("export-timings-file"), BuildFlagParam_String, Command__does_check); - add_flag(&build_flags, BuildFlag_ExportDependencies, str_lit("export-dependencies"), BuildFlagParam_String, Command__does_check); + add_flag(&build_flags, BuildFlag_ExportDependencies, str_lit("export-dependencies"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_ExportDependenciesFile, str_lit("export-dependencies-file"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_ShowUnused, str_lit("show-unused"), BuildFlagParam_None, Command_check); add_flag(&build_flags, BuildFlag_ShowUnusedWithLocation, str_lit("show-unused-with-location"), BuildFlagParam_None, Command_check); add_flag(&build_flags, BuildFlag_ShowSystemCalls, str_lit("show-system-calls"), BuildFlagParam_None, Command_all); @@ -757,6 +759,23 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_ExportDependencies: { GB_ASSERT(value.kind == ExactValue_String); + if (value.value_string == "make") { + build_context.export_dependencies_format = DependenciesExportMake; + } else if (value.value_string == "json") { + build_context.export_dependencies_format = DependenciesExportJson; + } else { + gb_printf_err("Invalid export format for -export-dependencies:, got %.*s\n", LIT(value.value_string)); + gb_printf_err("Valid export formats:\n"); + gb_printf_err("\tmake\n"); + gb_printf_err("\tjson\n"); + bad_flags = true; + } + + break; + } + case BuildFlag_ExportDependenciesFile: { + GB_ASSERT(value.kind == ExactValue_String); + String export_path = string_trim_whitespace(value.value_string); if (is_build_flag_path_valid(export_path)) { build_context.export_dependencies_file = path_to_full_path(heap_allocator(), export_path); @@ -764,8 +783,8 @@ gb_internal bool parse_build_flags(Array args) { gb_printf_err("Invalid -export-dependencies path, got %.*s\n", LIT(export_path)); bad_flags = true; } - - break; + + break; } case BuildFlag_ShowSystemCalls: { GB_ASSERT(value.kind == ExactValue_Invalid); @@ -1649,6 +1668,74 @@ gb_internal void show_timings(Checker *c, Timings *t) { } } +gb_internal void export_dependencies(Parser *p) { + GB_ASSERT(build_context.export_dependencies_format != DependenciesExportUnspecified); + + if (build_context.export_dependencies_file.len <= 0) { + gb_printf_err("No dependency file specified with `-export-dependencies-file`\n"); + exit_with_errors(); + return; + } + + gbFile f = {}; + char * fileName = (char *)build_context.export_dependencies_file.text; + gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, fileName); + if (err != gbFileError_None) { + gb_printf_err("Failed to export dependencies to: %s\n", fileName); + exit_with_errors(); + return; + } + defer (gb_file_close(&f)); + + if (build_context.export_dependencies_format == DependenciesExportMake) { + String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); + defer (gb_free(heap_allocator(), exe_name.text)); + + gb_fprintf(&f, "%.*s:", LIT(exe_name)); + + isize current_line_length = exe_name.len + 1; + + for(AstPackage *pkg : p->packages) { + for(AstFile *file : pkg->files) { + /* Arbitrary line break value. Maybe make this better? */ + if (current_line_length >= 80-2) { + gb_file_write(&f, " \\\n ", 4); + current_line_length = 1; + } + + gb_file_write(&f, " ", 1); + current_line_length++; + + for (isize k = 0; k < file->fullpath.len; k++) { + char part = file->fullpath.text[k]; + if (part == ' ') { + gb_file_write(&f, "\\", 1); + current_line_length++; + } + gb_file_write(&f, &part, 1); + current_line_length++; + } + } + } + + gb_fprintf(&f, "\n"); + } else if (build_context.export_dependencies_format == DependenciesExportJson) { + gb_fprintf(&f, "{\n"); + + gb_fprintf(&f, "\t\"source_files\": [\n"); + + for(AstPackage *pkg : p->packages) { + for(AstFile *file : pkg->files) { + gb_fprintf(&f, "\t\t\"%.*s\",\n", LIT(file->fullpath)); + } + } + + gb_fprintf(&f, "\t],\n"); + + gb_fprintf(&f, "}\n"); + } +} + gb_internal void remove_temp_files(lbGenerator *gen) { if (build_context.keep_temp_files) return; @@ -1804,9 +1891,16 @@ gb_internal void print_show_help(String const arg0, String const &command) { print_usage_line(2, "Example: -export-timings-file:timings.json"); print_usage_line(0, ""); - print_usage_line(1, "-export-dependencies:"); - print_usage_line(2, "Exports all source files used to create the output file in Make format."); - print_usage_line(2, "Example: -export-dependencies:odin.d"); + print_usage_line(1, "-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(0, ""); + + print_usage_line(1, "-export-dependencies-file:"); + print_usage_line(2, "Specifies the filename for `-export-dependencies`."); + print_usage_line(2, "Example: -export-dependencies-file:dependencies.d"); print_usage_line(0, ""); print_usage_line(1, "-thread-count:"); @@ -2900,6 +2994,10 @@ int main(int arg_count, char const **arg_ptr) { if (build_context.show_timings) { show_timings(checker, &global_timings); } + + if (build_context.export_dependencies_format != DependenciesExportUnspecified) { + export_dependencies(parser); + } return result; } break; @@ -2922,6 +3020,10 @@ int main(int arg_count, char const **arg_ptr) { if (build_context.show_timings) { show_timings(checker, &global_timings); } + + if (build_context.export_dependencies_format != DependenciesExportUnspecified) { + export_dependencies(parser); + } return result; } break; @@ -2935,54 +3037,14 @@ int main(int arg_count, char const **arg_ptr) { show_timings(checker, &global_timings); } - String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); - defer (gb_free(heap_allocator(), exe_name.text)); - - if (build_context.export_dependencies_file.len > 0) { - gbFile f = {}; - char * fileName = (char *)build_context.export_dependencies_file.text; - gbFileError err = gb_file_open_mode(&f, gbFileMode_Write, fileName); - if (err != gbFileError_None) { - gb_printf_err("Failed to export dependencies to: %s\n", fileName); - exit_with_errors(); - return 1; - } - defer (gb_file_close(&f)); - - gb_fprintf(&f, "%.*s:", LIT(exe_name)); - - isize current_line_length = exe_name.len + 1; - - for (isize i = parser->packages.count-1; i >= 0; i--) { - AstPackage *pkg = parser->packages[i]; - for (isize j = pkg->files.count-1; j >= 0; j--) { - AstFile *file = pkg->files[j]; - - /* Arbitrary line break value. Maybe make this better? */ - if (current_line_length >= 80-2) { - gb_file_write(&f, " \\\n ", 4); - current_line_length = 1; - } - - gb_file_write(&f, " ", 1); - current_line_length++; - - for (isize k = 0; k < file->fullpath.len; k++) { - char part = file->fullpath.text[k]; - if (part == ' ') { - gb_file_write(&f, "\\", 1); - current_line_length++; - } - gb_file_write(&f, &part, 1); - current_line_length++; - } - } - } - - gb_fprintf(&f, "\n"); + if (build_context.export_dependencies_format != DependenciesExportUnspecified) { + export_dependencies(parser); } 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)); + return system_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string)); } return 0; From 2ecf909be02f719596f566853d13666d0db4da8d Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Mon, 27 May 2024 11:54:28 -0400 Subject: [PATCH 005/270] Fix open() foreign libc signature on Darwin --- core/os/os_darwin.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index a688e1ac3..877a90bf1 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -442,7 +442,7 @@ F_GETPATH :: 50 // return the full path of the fd foreign libc { @(link_name="__error") __error :: proc() -> ^c.int --- - @(link_name="open") _unix_open :: proc(path: cstring, flags: i32, mode: u16) -> Handle --- + @(link_name="open") _unix_open :: proc(path: cstring, flags: i32, #c_vararg args: ..any) -> Handle --- @(link_name="close") _unix_close :: proc(handle: Handle) -> c.int --- @(link_name="read") _unix_read :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int --- @(link_name="write") _unix_write :: proc(handle: Handle, buffer: rawptr, count: c.size_t) -> int --- From fb6248925ae17ebc6abb3d4a735aa395dcbf831f Mon Sep 17 00:00:00 2001 From: shashank Date: Mon, 27 May 2024 23:38:50 +0530 Subject: [PATCH 006/270] fix pitch_from_quaternion --- core/math/linalg/specific_euler_angles_f16.odin | 2 +- core/math/linalg/specific_euler_angles_f32.odin | 2 +- core/math/linalg/specific_euler_angles_f64.odin | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/math/linalg/specific_euler_angles_f16.odin b/core/math/linalg/specific_euler_angles_f16.odin index bacda163e..1e9ded9ab 100644 --- a/core/math/linalg/specific_euler_angles_f16.odin +++ b/core/math/linalg/specific_euler_angles_f16.odin @@ -159,7 +159,7 @@ roll_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> f16 { @(require_results) pitch_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> f16 { - y := 2 * (q.y*q.z + q.w*q.w) + y := 2 * (q.y*q.z + q.w*q.x) x := q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z if abs(x) <= F16_EPSILON && abs(y) <= F16_EPSILON { diff --git a/core/math/linalg/specific_euler_angles_f32.odin b/core/math/linalg/specific_euler_angles_f32.odin index b9957034f..e33b1f095 100644 --- a/core/math/linalg/specific_euler_angles_f32.odin +++ b/core/math/linalg/specific_euler_angles_f32.odin @@ -159,7 +159,7 @@ roll_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> f32 { @(require_results) pitch_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> f32 { - y := 2 * (q.y*q.z + q.w*q.w) + y := 2 * (q.y*q.z + q.w*q.x) x := q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z if abs(x) <= F32_EPSILON && abs(y) <= F32_EPSILON { diff --git a/core/math/linalg/specific_euler_angles_f64.odin b/core/math/linalg/specific_euler_angles_f64.odin index 8001d080a..9b5cf4b56 100644 --- a/core/math/linalg/specific_euler_angles_f64.odin +++ b/core/math/linalg/specific_euler_angles_f64.odin @@ -159,7 +159,7 @@ roll_from_quaternion_f64 :: proc "contextless" (q: Quaternionf64) -> f64 { @(require_results) pitch_from_quaternion_f64 :: proc "contextless" (q: Quaternionf64) -> f64 { - y := 2 * (q.y*q.z + q.w*q.w) + y := 2 * (q.y*q.z + q.w*q.x) x := q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z if abs(x) <= F64_EPSILON && abs(y) <= F64_EPSILON { From 38fffff06a76004a02bedc34172aa5107784c03f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 27 May 2024 23:51:43 +0100 Subject: [PATCH 007/270] Begin moving `foreign import` import paths to be evaluated in the semantic phase rather than parsing. --- src/checker.cpp | 39 +++++++++++++++++++++++++++- src/parser.cpp | 65 +++++++++++++++++++++------------------------- src/parser.hpp | 2 +- src/parser_pos.cpp | 2 +- 4 files changed, 69 insertions(+), 39 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index 9d44c34dc..4e5aed2bf 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -4883,7 +4883,44 @@ gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { Scope *parent_scope = ctx->scope; GB_ASSERT(parent_scope->flags&ScopeFlag_File); - GB_ASSERT(fl->fullpaths.count > 0); + String base_dir = dir_from_path(decl->file()->fullpath); + + auto fullpaths = array_make(permanent_allocator(), 0, fl->filepaths.count); + + for (Ast *fp_node : fl->filepaths) { + Operand op = {}; + check_expr(ctx, &op, fp_node); + if (op.mode != Addressing_Constant && op.value.kind != ExactValue_String) { + gbString s = expr_to_string(op.expr); + error(fp_node, "Expected a constant string value, got '%s'", s); + gb_string_free(s); + continue; + } + if (!is_type_string(op.type)) { + gbString s = type_to_string(op.type); + error(fp_node, "Expected a constant string value, got value of type '%s'", s); + gb_string_free(s); + continue; + } + + String file_str = op.value.value_string; + file_str = string_trim_whitespace(file_str); + + String fullpath = file_str; + if (allow_check_foreign_filepath()) { + String foreign_path = {}; + bool ok = determine_path_from_string(nullptr, decl, base_dir, file_str, &foreign_path); + gb_unused(ok); + fullpath = foreign_path; + } + array_add(&fullpaths, fullpath); + } + fl->fullpaths = slice_from_array(fullpaths); + + + if (fl->fullpaths.count == 0) { + return; + } String fullpath = fl->fullpaths[0]; String library_name = path_to_entity_name(fl->library_name.string, fullpath); if (is_blank_ident(library_name)) { diff --git a/src/parser.cpp b/src/parser.cpp index 5aa11b5d0..be0d68177 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1284,7 +1284,7 @@ gb_internal Ast *ast_import_decl(AstFile *f, Token token, Token relpath, Token i return result; } -gb_internal Ast *ast_foreign_import_decl(AstFile *f, Token token, Array filepaths, Token library_name, +gb_internal Ast *ast_foreign_import_decl(AstFile *f, Token token, Array filepaths, Token library_name, CommentGroup *docs, CommentGroup *comment) { Ast *result = alloc_ast_node(f, Ast_ForeignImportDecl); result->ForeignImportDecl.token = token; @@ -4882,14 +4882,14 @@ gb_internal Ast *parse_foreign_decl(AstFile *f) { if (is_blank_ident(lib_name)) { syntax_error(lib_name, "Illegal foreign import name: '_'"); } - Array filepaths = {}; + Array filepaths = {}; if (allow_token(f, Token_OpenBrace)) { array_init(&filepaths, ast_allocator(f)); while (f->curr_token.kind != Token_CloseBrace && f->curr_token.kind != Token_EOF) { - Token path = expect_token(f, Token_String); + Ast *path = parse_expr(f, true); array_add(&filepaths, path); if (!allow_field_separator(f)) { @@ -4898,9 +4898,10 @@ gb_internal Ast *parse_foreign_decl(AstFile *f) { } expect_closing_brace_of_field_list(f); } else { - filepaths = array_make(ast_allocator(f), 0, 1); + filepaths = array_make(ast_allocator(f), 0, 1); Token path = expect_token(f, Token_String); - array_add(&filepaths, path); + Ast *lit = ast_basic_lit(f, path); + array_add(&filepaths, lit); } Ast *s = nullptr; @@ -4909,7 +4910,7 @@ gb_internal Ast *parse_foreign_decl(AstFile *f) { s = ast_bad_decl(f, lib_name, f->curr_token); } else if (f->curr_proc != nullptr) { syntax_error(lib_name, "You cannot use foreign import within a procedure. This must be done at the file scope"); - s = ast_bad_decl(f, lib_name, filepaths[0]); + s = ast_bad_decl(f, lib_name, ast_token(filepaths[0])); } else { s = ast_foreign_import_decl(f, token, filepaths, lib_name, docs, f->line_comment); } @@ -5648,9 +5649,19 @@ gb_internal bool is_package_name_reserved(String const &name) { } -gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node, String base_dir, String const &original_string, String *path) { +gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node, String base_dir, String const &original_string, String *path, bool use_check_errors=false) { GB_ASSERT(path != nullptr); + void (*do_error)(Ast *, char const *, ...); + void (*do_warning)(Token const &, char const *, ...); + + do_error = &syntax_error; + do_warning = &syntax_warning; + if (use_check_errors) { + do_error = &error; + do_error = &warning; + } + // NOTE(bill): if file_mutex == nullptr, this means that the code is used within the semantics stage String collection_name = {}; @@ -5677,7 +5688,7 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node String file_str = {}; if (colon_pos == 0) { - syntax_error(node, "Expected a collection name"); + do_error(node, "Expected a collection name"); return false; } @@ -5692,11 +5703,11 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node if (has_windows_drive) { String sub_file_path = substring(file_str, 3, file_str.len); if (!is_import_path_valid(sub_file_path)) { - syntax_error(node, "Invalid import path: '%.*s'", LIT(file_str)); + do_error(node, "Invalid import path: '%.*s'", LIT(file_str)); return false; } } else if (!is_import_path_valid(file_str)) { - syntax_error(node, "Invalid import path: '%.*s'", LIT(file_str)); + do_error(node, "Invalid import path: '%.*s'", LIT(file_str)); return false; } @@ -5718,16 +5729,16 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node } if (replace_with_base) { if (ast_file_vet_deprecated(node->file())) { - syntax_error(node, "import \"core:%.*s\" has been deprecated in favour of \"base:%.*s\"", LIT(file_str), LIT(file_str)); + do_error(node, "import \"core:%.*s\" has been deprecated in favour of \"base:%.*s\"", LIT(file_str), LIT(file_str)); } else { - syntax_warning(ast_token(node), "import \"core:%.*s\" has been deprecated in favour of \"base:%.*s\"", LIT(file_str), LIT(file_str)); + do_warning(ast_token(node), "import \"core:%.*s\" has been deprecated in favour of \"base:%.*s\"", LIT(file_str), LIT(file_str)); } } } if (collection_name == "system") { if (node->kind != Ast_ForeignImportDecl) { - syntax_error(node, "The library collection 'system' is restrict for 'foreign_library'"); + do_error(node, "The library collection 'system' is restrict for 'foreign import'"); return false; } else { *path = file_str; @@ -5735,7 +5746,7 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node } } else if (!find_library_collection_path(collection_name, &base_dir)) { // NOTE(bill): It's a naughty name - syntax_error(node, "Unknown library collection: '%.*s'", LIT(collection_name)); + do_error(node, "Unknown library collection: '%.*s'", LIT(collection_name)); return false; } } else { @@ -5759,7 +5770,7 @@ gb_internal bool determine_path_from_string(BlockingMutex *file_mutex, Ast *node if (collection_name == "core" || collection_name == "base") { return true; } else { - syntax_error(node, "The package '%.*s' must be imported with the 'base' library collection: 'base:%.*s'", LIT(file_str), LIT(file_str)); + do_error(node, "The package '%.*s' must be imported with the 'base' library collection: 'base:%.*s'", LIT(file_str), LIT(file_str)); return false; } } @@ -5844,31 +5855,13 @@ gb_internal void parse_setup_file_decls(Parser *p, AstFile *f, String const &bas } else if (node->kind == Ast_ForeignImportDecl) { ast_node(fl, ForeignImportDecl, node); - auto fullpaths = array_make(permanent_allocator(), 0, fl->filepaths.count); - - for (Token const &fp : fl->filepaths) { - String file_str = string_trim_whitespace(string_value_from_token(f, fp)); - String fullpath = file_str; - if (allow_check_foreign_filepath()) { - String foreign_path = {}; - bool ok = determine_path_from_string(&p->file_decl_mutex, node, base_dir, file_str, &foreign_path); - if (!ok) { - decls[i] = ast_bad_decl(f, fp, fl->filepaths[fl->filepaths.count-1]); - goto end; - } - fullpath = foreign_path; - } - array_add(&fullpaths, fullpath); - } - if (fullpaths.count == 0) { + if (fl->filepaths.count == 0) { syntax_error(decls[i], "No foreign paths found"); - decls[i] = ast_bad_decl(f, fl->filepaths[0], fl->filepaths[fl->filepaths.count-1]); + decls[i] = ast_bad_decl(f, ast_token(fl->filepaths[0]), ast_end_token(fl->filepaths[fl->filepaths.count-1])); goto end; + } - fl->fullpaths = slice_from_array(fullpaths); - - } else if (node->kind == Ast_WhenStmt) { ast_node(ws, WhenStmt, node); parse_setup_file_when_stmt(p, f, base_dir, ws); diff --git a/src/parser.hpp b/src/parser.hpp index 5820275c8..1e07cfd59 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -631,7 +631,7 @@ AST_KIND(_DeclBegin, "", bool) \ }) \ AST_KIND(ForeignImportDecl, "foreign import declaration", struct { \ Token token; \ - Slice filepaths; \ + Slice filepaths; \ Token library_name; \ String collection_name; \ Slice fullpaths; \ diff --git a/src/parser_pos.cpp b/src/parser_pos.cpp index b2e12999b..1ffd3a82f 100644 --- a/src/parser_pos.cpp +++ b/src/parser_pos.cpp @@ -278,7 +278,7 @@ Token ast_end_token(Ast *node) { case Ast_ImportDecl: return node->ImportDecl.relpath; case Ast_ForeignImportDecl: if (node->ForeignImportDecl.filepaths.count > 0) { - return node->ForeignImportDecl.filepaths[node->ForeignImportDecl.filepaths.count-1]; + return ast_end_token(node->ForeignImportDecl.filepaths[node->ForeignImportDecl.filepaths.count-1]); } if (node->ForeignImportDecl.library_name.kind != Token_Invalid) { return node->ForeignImportDecl.library_name; From a1b8749e74639875467cba56b0ab02c342870338 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 28 May 2024 00:23:23 +0100 Subject: [PATCH 008/270] Delay checking foreign import paths until after global scope is checked --- src/checker.cpp | 174 +++++++++++++++++++++++++----------------------- src/checker.hpp | 3 + src/entity.cpp | 1 + src/parser.cpp | 28 +++++++- src/parser.hpp | 1 + 5 files changed, 121 insertions(+), 86 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index 4e5aed2bf..1ded6ea6e 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1283,6 +1283,7 @@ gb_internal void init_checker_info(CheckerInfo *i) { mpsc_init(&i->definition_queue, a); //); // 1<<20); mpsc_init(&i->required_global_variable_queue, a); // 1<<10); mpsc_init(&i->required_foreign_imports_through_force_queue, a); // 1<<10); + mpsc_init(&i->foreign_imports_to_check_fullpaths, a); // 1<<10); mpsc_init(&i->intrinsics_entry_point_usage, a); // 1<<10); // just waste some memory here, even if it probably never used string_map_init(&i->load_directory_cache); @@ -1307,6 +1308,7 @@ gb_internal void destroy_checker_info(CheckerInfo *i) { mpsc_destroy(&i->definition_queue); mpsc_destroy(&i->required_global_variable_queue); mpsc_destroy(&i->required_foreign_imports_through_force_queue); + mpsc_destroy(&i->foreign_imports_to_check_fullpaths); map_destroy(&i->objc_msgSend_types); string_map_destroy(&i->load_file_cache); @@ -4874,6 +4876,83 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_import_decl_attribute) { return false; } +gb_internal void check_foreign_import_fullpaths(Checker *c) { + CheckerContext ctx = make_checker_context(c); + + UntypedExprInfoMap untyped = {}; + defer (map_destroy(&untyped)); + + for (Entity *e = nullptr; mpsc_dequeue(&c->info.foreign_imports_to_check_fullpaths, &e); /**/) { + GB_ASSERT(e != nullptr); + GB_ASSERT(e->kind == Entity_LibraryName); + Ast *decl = e->LibraryName.decl; + ast_node(fl, ForeignImportDecl, decl); + + AstFile *f = decl->file(); + + reset_checker_context(&ctx, f, &untyped); + ctx.collect_delayed_decls = false; + + GB_ASSERT(ctx.scope == e->scope); + + if (fl->fullpaths.count == 0) { + String base_dir = dir_from_path(decl->file()->fullpath); + + auto fullpaths = array_make(permanent_allocator(), 0, fl->filepaths.count); + + for (Ast *fp_node : fl->filepaths) { + Operand op = {}; + check_expr(&ctx, &op, fp_node); + if (op.mode != Addressing_Constant && op.value.kind != ExactValue_String) { + gbString s = expr_to_string(op.expr); + error(fp_node, "Expected a constant string value, got '%s'", s); + gb_string_free(s); + continue; + } + if (!is_type_string(op.type)) { + gbString s = type_to_string(op.type); + error(fp_node, "Expected a constant string value, got value of type '%s'", s); + gb_string_free(s); + continue; + } + + String file_str = op.value.value_string; + file_str = string_trim_whitespace(file_str); + + String fullpath = file_str; + if (allow_check_foreign_filepath()) { + String foreign_path = {}; + bool ok = determine_path_from_string(nullptr, decl, base_dir, file_str, &foreign_path, /*use error not syntax_error*/true); + if (ok) { + fullpath = foreign_path; + } + } + array_add(&fullpaths, fullpath); + } + fl->fullpaths = slice_from_array(fullpaths); + } + + for (String const &path : fl->fullpaths) { + String ext = path_extension(path); + if (str_eq_ignore_case(ext, ".c") || + str_eq_ignore_case(ext, ".cpp") || + str_eq_ignore_case(ext, ".cxx") || + str_eq_ignore_case(ext, ".h") || + str_eq_ignore_case(ext, ".hpp") || + str_eq_ignore_case(ext, ".hxx") || + false + ) { + error(fl->token, "With 'foreign import', you cannot import a %.*s file/directory, you must precompile the library and link against that", LIT(ext)); + break; + } + } + + add_untyped_expressions(ctx.info, &untyped); + + e->LibraryName.paths = fl->fullpaths; + } +} + gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { if (decl->state_flags & StateFlag_BeenHandled) return; decl->state_flags |= StateFlag_BeenHandled; @@ -4883,96 +4962,26 @@ gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { Scope *parent_scope = ctx->scope; GB_ASSERT(parent_scope->flags&ScopeFlag_File); - String base_dir = dir_from_path(decl->file()->fullpath); - - auto fullpaths = array_make(permanent_allocator(), 0, fl->filepaths.count); - - for (Ast *fp_node : fl->filepaths) { - Operand op = {}; - check_expr(ctx, &op, fp_node); - if (op.mode != Addressing_Constant && op.value.kind != ExactValue_String) { - gbString s = expr_to_string(op.expr); - error(fp_node, "Expected a constant string value, got '%s'", s); - gb_string_free(s); - continue; - } - if (!is_type_string(op.type)) { - gbString s = type_to_string(op.type); - error(fp_node, "Expected a constant string value, got value of type '%s'", s); - gb_string_free(s); - continue; - } - - String file_str = op.value.value_string; - file_str = string_trim_whitespace(file_str); - - String fullpath = file_str; - if (allow_check_foreign_filepath()) { - String foreign_path = {}; - bool ok = determine_path_from_string(nullptr, decl, base_dir, file_str, &foreign_path); - gb_unused(ok); - fullpath = foreign_path; - } - array_add(&fullpaths, fullpath); + String library_name = fl->library_name.string; + if (library_name.len == 0 && fl->fullpaths.count != 0) { + String fullpath = fl->fullpaths[0]; + library_name = path_to_entity_name(fl->library_name.string, fullpath); } - fl->fullpaths = slice_from_array(fullpaths); - - - if (fl->fullpaths.count == 0) { - return; - } - String fullpath = fl->fullpaths[0]; - String library_name = path_to_entity_name(fl->library_name.string, fullpath); - if (is_blank_ident(library_name)) { - error(fl->token, "File name, %.*s, cannot be as a library name as it is not a valid identifier", LIT(fl->library_name.string)); + if (library_name.len == 0 || is_blank_ident(library_name)) { + error(fl->token, "File name, '%.*s', cannot be as a library name as it is not a valid identifier", LIT(library_name)); return; } - for (String const &path : fl->fullpaths) { - String ext = path_extension(path); - if (str_eq_ignore_case(ext, ".c") || - str_eq_ignore_case(ext, ".cpp") || - str_eq_ignore_case(ext, ".cxx") || - str_eq_ignore_case(ext, ".h") || - str_eq_ignore_case(ext, ".hpp") || - str_eq_ignore_case(ext, ".hxx") || - false - ) { - error(fl->token, "With 'foreign import', you cannot import a %.*s file directory, you must precompile the library and link against that", LIT(ext)); - break; - } - } - - - // if (fl->collection_name != "system") { - // char *c_str = gb_alloc_array(heap_allocator(), char, fullpath.len+1); - // defer (gb_free(heap_allocator(), c_str)); - // gb_memmove(c_str, fullpath.text, fullpath.len); - // c_str[fullpath.len] = '\0'; - - // gbFile f = {}; - // gbFileError file_err = gb_file_open(&f, c_str); - // defer (gb_file_close(&f)); - - // switch (file_err) { - // case gbFileError_Invalid: - // error(decl, "Invalid file or cannot be found ('%.*s')", LIT(fullpath)); - // return; - // case gbFileError_NotExists: - // error(decl, "File cannot be found ('%.*s')", LIT(fullpath)); - // return; - // } - // } GB_ASSERT(fl->library_name.pos.line != 0); fl->library_name.string = library_name; Entity *e = alloc_entity_library_name(parent_scope, fl->library_name, t_invalid, fl->fullpaths, library_name); + e->LibraryName.decl = decl; add_entity_flags_from_file(ctx, e, parent_scope); add_entity(ctx, parent_scope, nullptr, e); - AttributeContext ac = {}; check_decl_attributes(ctx, fl->attributes, foreign_import_decl_attribute, &ac); if (ac.require_declaration) { @@ -4987,12 +4996,8 @@ gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { e->LibraryName.extra_linker_flags = extra_linker_flags; } - if (has_asm_extension(fullpath)) { - if (build_context.metrics.arch != TargetArch_amd64 && build_context.metrics.os != TargetOs_darwin) { - error(decl, "Assembly files are not yet supported on this platform: %.*s_%.*s", - LIT(target_os_names[build_context.metrics.os]), LIT(target_arch_names[build_context.metrics.arch])); - } - } + mpsc_enqueue(&ctx->info->foreign_imports_to_check_fullpaths, e); + } // Returns true if a new package is present @@ -6354,6 +6359,9 @@ gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("check procedure bodies"); check_procedure_bodies(c); + TIME_SECTION("check foreign import fullpaths"); + check_foreign_import_fullpaths(c); + TIME_SECTION("add entities from procedure bodies"); check_merge_queues_into_arrays(c); diff --git a/src/checker.hpp b/src/checker.hpp index 2ade9312e..6ae7b90e2 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -414,6 +414,7 @@ struct CheckerInfo { MPSCQueue entity_queue; MPSCQueue required_global_variable_queue; MPSCQueue required_foreign_imports_through_force_queue; + MPSCQueue foreign_imports_to_check_fullpaths; MPSCQueue intrinsics_entry_point_usage; @@ -434,6 +435,8 @@ struct CheckerInfo { BlockingMutex load_directory_mutex; StringMap load_directory_cache; PtrMap load_directory_map; // Key: Ast_CallExpr * + + }; struct CheckerContext { diff --git a/src/entity.cpp b/src/entity.cpp index 8a7417006..1461b96d7 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -266,6 +266,7 @@ struct Entity { Scope *scope; } ImportName; struct { + Ast *decl; Slice paths; String name; i64 priority_index; diff --git a/src/parser.cpp b/src/parser.cpp index be0d68177..7e72f3c21 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1285,13 +1285,15 @@ gb_internal Ast *ast_import_decl(AstFile *f, Token token, Token relpath, Token i } gb_internal Ast *ast_foreign_import_decl(AstFile *f, Token token, Array filepaths, Token library_name, - CommentGroup *docs, CommentGroup *comment) { + bool multiple_filepaths, + CommentGroup *docs, CommentGroup *comment) { Ast *result = alloc_ast_node(f, Ast_ForeignImportDecl); result->ForeignImportDecl.token = token; result->ForeignImportDecl.filepaths = slice_from_array(filepaths); result->ForeignImportDecl.library_name = library_name; result->ForeignImportDecl.docs = docs; result->ForeignImportDecl.comment = comment; + result->ForeignImportDecl.multiple_filepaths = multiple_filepaths; result->ForeignImportDecl.attributes.allocator = ast_allocator(f); return result; @@ -4882,8 +4884,11 @@ gb_internal Ast *parse_foreign_decl(AstFile *f) { if (is_blank_ident(lib_name)) { syntax_error(lib_name, "Illegal foreign import name: '_'"); } + bool multiple_filepaths = false; + Array filepaths = {}; if (allow_token(f, Token_OpenBrace)) { + multiple_filepaths = true; array_init(&filepaths, ast_allocator(f)); while (f->curr_token.kind != Token_CloseBrace && @@ -4912,7 +4917,7 @@ gb_internal Ast *parse_foreign_decl(AstFile *f) { syntax_error(lib_name, "You cannot use foreign import within a procedure. This must be done at the file scope"); s = ast_bad_decl(f, lib_name, ast_token(filepaths[0])); } else { - s = ast_foreign_import_decl(f, token, filepaths, lib_name, docs, f->line_comment); + s = ast_foreign_import_decl(f, token, filepaths, lib_name, multiple_filepaths, docs, f->line_comment); } expect_semicolon(f); return s; @@ -5859,7 +5864,24 @@ gb_internal void parse_setup_file_decls(Parser *p, AstFile *f, String const &bas syntax_error(decls[i], "No foreign paths found"); decls[i] = ast_bad_decl(f, ast_token(fl->filepaths[0]), ast_end_token(fl->filepaths[fl->filepaths.count-1])); goto end; - + } else if (!fl->multiple_filepaths && + fl->filepaths.count == 1) { + Ast *fp = fl->filepaths[0]; + GB_ASSERT(fp->kind == Ast_BasicLit); + Token fp_token = fp->BasicLit.token; + String file_str = string_trim_whitespace(string_value_from_token(f, fp_token)); + String fullpath = file_str; + if (allow_check_foreign_filepath()) { + String foreign_path = {}; + bool ok = determine_path_from_string(&p->file_decl_mutex, node, base_dir, file_str, &foreign_path); + if (!ok) { + decls[i] = ast_bad_decl(f, fp_token, fp_token); + goto end; + } + fullpath = foreign_path; + } + fl->fullpaths = slice_make(permanent_allocator(), 1); + fl->fullpaths[0] = fullpath; } } else if (node->kind == Ast_WhenStmt) { diff --git a/src/parser.hpp b/src/parser.hpp index 1e07cfd59..0e411d9ac 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -632,6 +632,7 @@ AST_KIND(_DeclBegin, "", bool) \ AST_KIND(ForeignImportDecl, "foreign import declaration", struct { \ Token token; \ Slice filepaths; \ + bool multiple_filepaths; \ Token library_name; \ String collection_name; \ Slice fullpaths; \ From d91054b615e5e185ded106e4903cdd66b2c4f582 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 28 May 2024 00:27:13 +0100 Subject: [PATCH 009/270] Change parser to use `^Expr` rather than `string` for the foreign import paths --- core/odin/ast/ast.odin | 2 +- core/odin/parser/parser.odin | 10 ++++++---- src/parser.cpp | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index be541befa..7891fb12d 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -538,7 +538,7 @@ Foreign_Import_Decl :: struct { import_tok: tokenizer.Token, name: ^Ident, collection_name: string, - fullpaths: []string, + fullpaths: []^Expr, comment: ^Comment_Group, } diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index e32fbdced..813585ba4 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -1190,12 +1190,12 @@ parse_foreign_decl :: proc(p: ^Parser) -> ^ast.Decl { error(p, name.pos, "illegal foreign import name: '_'") } - fullpaths: [dynamic]string + fullpaths: [dynamic]^ast.Expr if allow_token(p, .Open_Brace) { for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF { - path := expect_token(p, .String) - append(&fullpaths, path.text) + path := parse_expr(p, false) + append(&fullpaths, path) allow_token(p, .Comma) or_break } @@ -1203,7 +1203,9 @@ parse_foreign_decl :: proc(p: ^Parser) -> ^ast.Decl { } else { path := expect_token(p, .String) reserve(&fullpaths, 1) - append(&fullpaths, path.text) + bl := ast.new(ast.Basic_Lit, path.pos, end_pos(path)) + bl.tok = tok + append(&fullpaths, bl) } if len(fullpaths) == 0 { diff --git a/src/parser.cpp b/src/parser.cpp index 7e72f3c21..c004a8f65 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -4894,7 +4894,7 @@ gb_internal Ast *parse_foreign_decl(AstFile *f) { while (f->curr_token.kind != Token_CloseBrace && f->curr_token.kind != Token_EOF) { - Ast *path = parse_expr(f, true); + Ast *path = parse_expr(f, false); array_add(&filepaths, path); if (!allow_field_separator(f)) { From fa6e07d976871a9f1639bfadae708948fdb76a61 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 01:32:20 -0400 Subject: [PATCH 010/270] Make `ODIN_OS`, `ODIN_BUILD_MODE` comments congruent to underlying data Sourced from `src/checker.cpp`. --- base/runtime/core.odin | 3 +++ 1 file changed, 3 insertions(+) diff --git a/base/runtime/core.odin b/base/runtime/core.odin index 66099e787..4b6a1949e 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -481,7 +481,9 @@ Raw_Soa_Pointer :: struct { Linux, Essence, FreeBSD, + Haiku, OpenBSD, + NetBSD, WASI, JS, Freestanding, @@ -508,6 +510,7 @@ Odin_Arch_Type :: type_of(ODIN_ARCH) Odin_Build_Mode_Type :: enum int { Executable, Dynamic, + Static, Object, Assembly, LLVM_IR, From 01ad69413ac99c721ef6c6d8c921aaebb3d34e7d Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 01:36:44 -0400 Subject: [PATCH 011/270] Remove unused code in `internal_random_prime` --- core/math/big/prime.odin | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/math/big/prime.odin b/core/math/big/prime.odin index 5e7c02f37..7fc78c7e5 100644 --- a/core/math/big/prime.odin +++ b/core/math/big/prime.odin @@ -1188,9 +1188,6 @@ internal_random_prime :: proc(a: ^Int, size_in_bits: int, trials: int, flags := flags := flags trials := trials - t := &Int{} - defer internal_destroy(t) - /* Sanity check the input. */ From 223c987db2a80b6fba1cb4d240af0713e0b37e9a Mon Sep 17 00:00:00 2001 From: Vitalii Kravchenko Date: Tue, 28 May 2024 21:06:43 +0100 Subject: [PATCH 012/270] Take logger itself, not a pointer to logger in multi-logger destructor. --- core/log/multi_logger.odin | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/log/multi_logger.odin b/core/log/multi_logger.odin index 55c0f1436..96d0f3dbd 100644 --- a/core/log/multi_logger.odin +++ b/core/log/multi_logger.odin @@ -12,11 +12,10 @@ create_multi_logger :: proc(logs: ..Logger) -> Logger { return Logger{multi_logger_proc, data, Level.Debug, nil} } -destroy_multi_logger :: proc(log : ^Logger) { +destroy_multi_logger :: proc(log: Logger) { data := (^Multi_Logger_Data)(log.data) delete(data.loggers) - free(log.data) - log^ = nil_logger() + free(data) } multi_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, From 9b78061c8fb83202973d509b9af24f0a12786958 Mon Sep 17 00:00:00 2001 From: Dudejoe870 Date: Tue, 28 May 2024 22:25:16 -0500 Subject: [PATCH 013/270] Initial hash directive implementation --- src/check_builtin.cpp | 167 +++++++++++++++++++++++++++++------------- src/check_expr.cpp | 3 +- 2 files changed, 120 insertions(+), 50 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index d85e94db3..aa3be0bbd 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1403,6 +1403,65 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c return result; } +gb_internal bool check_hash_kind(CheckerContext *c, Ast *call, String const &hash_kind, u8 const *data, isize data_size, u64 *hash_value) { + ast_node(ce, CallExpr, call); + ast_node(bd, BasicDirective, ce->proc); + String name = bd->name.string; + GB_ASSERT(name == "load_hash" || name == "hash"); + + String supported_hashes[] = { + str_lit("adler32"), + str_lit("crc32"), + str_lit("crc64"), + str_lit("fnv32"), + str_lit("fnv64"), + str_lit("fnv32a"), + str_lit("fnv64a"), + str_lit("murmur32"), + str_lit("murmur64"), + }; + + bool hash_found = false; + for (isize i = 0; i < gb_count_of(supported_hashes); i++) { + if (supported_hashes[i] == hash_kind) { + hash_found = true; + break; + } + } + if (!hash_found) { + ERROR_BLOCK(); + error(ce->proc, "Invalid hash kind passed to `#%.*s`, got: %.*s", LIT(name), LIT(hash_kind)); + error_line("\tAvailable hash kinds:\n"); + for (isize i = 0; i < gb_count_of(supported_hashes); i++) { + error_line("\t%.*s\n", LIT(supported_hashes[i])); + } + return false; + } + + if (hash_kind == "adler32") { + *hash_value = gb_adler32(data, data_size); + } else if (hash_kind == "crc32") { + *hash_value = gb_crc32(data, data_size); + } else if (hash_kind == "crc64") { + *hash_value = gb_crc64(data, data_size); + } else if (hash_kind == "fnv32") { + *hash_value = gb_fnv32(data, data_size); + } else if (hash_kind == "fnv64") { + *hash_value = gb_fnv64(data, data_size); + } else if (hash_kind == "fnv32a") { + *hash_value = fnv32a(data, data_size); + } else if (hash_kind == "fnv64a") { + *hash_value = fnv64a(data, data_size); + } else if (hash_kind == "murmur32") { + *hash_value = gb_murmur32(data, data_size); + } else if (hash_kind == "murmur64") { + *hash_value = gb_murmur64(data, data_size); + } else { + compiler_error("unhandled hash kind: %.*s", LIT(hash_kind)); + } + return true; +} + gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *operand, Ast *call, Type *type_hint) { @@ -1480,35 +1539,6 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o String original_string = o.value.value_string; String hash_kind = o_hash.value.value_string; - String supported_hashes[] = { - str_lit("adler32"), - str_lit("crc32"), - str_lit("crc64"), - str_lit("fnv32"), - str_lit("fnv64"), - str_lit("fnv32a"), - str_lit("fnv64a"), - str_lit("murmur32"), - str_lit("murmur64"), - }; - - bool hash_found = false; - for (isize i = 0; i < gb_count_of(supported_hashes); i++) { - if (supported_hashes[i] == hash_kind) { - hash_found = true; - break; - } - } - if (!hash_found) { - ERROR_BLOCK(); - error(ce->proc, "Invalid hash kind passed to `#load_hash`, got: %.*s", LIT(hash_kind)); - error_line("\tAvailable hash kinds:\n"); - for (isize i = 0; i < gb_count_of(supported_hashes); i++) { - error_line("\t%.*s\n", LIT(supported_hashes[i])); - } - return false; - } - LoadFileCache *cache = nullptr; if (cache_load_file_directive(c, call, original_string, true, &cache)) { MUTEX_GUARD(&c->info->load_file_mutex); @@ -1520,26 +1550,9 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o } else { u8 *data = cache->data.text; isize file_size = cache->data.len; - if (hash_kind == "adler32") { - hash_value = gb_adler32(data, file_size); - } else if (hash_kind == "crc32") { - hash_value = gb_crc32(data, file_size); - } else if (hash_kind == "crc64") { - hash_value = gb_crc64(data, file_size); - } else if (hash_kind == "fnv32") { - hash_value = gb_fnv32(data, file_size); - } else if (hash_kind == "fnv64") { - hash_value = gb_fnv64(data, file_size); - } else if (hash_kind == "fnv32a") { - hash_value = fnv32a(data, file_size); - } else if (hash_kind == "fnv64a") { - hash_value = fnv64a(data, file_size); - } else if (hash_kind == "murmur32") { - hash_value = gb_murmur32(data, file_size); - } else if (hash_kind == "murmur64") { - hash_value = gb_murmur64(data, file_size); - } else { - compiler_error("unhandled hash kind: %.*s", LIT(hash_kind)); + + if (!check_hash_kind(c, call, hash_kind, data, file_size, &hash_value)) { + return false; } string_map_set(&cache->hashes, hash_kind, hash_value); } @@ -1550,6 +1563,62 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o return true; } return false; + } else if (name == "hash") { + if (ce->args.count != 2) { + if (ce->args.count == 0) { + error(ce->close, "'#hash' expects 2 argument, got 0"); + } else { + error(ce->args[0], "'#hash' expects 2 argument, got %td", ce->args.count); + } + return false; + } + + Ast *arg0 = ce->args[0]; + Ast *arg1 = ce->args[1]; + Operand o = {}; + check_expr(c, &o, arg0); + if (o.mode != Addressing_Constant) { + error(arg0, "'#hash' expected a constant string argument"); + return false; + } + + if (!is_type_string(o.type)) { + gbString str = type_to_string(o.type); + error(arg0, "'#hash' expected a constant string, got %s", str); + gb_string_free(str); + return false; + } + + Operand o_hash = {}; + check_expr(c, &o_hash, arg1); + if (o_hash.mode != Addressing_Constant) { + error(arg1, "'#hash' expected a constant string argument"); + return false; + } + + if (!is_type_string(o_hash.type)) { + gbString str = type_to_string(o.type); + error(arg1, "'#hash' expected a constant string, got %s", str); + gb_string_free(str); + return false; + } + gbAllocator a = heap_allocator(); + + GB_ASSERT(o.value.kind == ExactValue_String); + GB_ASSERT(o_hash.value.kind == ExactValue_String); + + String original_string = o.value.value_string; + String hash_kind = o_hash.value.value_string; + + // TODO: Cache hash values based off of string constant and hash kind? + u64 hash_value = 0; + if (check_hash_kind(c, call, hash_kind, original_string.text, original_string.len, &hash_value)) { + operand->type = t_untyped_integer; + operand->mode = Addressing_Constant; + operand->value = exact_value_u64(hash_value); + return true; + } + return false; } else if (name == "assert") { if (ce->args.count != 1 && ce->args.count != 2) { error(call, "'#assert' expects either 1 or 2 arguments, got %td", ce->args.count); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 8672941c1..2e008fe93 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7414,7 +7414,8 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c name == "config" || name == "load" || name == "load_directory" || - name == "load_hash" + name == "load_hash" || + name == "hash" ) { operand->mode = Addressing_Builtin; operand->builtin_id = BuiltinProc_DIRECTIVE; From 692ca13ffd0b84ffe1a1b67dbc618d17e11a0315 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 30 May 2024 01:49:30 +0200 Subject: [PATCH 014/270] wasm: fix the WheelEvent not storing data properly A `WheelEvent` is also an instanceof `MouseEvent` so it was never hitting the if statement for the `WheelEvent`. --- vendor/wasm/js/runtime.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index 8b4ad157b..f3df85648 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -1443,7 +1443,12 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { wmi.storeU8(off(1), !!e.isTrusted); let base = off(0, 8); - if (e instanceof MouseEvent) { + if (e instanceof WheelEvent) { + wmi.storeF64(off(8), e.deltaX); + wmi.storeF64(off(8), e.deltaY); + wmi.storeF64(off(8), e.deltaZ); + wmi.storeU32(off(4), e.deltaMode); + } else if (e instanceof MouseEvent) { wmi.storeI64(off(8), e.screenX); wmi.storeI64(off(8), e.screenY); wmi.storeI64(off(8), e.clientX); @@ -1482,11 +1487,6 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { wmi.storeI32(off(W), e.code.length) wmi.storeString(off(16, 1), e.key); wmi.storeString(off(16, 1), e.code); - } else if (e instanceof WheelEvent) { - wmi.storeF64(off(8), e.deltaX); - wmi.storeF64(off(8), e.deltaY); - wmi.storeF64(off(8), e.deltaZ); - wmi.storeU32(off(4), e.deltaMode); } else if (e.type === 'scroll') { wmi.storeF64(off(8), window.scrollX); wmi.storeF64(off(8), window.scrollY); From eeb057b76d8689dcff9d0bee44fbfcdfb07e8bc7 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 30 May 2024 01:53:38 +0200 Subject: [PATCH 015/270] darwin: fix sysroot retrieval for some systems Got a report on Discord that the current way didn't work for a user, this change did work and I confirmed with @harold-b (who initially added this) that it also works for them and is actually a better way. --- build_odin.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build_odin.sh b/build_odin.sh index ec65bb49d..d2f865e24 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -71,8 +71,8 @@ Darwin) fi darwin_sysroot= - if [ $(which xcode-select) ]; then - darwin_sysroot="--sysroot $(xcode-select -p)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" + if [ $(which xcrun) ]; then + darwin_sysroot="--sysroot $(xcrun --sdk macosx --show-sdk-path)" elif [[ -e "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" ]]; then darwin_sysroot="--sysroot /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" else From f6e699cd22828e50b00fb867e8ee573da9f6ca9a Mon Sep 17 00:00:00 2001 From: Erik Isidore Date: Thu, 30 May 2024 00:14:00 -0300 Subject: [PATCH 016/270] core:sys/linux - Add support for Unix Domain Socket addresses --- core/sys/linux/sys.odin | 5 +++++ core/sys/linux/types.odin | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/core/sys/linux/sys.odin b/core/sys/linux/sys.odin index 413c8742b..171829cde 100644 --- a/core/sys/linux/sys.odin +++ b/core/sys/linux/sys.odin @@ -487,6 +487,7 @@ connect :: proc "contextless" (sock: Fd, addr: ^$T) -> (Errno) where T == Sock_Addr_In || T == Sock_Addr_In6 || + T == Sock_Addr_Un || T == Sock_Addr_Any { ret := syscall(SYS_connect, sock, addr, size_of(T)) @@ -502,6 +503,7 @@ accept :: proc "contextless" (sock: Fd, addr: ^$T, sockflags: Socket_FD_Flags = where T == Sock_Addr_In || T == Sock_Addr_In6 || + T == Sock_Addr_Un || T == Sock_Addr_Any { addr_len: i32 = size_of(T) @@ -514,6 +516,7 @@ recvfrom :: proc "contextless" (sock: Fd, buf: []u8, flags: Socket_Msg, addr: ^$ where T == Sock_Addr_In || T == Sock_Addr_In6 || + T == Sock_Addr_Un || T == Sock_Addr_Any { addr_len: i32 = size_of(T) @@ -531,6 +534,7 @@ sendto :: proc "contextless" (sock: Fd, buf: []u8, flags: Socket_Msg, addr: ^$T) where T == Sock_Addr_In || T == Sock_Addr_In6 || + T == Sock_Addr_Un || T == Sock_Addr_Any { ret := syscall(SYS_sendto, sock, raw_data(buf), len(buf), transmute(i32) flags, addr, size_of(T)) @@ -590,6 +594,7 @@ bind :: proc "contextless" (sock: Fd, addr: ^$T) -> (Errno) where T == Sock_Addr_In || T == Sock_Addr_In6 || + T == Sock_Addr_Un || T == Sock_Addr_Any { ret := syscall(SYS_bind, sock, addr, size_of(T)) diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index 677bac7e0..fe48d195e 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -631,6 +631,14 @@ Sock_Addr_In6 :: struct #packed { sin6_scope_id: u32, } +/* + Struct representing Unix Domain Socket address +*/ +Sock_Addr_Un :: struct #packed { + sun_family: Address_Family, + sun_path: [108]u8 +} + /* Struct representing an arbitrary socket address. */ @@ -641,6 +649,7 @@ Sock_Addr_Any :: struct #raw_union { }, using ipv4: Sock_Addr_In, using ipv6: Sock_Addr_In6, + using uds: Sock_Addr_Un, } /* From 6bbe7d88b8fd93f6c07b7d1ece14af00353526eb Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Thu, 30 May 2024 16:19:33 +0200 Subject: [PATCH 017/270] microui: make clipboard optional during init Clipboard is an optional addition to the microui functionality, but the init function makes it look like it is required. Additionally, a bunch of the examples both on the Odin-Lang/examples repo and others are now "broken". --- vendor/microui/microui.odin | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/vendor/microui/microui.odin b/vendor/microui/microui.odin index cf39e2f55..e545b742b 100644 --- a/vendor/microui/microui.odin +++ b/vendor/microui/microui.odin @@ -319,7 +319,12 @@ default_draw_frame :: proc(ctx: ^Context, rect: Rect, colorid: Color_Type) { } } -init :: proc(ctx: ^Context, set_clipboard: proc(user_data: rawptr, text: string) -> (ok: bool), get_clipboard: proc(user_data: rawptr) -> (text: string, ok: bool), clipboard_user_data: rawptr) { +init :: proc( + ctx: ^Context, + set_clipboard: proc(user_data: rawptr, text: string) -> (ok: bool) = nil, + get_clipboard: proc(user_data: rawptr) -> (text: string, ok: bool) = nil, + clipboard_user_data: rawptr = nil, +) { ctx^ = {} // zero memory ctx.draw_frame = default_draw_frame ctx._style = default_style From 0514ee0410826d00f71fd8ce188371cf7afab494 Mon Sep 17 00:00:00 2001 From: Erik Isidore Date: Thu, 30 May 2024 16:12:20 -0300 Subject: [PATCH 018/270] PR#3655 - small linter issue correction --- core/sys/linux/types.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sys/linux/types.odin b/core/sys/linux/types.odin index fe48d195e..5053e1e1c 100644 --- a/core/sys/linux/types.odin +++ b/core/sys/linux/types.odin @@ -636,7 +636,7 @@ Sock_Addr_In6 :: struct #packed { */ Sock_Addr_Un :: struct #packed { sun_family: Address_Family, - sun_path: [108]u8 + sun_path: [108]u8, } /* From 66acbb7fed88ed9132dfc8107865e0ac27ed3ac8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 30 May 2024 21:48:23 +0100 Subject: [PATCH 019/270] Add `@(link_suffix=)` --- src/check_decl.cpp | 13 +++++++------ src/check_stmt.cpp | 4 ++-- src/checker.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/checker.hpp | 5 ++++- src/entity.cpp | 2 ++ 5 files changed, 61 insertions(+), 9 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 1ec366ae7..44b06d712 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -724,15 +724,16 @@ gb_internal Entity *init_entity_foreign_library(CheckerContext *ctx, Entity *e) return nullptr; } -gb_internal String handle_link_name(CheckerContext *ctx, Token token, String link_name, String link_prefix) { +gb_internal String handle_link_name(CheckerContext *ctx, Token token, String link_name, String link_prefix, String link_suffix) { if (link_prefix.len > 0) { if (link_name.len > 0) { error(token, "'link_name' and 'link_prefix' cannot be used together"); } else { - isize len = link_prefix.len + token.string.len; + isize len = link_prefix.len + token.string.len + link_suffix.len; u8 *name = gb_alloc_array(permanent_allocator(), u8, len+1); gb_memmove(name, &link_prefix[0], link_prefix.len); gb_memmove(name+link_prefix.len, &token.string[0], token.string.len); + gb_memmove(name+link_prefix.len+token.string.len, link_suffix.text, link_suffix.len); name[len] = 0; link_name = make_string(name, len); @@ -862,7 +863,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { } TypeProc *pt = &proc_type->Proc; - AttributeContext ac = make_attribute_context(e->Procedure.link_prefix); + AttributeContext ac = make_attribute_context(e->Procedure.link_prefix, e->Procedure.link_suffix); if (d != nullptr) { check_decl_attributes(ctx, d->attributes, proc_decl_attribute, &ac); @@ -1015,7 +1016,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { e->deprecated_message = ac.deprecated_message; e->warning_message = ac.warning_message; - ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix); + ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix,ac.link_suffix); if (ac.has_disabled_proc) { if (ac.disabled_proc) { e->flags |= EntityFlag_Disabled; @@ -1223,7 +1224,7 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast } e->flags |= EntityFlag_Visited; - AttributeContext ac = make_attribute_context(e->Variable.link_prefix); + AttributeContext ac = make_attribute_context(e->Variable.link_prefix, e->Variable.link_suffix); ac.init_expr_list_count = init_expr != nullptr ? 1 : 0; DeclInfo *decl = decl_info_of_entity(e); @@ -1244,7 +1245,7 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast if (ac.is_static) { error(e->token, "@(static) is not supported for global variables, nor required"); } - ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix); + ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix, ac.link_suffix); if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) { e->Variable.thread_local_model.len = 0; diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 866cdb5a1..2c37bced0 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -2020,7 +2020,7 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f // TODO NOTE(bill): This technically checks things multple times - AttributeContext ac = make_attribute_context(ctx->foreign_context.link_prefix); + AttributeContext ac = make_attribute_context(ctx->foreign_context.link_prefix, ctx->foreign_context.link_suffix); check_decl_attributes(ctx, vd->attributes, var_decl_attribute, &ac); for (isize i = 0; i < entity_count; i++) { @@ -2037,7 +2037,7 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f e->type = init_type; e->state = EntityState_Resolved; } - ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix); + ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix, ac.link_suffix); if (ac.link_name.len > 0) { e->Variable.link_name = ac.link_name; diff --git a/src/checker.cpp b/src/checker.cpp index 1ded6ea6e..ec58b9d8e 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -3127,6 +3127,18 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_block_decl_attribute) { error(elem, "Expected a string value for '%.*s'", LIT(name)); } return true; + } else if (name == "link_suffix") { + if (ev.kind == ExactValue_String) { + String link_suffix = ev.value_string; + if (!is_foreign_name_valid(link_suffix)) { + error(elem, "Invalid link suffix: '%.*s'", LIT(link_suffix)); + } else { + c->foreign_context.link_suffix = link_suffix; + } + } else { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; } else if (name == "private") { EntityVisiblityKind kind = EntityVisiblity_PrivateToPackage; if (ev.kind == ExactValue_Invalid) { @@ -3421,6 +3433,18 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { error(elem, "Expected a string value for '%.*s'", LIT(name)); } return true; + } else if (name == "link_suffix") { + ExactValue ev = check_decl_attribute_value(c, value); + + if (ev.kind == ExactValue_String) { + ac->link_suffix = ev.value_string; + if (!is_foreign_name_valid(ac->link_suffix)) { + error(elem, "Invalid link suffix: %.*s", LIT(ac->link_suffix)); + } + } else { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; } else if (name == "deprecated") { ExactValue ev = check_decl_attribute_value(c, value); @@ -3702,6 +3726,17 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) { error(elem, "Expected a string value for '%.*s'", LIT(name)); } return true; + } else if (name == "link_suffix") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind == ExactValue_String) { + ac->link_suffix = ev.value_string; + if (!is_foreign_name_valid(ac->link_suffix)) { + error(elem, "Invalid link suffix: %.*s", LIT(ac->link_suffix)); + } + } else { + error(elem, "Expected a string value for '%.*s'", LIT(name)); + } + return true; } else if (name == "link_section") { ExactValue ev = check_decl_attribute_value(c, value); if (ev.kind == ExactValue_String) { @@ -3733,6 +3768,7 @@ gb_internal DECL_ATTRIBUTE_PROC(const_decl_attribute) { name == "linkage" || name == "link_name" || name == "link_prefix" || + name == "link_suffix" || false) { error(elem, "@(%.*s) is not supported for compile time constant value declarations", LIT(name)); return true; @@ -3775,8 +3811,10 @@ gb_internal void check_decl_attributes(CheckerContext *c, Array const &at if (attributes.count == 0) return; String original_link_prefix = {}; + String original_link_suffix = {}; if (ac) { original_link_prefix = ac->link_prefix; + original_link_suffix = ac->link_suffix; } StringSet set = {}; @@ -3851,6 +3889,12 @@ gb_internal void check_decl_attributes(CheckerContext *c, Array const &at ac->link_prefix.len = 0; } } + if (ac->link_suffix.text == original_link_suffix.text) { + if (ac->link_name.len > 0) { + ac->link_suffix.text = nullptr; + ac->link_suffix.len = 0; + } + } } } @@ -4145,6 +4189,7 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) { e->Variable.foreign_library_ident = fl; e->Variable.link_prefix = c->foreign_context.link_prefix; + e->Variable.link_suffix = c->foreign_context.link_suffix; } Ast *init_expr = value; @@ -4219,6 +4264,7 @@ gb_internal void check_collect_value_decl(CheckerContext *c, Ast *decl) { } } e->Procedure.link_prefix = c->foreign_context.link_prefix; + e->Procedure.link_suffix = c->foreign_context.link_suffix; GB_ASSERT(cc != ProcCC_Invalid); pl->type->ProcType.calling_convention = cc; diff --git a/src/checker.hpp b/src/checker.hpp index 6ae7b90e2..539b72b2d 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -112,6 +112,7 @@ enum InstrumentationFlag : i32 { struct AttributeContext { String link_name; String link_prefix; + String link_suffix; String link_section; String linkage; isize init_expr_list_count; @@ -146,9 +147,10 @@ struct AttributeContext { String enable_target_feature; // will be enabled for the procedure only }; -gb_internal gb_inline AttributeContext make_attribute_context(String link_prefix) { +gb_internal gb_inline AttributeContext make_attribute_context(String link_prefix, String link_suffix) { AttributeContext ac = {}; ac.link_prefix = link_prefix; + ac.link_suffix = link_suffix; return ac; } @@ -302,6 +304,7 @@ struct ForeignContext { Ast * curr_library; ProcCallingConvention default_cc; String link_prefix; + String link_suffix; EntityVisiblityKind visibility_kind; }; diff --git a/src/entity.cpp b/src/entity.cpp index 1461b96d7..e4fc66dac 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -223,6 +223,7 @@ struct Entity { Ast * foreign_library_ident; String link_name; String link_prefix; + String link_suffix; String link_section; CommentGroup *docs; CommentGroup *comment; @@ -243,6 +244,7 @@ struct Entity { Ast * foreign_library_ident; String link_name; String link_prefix; + String link_suffix; DeferredProcedure deferred_procedure; struct GenProcsData *gen_procs; From ba1e9c8abe38edf833dbe89f1c22456d1a692072 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 30 May 2024 21:50:30 +0100 Subject: [PATCH 020/270] Fix #3651 --- vendor/darwin/Metal/MetalClasses.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/darwin/Metal/MetalClasses.odin b/vendor/darwin/Metal/MetalClasses.odin index ea1711bbc..c975e0cbb 100644 --- a/vendor/darwin/Metal/MetalClasses.odin +++ b/vendor/darwin/Metal/MetalClasses.odin @@ -4949,11 +4949,11 @@ CommandQueue_commandBuffer :: #force_inline proc "c" (self: ^CommandQueue) -> ^C return msgSend(^CommandBuffer, self, "commandBuffer") } @(objc_type=CommandQueue, objc_name="commandBufferWithDescriptor") -CommandQueue_commandBufferWithDescriptor :: #force_inline proc "c" (self: ^CommandQueue, descriptor: ^CommandBufferDescriptor) -> ^CommandQueue { +CommandQueue_commandBufferWithDescriptor :: #force_inline proc "c" (self: ^CommandQueue, descriptor: ^CommandBufferDescriptor) -> ^CommandBuffer { return msgSend(^CommandQueue, self, "commandBufferWithDescriptor:", descriptor) } @(objc_type=CommandQueue, objc_name="commandBufferWithUnretainedReferences") -CommandQueue_commandBufferWithUnretainedReferences :: #force_inline proc "c" (self: ^CommandQueue) -> ^CommandQueue { +CommandQueue_commandBufferWithUnretainedReferences :: #force_inline proc "c" (self: ^CommandQueue) -> ^CommandBuffer { return msgSend(^CommandQueue, self, "commandBufferWithUnretainedReferences") } @(objc_type=CommandQueue, objc_name="device") From 8db87170a9f491145cb10a69e52c6c2674efdf71 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 30 May 2024 21:53:23 +0100 Subject: [PATCH 021/270] Clean up `handle_link_name` handling of `link_suffix` --- src/check_decl.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 44b06d712..f2afce59c 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -725,23 +725,42 @@ gb_internal Entity *init_entity_foreign_library(CheckerContext *ctx, Entity *e) } gb_internal String handle_link_name(CheckerContext *ctx, Token token, String link_name, String link_prefix, String link_suffix) { + String original_link_name = link_name; if (link_prefix.len > 0) { - if (link_name.len > 0) { + if (original_link_name.len > 0) { error(token, "'link_name' and 'link_prefix' cannot be used together"); } else { - isize len = link_prefix.len + token.string.len + link_suffix.len; + isize len = link_prefix.len + token.string.len; u8 *name = gb_alloc_array(permanent_allocator(), u8, len+1); gb_memmove(name, &link_prefix[0], link_prefix.len); gb_memmove(name+link_prefix.len, &token.string[0], token.string.len); - gb_memmove(name+link_prefix.len+token.string.len, link_suffix.text, link_suffix.len); name[len] = 0; link_name = make_string(name, len); } } + + if (link_suffix.len > 0) { + if (original_link_name.len > 0) { + error(token, "'link_name' and 'link_suffix' cannot be used together"); + } else { + String new_name = token.string; + if (link_name != original_link_name) { + new_name = link_name; + } + + isize len = new_name.len + link_suffix.len; + u8 *name = gb_alloc_array(permanent_allocator(), u8, len+1); + gb_memmove(name, &new_name[0], new_name.len); + gb_memmove(name+new_name.len, &link_suffix[0], link_suffix.len); + name[len] = 0; + link_name = make_string(name, len); + } + } return link_name; } + gb_internal void check_objc_methods(CheckerContext *ctx, Entity *e, AttributeContext const &ac) { if (!(ac.objc_name.len || ac.objc_is_class_method || ac.objc_type)) { return; From e737122ce8c6273af7480635d4d113cf7a049914 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 30 May 2024 21:58:27 +0100 Subject: [PATCH 022/270] Add experimental target `orca_wasm32` --- src/build_settings.cpp | 14 +++++++++++++- src/checker.cpp | 1 + src/linker.cpp | 20 ++++++++++++++++---- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index d9454ba9b..376e56a8e 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -23,6 +23,7 @@ enum TargetOsKind : u16 { TargetOs_wasi, TargetOs_js, + TargetOs_orca, TargetOs_freestanding, @@ -90,6 +91,7 @@ gb_global String target_os_names[TargetOs_COUNT] = { str_lit("wasi"), str_lit("js"), + str_lit("orca"), str_lit("freestanding"), }; @@ -1067,6 +1069,15 @@ gb_global TargetMetrics target_wasi_wasm32 = { }; +gb_global TargetMetrics target_orca_wasm32 = { + TargetOs_orca, + TargetArch_wasm32, + 4, 4, 8, 16, + str_lit("wasm32-wasi-js"), + // str_lit("e-m:e-p:32:32-i64:64-n32:64-S128"), +}; + + gb_global TargetMetrics target_freestanding_wasm64p32 = { TargetOs_freestanding, TargetArch_wasm64p32, @@ -2012,8 +2023,9 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta // if (bc->metrics.arch == TargetArch_wasm64) { // link_flags = gb_string_appendc(link_flags, "-mwasm64 "); // } - if (bc->no_entry_point) { + if (bc->no_entry_point || bc->metrics.os == TargetOs_orca) { link_flags = gb_string_appendc(link_flags, "--no-entry "); + bc->no_entry_point = true; // just in case for the "orca" target } bc->link_flags = make_string_c(link_flags); diff --git a/src/checker.cpp b/src/checker.cpp index ec58b9d8e..2fd274975 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1016,6 +1016,7 @@ gb_internal void init_universal(void) { {"NetBSD", TargetOs_netbsd}, {"WASI", TargetOs_wasi}, {"JS", TargetOs_js}, + {"Orca", TargetOs_orca}, {"Freestanding", TargetOs_freestanding}, }; diff --git a/src/linker.cpp b/src/linker.cpp index c41f10593..b699c0dfb 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -69,15 +69,27 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (is_arch_wasm()) { timings_start_section(timings, str_lit("wasm-ld")); + String extra_orca_flags = {}; + #if defined(GB_SYSTEM_WINDOWS) + if (build_context.metrics.os == TargetOs_orca) { + extra_orca_flags = str_lit(" W:/orca/installation/dev-afb9591/bin/liborca_wasm.a --export-dynamic"); + } + result = system_exec_command_line_app("wasm-ld", - "\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s", + "\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s %.*s", LIT(build_context.ODIN_ROOT), - LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); + LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags), + LIT(extra_orca_flags)); #else + if (build_context.metrics.os == TargetOs_orca) { + extra_orca_flags = str_lit(" -L . -lorca --export-dynamic"); + } + result = system_exec_command_line_app("wasm-ld", - "wasm-ld \"%.*s.o\" -o \"%.*s\" %.*s %.*s", - LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags)); + "wasm-ld \"%.*s.o\" -o \"%.*s\" %.*s %.*s %.*s", + LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags), + LIT(extra_orca_flags)); #endif return result; } From 6d9957d7e4fe51bdb5c80c145fdad17fec0ecc54 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 30 May 2024 21:59:01 +0100 Subject: [PATCH 023/270] Fix types again --- vendor/darwin/Metal/MetalClasses.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/darwin/Metal/MetalClasses.odin b/vendor/darwin/Metal/MetalClasses.odin index c975e0cbb..2d681b0ee 100644 --- a/vendor/darwin/Metal/MetalClasses.odin +++ b/vendor/darwin/Metal/MetalClasses.odin @@ -4950,11 +4950,11 @@ CommandQueue_commandBuffer :: #force_inline proc "c" (self: ^CommandQueue) -> ^C } @(objc_type=CommandQueue, objc_name="commandBufferWithDescriptor") CommandQueue_commandBufferWithDescriptor :: #force_inline proc "c" (self: ^CommandQueue, descriptor: ^CommandBufferDescriptor) -> ^CommandBuffer { - return msgSend(^CommandQueue, self, "commandBufferWithDescriptor:", descriptor) + return msgSend(^CommandBuffer, self, "commandBufferWithDescriptor:", descriptor) } @(objc_type=CommandQueue, objc_name="commandBufferWithUnretainedReferences") CommandQueue_commandBufferWithUnretainedReferences :: #force_inline proc "c" (self: ^CommandQueue) -> ^CommandBuffer { - return msgSend(^CommandQueue, self, "commandBufferWithUnretainedReferences") + return msgSend(^CommandBuffer, self, "commandBufferWithUnretainedReferences") } @(objc_type=CommandQueue, objc_name="device") CommandQueue_device :: #force_inline proc "c" (self: ^CommandQueue) -> ^Device { From ae63fd923039b6d3ab9a8abe0407827bf5fcd86d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 30 May 2024 23:08:42 +0100 Subject: [PATCH 024/270] Fix #3649 --- src/llvm_backend_utility.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index f7674a8bc..94153e233 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -1365,6 +1365,8 @@ gb_internal lbValue lb_emit_deep_field_gep(lbProcedure *p, lbValue e, Selection } else { e = lb_emit_ptr_offset(p, lb_emit_load(p, arr), index); } + e.type = alloc_type_multi_pointer_to_pointer(e.type); + } else if (is_type_quaternion(type)) { e = lb_emit_struct_ep(p, e, index); } else if (is_type_raw_union(type)) { From 3a0ec3d6a88efcd8cf7466eb54a76dc6e0d027f5 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 31 May 2024 16:32:27 +0200 Subject: [PATCH 025/270] wasm: fix target wasm64p32 runtime procs LLVM generates calls with `i32` regardless of target, so if a call to any of these procs was generated this failed to compile. I opted to fix by changing from `int` to `i32` on wasm64p32 and adding `#any_int` so existing code keeps working. --- base/runtime/procs.odin | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/base/runtime/procs.odin b/base/runtime/procs.odin index 454574c35..c9347463b 100644 --- a/base/runtime/procs.odin +++ b/base/runtime/procs.odin @@ -26,12 +26,18 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { return dst } } else when ODIN_NO_CRT || (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) { + // NOTE: on wasm, calls to these procs are generated (by LLVM) with type `i32` instead of `int`. + // + // NOTE: `#any_int` is also needed, because calls that we generate (and package code) + // will be using `int` and need to be converted. + int_t :: i32 when ODIN_ARCH == .wasm64p32 else int + @(link_name="memset", linkage="strong", require) - memset :: proc "c" (ptr: rawptr, val: i32, len: int) -> rawptr { + memset :: proc "c" (ptr: rawptr, val: i32, #any_int len: int_t) -> rawptr { if ptr != nil && len != 0 { b := byte(val) p := ([^]byte)(ptr) - for i := 0; i < len; i += 1 { + for i := int_t(0); i < len; i += 1 { p[i] = b } } @@ -39,10 +45,10 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { } @(link_name="bzero", linkage="strong", require) - bzero :: proc "c" (ptr: rawptr, len: int) -> rawptr { + bzero :: proc "c" (ptr: rawptr, #any_int len: int_t) -> rawptr { if ptr != nil && len != 0 { p := ([^]byte)(ptr) - for i := 0; i < len; i += 1 { + for i := int_t(0); i < len; i += 1 { p[i] = 0 } } @@ -50,7 +56,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { } @(link_name="memmove", linkage="strong", require) - memmove :: proc "c" (dst, src: rawptr, len: int) -> rawptr { + memmove :: proc "c" (dst, src: rawptr, #any_int len: int_t) -> rawptr { d, s := ([^]byte)(dst), ([^]byte)(src) if d == s || len == 0 { return dst @@ -63,7 +69,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { } if s > d && uintptr(s)-uintptr(d) < uintptr(len) { - for i := 0; i < len; i += 1 { + for i := int_t(0); i < len; i += 1 { d[i] = s[i] } return dst @@ -71,10 +77,10 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { return memcpy(dst, src, len) } @(link_name="memcpy", linkage="strong", require) - memcpy :: proc "c" (dst, src: rawptr, len: int) -> rawptr { + memcpy :: proc "c" (dst, src: rawptr, #any_int len: int_t) -> rawptr { d, s := ([^]byte)(dst), ([^]byte)(src) if d != s { - for i := 0; i < len; i += 1 { + for i := int_t(0); i < len; i += 1 { d[i] = s[i] } } @@ -92,4 +98,4 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { } return ptr } -} \ No newline at end of file +} From 31a9b3f4282334329540b7e75bf6e5ec3dae6030 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 31 May 2024 16:35:30 +0100 Subject: [PATCH 026/270] `core:encoding/ini` --- core/encoding/ini/ini.odin | 189 +++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 core/encoding/ini/ini.odin diff --git a/core/encoding/ini/ini.odin b/core/encoding/ini/ini.odin new file mode 100644 index 000000000..eb0ad9e7c --- /dev/null +++ b/core/encoding/ini/ini.odin @@ -0,0 +1,189 @@ +package encoding_ini + +import "base:runtime" +import "base:intrinsics" +import "core:strings" +import "core:strconv" +import "core:io" +import "core:os" +import "core:fmt" +_ :: fmt + +Options :: struct { + comment: string, + key_lower_case: bool, +} + +DEFAULT_OPTIONS :: Options { + comment = ";", + key_lower_case = false, +} + +Iterator :: struct { + section: string, + _src: string, + options: Options, +} + +iterator_from_string :: proc(src: string, options := DEFAULT_OPTIONS) -> Iterator { + return { + section = "", + options = options, + _src = src, + } +} + + +// Returns the raw `key` and `value`. `ok` will be false if no more key=value pairs cannot be found. +// They key and value may be quoted, which may require the use of `strconv.unquote_string`. +iterate :: proc(it: ^Iterator) -> (key, value: string, ok: bool) { + for line_ in strings.split_lines_iterator(&it._src) { + line := strings.trim_space(line_) + + if len(line) == 0 { + continue + } + + if line[0] == '[' { + end_idx := strings.index_byte(line, ']') + if end_idx < 0 { + end_idx = len(line) + } + it.section = line[1:end_idx] + continue + } + + if it.options.comment != "" && strings.has_prefix(line, it.options.comment) { + continue + } + + equal := strings.index(line, " =") // check for things keys that `ctrl+= = zoom_in` + quote := strings.index_byte(line, '"') + if equal < 0 || quote > 0 && quote < equal { + equal = strings.index_byte(line, '=') + if equal < 0 { + continue + } + } else { + equal += 1 + } + + key = strings.trim_space(line[:equal]) + value = strings.trim_space(line[equal+1:]) + ok = true + return + } + + it.section = "" + return +} + +Map :: distinct map[string]map[string]string + +load_map_from_string :: proc(src: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error) { + unquote :: proc(val: string) -> (string, runtime.Allocator_Error) { + v, allocated, ok := strconv.unquote_string(val) + if !ok { + return strings.clone(val) + } + if allocated { + return v, nil + } + return strings.clone(v) + + } + + context.allocator = allocator + + it := iterator_from_string(src, options) + + for key, value in iterate(&it) { + section := it.section + if section not_in m { + section = strings.clone(section) or_return + m[section] = {} + } + + // store key-value pair + pairs := &m[section] + new_key := unquote(key) or_return + if options.key_lower_case { + old_key := new_key + new_key = strings.to_lower(key) or_return + delete(old_key) or_return + } + pairs[new_key] = unquote(value) or_return + } + return +} + +load_map_from_path :: proc(path: string, allocator: runtime.Allocator, options := DEFAULT_OPTIONS) -> (m: Map, err: runtime.Allocator_Error, ok: bool) { + data := os.read_entire_file(path, allocator) or_return + defer delete(data, allocator) + m, err = load_map_from_string(string(data), allocator, options) + ok = err != nil + defer if !ok { + delete_map(m) + } + return +} + +save_map_to_string :: proc(m: Map, allocator: runtime.Allocator) -> (data: string) { + b := strings.builder_make(allocator) + _, _ = write_map(strings.to_writer(&b), m) + return strings.to_string(b) +} + +delete_map :: proc(m: Map) { + allocator := m.allocator + for section, pairs in m { + for key, value in pairs { + delete(key, allocator) + delete(value, allocator) + } + delete(section) + } + delete(m) +} + +write_section :: proc(w: io.Writer, name: string, n_written: ^int = nil) -> (n: int, err: io.Error) { + defer if n_written != nil { n_written^ += n } + io.write_byte (w, '[', &n) or_return + io.write_string(w, name, &n) or_return + io.write_byte (w, ']', &n) or_return + return +} + +write_pair :: proc(w: io.Writer, key: string, value: $T, n_written: ^int = nil) -> (n: int, err: io.Error) { + defer if n_written != nil { n_written^ += n } + io.write_string(w, key, &n) or_return + io.write_string(w, " = ", &n) or_return + when intrinsics.type_is_string(T) { + val := string(value) + if len(val) > 0 && (val[0] == ' ' || val[len(val)-1] == ' ') { + io.write_quoted_string(w, val, n_written=&n) or_return + } else { + io.write_string(w, val, &n) or_return + } + } else { + n += fmt.wprint(w, value) + } + io.write_byte(w, '\n', &n) or_return + return +} + +write_map :: proc(w: io.Writer, m: Map) -> (n: int, err: io.Error) { + section_index := 0 + for section, pairs in m { + if section_index == 0 && section == "" { + // ignore section + } else { + write_section(w, section, &n) or_return + } + for key, value in pairs { + write_pair(w, key, value, &n) or_return + } + section_index += 1 + } + return +} From 8a521648b9f7e1ba29c64006b6469cbf2095db68 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 31 May 2024 20:54:20 +0200 Subject: [PATCH 027/270] wasm: fix runtime.js even more for wasm64p32 - make the int size configurable in the `runWasm` call, no more constants to hunt down and change - make storeU64 and storeI64 handle bigints, this is needed in the odin_dom library - fix alignment issues within init_event_raw --- vendor/wasm/js/runtime.js | 94 ++++++++++++++++++++++++++------------- 1 file changed, 64 insertions(+), 30 deletions(-) diff --git a/vendor/wasm/js/runtime.js b/vendor/wasm/js/runtime.js index f3df85648..5c7f97fae 100644 --- a/vendor/wasm/js/runtime.js +++ b/vendor/wasm/js/runtime.js @@ -13,14 +13,18 @@ function stripNewline(str) { return str.replace(/\n/, ' ') } -const INT_SIZE = 4; // NOTE: set to `8` if the target has 64 bit ints (`wasm64p32` for example). -const STRING_SIZE = 2*INT_SIZE; - class WasmMemoryInterface { constructor() { this.memory = null; this.exports = null; this.listenerMap = {}; + + // Size (in bytes) of the integer type, should be 4 on `js_wasm32` and 8 on `js_wasm64p32` + this.intSize = 4; + } + + setIntSize(size) { + this.intSize = size; } setMemory(memory) { @@ -73,21 +77,21 @@ class WasmMemoryInterface { loadF32(addr) { return this.mem.getFloat32(addr, true); } loadF64(addr) { return this.mem.getFloat64(addr, true); } loadInt(addr) { - if (INT_SIZE == 8) { + if (this.intSize == 8) { return this.loadI64(addr); - } else if (INT_SIZE == 4) { + } else if (this.intSize == 4) { return this.loadI32(addr); } else { - throw new Error('Unhandled `INT_SIZE`, expected `4` or `8`'); + throw new Error('Unhandled `intSize`, expected `4` or `8`'); } }; loadUint(addr) { - if (INT_SIZE == 8) { + if (this.intSize == 8) { return this.loadU64(addr); - } else if (INT_SIZE == 4) { + } else if (this.intSize == 4) { return this.loadU32(addr); } else { - throw new Error('Unhandled `INT_SIZE`, expected `4` or `8`'); + throw new Error('Unhandled `intSize`, expected `4` or `8`'); } }; loadPtr(addr) { return this.loadU32(addr); } @@ -108,31 +112,43 @@ class WasmMemoryInterface { storeU32(addr, value) { this.mem.setUint32 (addr, value, true); } storeI32(addr, value) { this.mem.setInt32 (addr, value, true); } storeU64(addr, value) { - this.mem.setUint32(addr + 0, value, true); - this.mem.setUint32(addr + 4, Math.floor(value / 4294967296), true); + this.mem.setUint32(addr + 0, Number(value), true); + + let div = 4294967296; + if (typeof value == 'bigint') { + div = BigInt(div); + } + + this.mem.setUint32(addr + 4, Math.floor(Number(value / div)), true); } storeI64(addr, value) { - this.mem.setUint32(addr + 0, value, true); - this.mem.setInt32 (addr + 4, Math.floor(value / 4294967296), true); + this.mem.setUint32(addr + 0, Number(value), true); + + let div = 4294967296; + if (typeof value == 'bigint') { + div = BigInt(div); + } + + this.mem.setInt32(addr + 4, Math.floor(Number(value / div)), true); } storeF32(addr, value) { this.mem.setFloat32(addr, value, true); } storeF64(addr, value) { this.mem.setFloat64(addr, value, true); } storeInt(addr, value) { - if (INT_SIZE == 8) { + if (this.intSize == 8) { this.storeI64(addr, value); - } else if (INT_SIZE == 4) { + } else if (this.intSize == 4) { this.storeI32(addr, value); } else { - throw new Error('Unhandled `INT_SIZE`, expected `4` or `8`'); + throw new Error('Unhandled `intSize`, expected `4` or `8`'); } } storeUint(addr, value) { - if (INT_SIZE == 8) { + if (this.intSize == 8) { this.storeU64(addr, value); - } else if (INT_SIZE == 4) { + } else if (this.intSize == 4) { this.storeU32(addr, value); } else { - throw new Error('Unhandled `INT_SIZE`, expected `4` or `8`'); + throw new Error('Unhandled `intSize`, expected `4` or `8`'); } } @@ -241,10 +257,11 @@ class WebGLInterface { } } getSource(shader, strings_ptr, strings_length) { + const stringSize = this.mem.intSize*2; let source = ""; for (let i = 0; i < strings_length; i++) { - let ptr = this.mem.loadPtr(strings_ptr + i*STRING_SIZE); - let len = this.mem.loadPtr(strings_ptr + i*STRING_SIZE + 4); + let ptr = this.mem.loadPtr(strings_ptr + i*stringSize); + let len = this.mem.loadPtr(strings_ptr + i*stringSize + 4); let str = this.mem.loadString(ptr, len); source += str; } @@ -1151,10 +1168,11 @@ class WebGLInterface { }, TransformFeedbackVaryings: (program, varyings_ptr, varyings_len, bufferMode) => { this.assertWebGL2(); + const stringSize = this.mem.intSize*2; let varyings = []; for (let i = 0; i < varyings_len; i++) { - let ptr = this.mem.loadPtr(varyings_ptr + i*STRING_SIZE + 0*4); - let len = this.mem.loadPtr(varyings_ptr + i*STRING_SIZE + 1*4); + let ptr = this.mem.loadPtr(varyings_ptr + i*stringSize + 0*4); + let len = this.mem.loadPtr(varyings_ptr + i*stringSize + 1*4); varyings.push(this.mem.loadString(ptr, len)); } this.ctx.transformFeedbackVaryings(this.programs[program], varyings, bufferMode); @@ -1393,7 +1411,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { }, "odin_dom": { init_event_raw: (ep) => { - const W = 4; + const W = wasmMemoryInterface.intSize; let offset = ep; let off = (amount, alignment) => { if (alignment === undefined) { @@ -1407,6 +1425,13 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { return x; }; + let align = (alignment) => { + const modulo = offset & (alignment-1); + if (modulo != 0) { + offset += alignment - modulo + } + }; + let wmi = wasmMemoryInterface; let e = event_temp_data.event; @@ -1427,10 +1452,12 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { wmi.storeU32(off(4), 0); } - wmi.storeUint(off(W), event_temp_data.id_ptr); - wmi.storeUint(off(W), event_temp_data.id_len); - wmi.storeUint(off(W), 0); // padding + align(W); + wmi.storeI32(off(W), event_temp_data.id_ptr); + wmi.storeUint(off(W), event_temp_data.id_len); + + align(8); wmi.storeF64(off(8), e.timeStamp*1e-3); wmi.storeU8(off(1), e.eventPhase); @@ -1442,7 +1469,7 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { wmi.storeU8(off(1), !!e.isComposing); wmi.storeU8(off(1), !!e.isTrusted); - let base = off(0, 8); + align(8); if (e instanceof WheelEvent) { wmi.storeF64(off(8), e.deltaX); wmi.storeF64(off(8), e.deltaY); @@ -1689,8 +1716,15 @@ function odinSetupDefaultImports(wasmMemoryInterface, consoleElement) { }; }; -async function runWasm(wasmPath, consoleElement, extraForeignImports) { - let wasmMemoryInterface = new WasmMemoryInterface(); +/** + * @param {string} wasmPath - Path to the WASM module to run + * @param {?HTMLPreElement} consoleElement - Optional console/pre element to append output to, in addition to the console + * @param {any} extraForeignImports - Imports, in addition to the default runtime to provide the module + * @param {?int} intSize - Size (in bytes) of the integer type, should be 4 on `js_wasm32` and 8 on `js_wasm64p32` + */ +async function runWasm(wasmPath, consoleElement, extraForeignImports, intSize = 4) { + const wasmMemoryInterface = new WasmMemoryInterface(); + wasmMemoryInterface.setIntSize(intSize); let imports = odinSetupDefaultImports(wasmMemoryInterface, consoleElement); let exports = {}; From 451dc645df9f12058267d7d5ae0f8ac8a1fb8998 Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Fri, 31 May 2024 21:55:40 +0200 Subject: [PATCH 028/270] Add unreachable to base/builtin/builtin.odin --- base/builtin/builtin.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/builtin/builtin.odin b/base/builtin/builtin.odin index 5cba3c8ea..c4a9b141f 100644 --- a/base/builtin/builtin.odin +++ b/base/builtin/builtin.odin @@ -126,3 +126,5 @@ clamp :: proc(value, minimum, maximum: T) -> T --- soa_zip :: proc(slices: ...) -> #soa[]Struct --- soa_unzip :: proc(value: $S/#soa[]$E) -> (slices: ...) --- + +unreachable :: proc() -> ! --- From f49575f1fbb0009b57d98cdacb90f2fed6b2c075 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Sat, 18 May 2024 17:55:48 +0900 Subject: [PATCH 029/270] core/simd/x86: Add the AES-NI intrinsics --- core/simd/x86/aes.odin | 49 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 core/simd/x86/aes.odin diff --git a/core/simd/x86/aes.odin b/core/simd/x86/aes.odin new file mode 100644 index 000000000..3a32de0d6 --- /dev/null +++ b/core/simd/x86/aes.odin @@ -0,0 +1,49 @@ +//+build i386, amd64 +package simd_x86 + +@(require_results, enable_target_feature = "aes") +_mm_aesdec :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return aesdec(a, b) +} + +@(require_results, enable_target_feature = "aes") +_mm_aesdeclast :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return aesdeclast(a, b) +} + +@(require_results, enable_target_feature = "aes") +_mm_aesenc :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return aesenc(a, b) +} + +@(require_results, enable_target_feature = "aes") +_mm_aesenclast :: #force_inline proc "c" (a, b: __m128i) -> __m128i { + return aesenclast(a, b) +} + +@(require_results, enable_target_feature = "aes") +_mm_aesimc :: #force_inline proc "c" (a: __m128i) -> __m128i { + return aesimc(a) +} + +@(require_results, enable_target_feature = "aes") +_mm_aeskeygenassist :: #force_inline proc "c" (a: __m128i, $IMM8: u8) -> __m128i { + return aeskeygenassist(a, u8(IMM8)) +} + + +@(private, default_calling_convention = "none") +foreign _ { + @(link_name = "llvm.x86.aesni.aesdec") + aesdec :: proc(a, b: __m128i) -> __m128i --- + @(link_name = "llvm.x86.aesni.aesdeclast") + aesdeclast :: proc(a, b: __m128i) -> __m128i --- + @(link_name = "llvm.x86.aesni.aesenc") + aesenc :: proc(a, b: __m128i) -> __m128i --- + @(link_name = "llvm.x86.aesni.aesenclast") + aesenclast :: proc(a, b: __m128i) -> __m128i --- + @(link_name = "llvm.x86.aesni.aesimc") + aesimc :: proc(a: __m128i) -> __m128i --- + @(link_name = "llvm.x86.aesni.aeskeygenassist") + aeskeygenassist :: proc(a: __m128i, imm8: u8) -> __m128i --- +} From cba58924a895822d8160c957b0e859a358a29391 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Mon, 20 May 2024 21:46:18 +0900 Subject: [PATCH 030/270] core/crypto/_aes: 64-bit portable implementation --- core/crypto/_aes/aes.odin | 22 ++ core/crypto/_aes/ct64/api.odin | 96 ++++++++ core/crypto/_aes/ct64/ct64.odin | 265 +++++++++++++++++++++++ core/crypto/_aes/ct64/ct64_dec.odin | 135 ++++++++++++ core/crypto/_aes/ct64/ct64_enc.odin | 95 ++++++++ core/crypto/_aes/ct64/ct64_keysched.odin | 179 +++++++++++++++ core/crypto/_aes/ct64/helpers.odin | 75 +++++++ 7 files changed, 867 insertions(+) create mode 100644 core/crypto/_aes/aes.odin create mode 100644 core/crypto/_aes/ct64/api.odin create mode 100644 core/crypto/_aes/ct64/ct64.odin create mode 100644 core/crypto/_aes/ct64/ct64_dec.odin create mode 100644 core/crypto/_aes/ct64/ct64_enc.odin create mode 100644 core/crypto/_aes/ct64/ct64_keysched.odin create mode 100644 core/crypto/_aes/ct64/helpers.odin diff --git a/core/crypto/_aes/aes.odin b/core/crypto/_aes/aes.odin new file mode 100644 index 000000000..74906fcd4 --- /dev/null +++ b/core/crypto/_aes/aes.odin @@ -0,0 +1,22 @@ +package _aes + +// KEY_SIZE_128 is the AES-128 key size in bytes. +KEY_SIZE_128 :: 16 +// KEY_SIZE_192 is the AES-192 key size in bytes. +KEY_SIZE_192 :: 24 +// KEY_SIZE_256 is the AES-256 key size in bytes. +KEY_SIZE_256 :: 32 + +// BLOCK_SIZE is the AES block size in bytes. +BLOCK_SIZE :: 16 + + +// ROUNDS_128 is the number of rounds for AES-128. +ROUNDS_128 :: 10 +// ROUNDS_192 is the number of rounds for AES-192. +ROUNDS_192 :: 12 +// ROUNDS_256 is the number of rounds for AES-256. +ROUNDS_256 :: 14 + +// RCON is the AES keyschedule round constants. +RCON := [10]byte{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36} diff --git a/core/crypto/_aes/ct64/api.odin b/core/crypto/_aes/ct64/api.odin new file mode 100644 index 000000000..ae624971c --- /dev/null +++ b/core/crypto/_aes/ct64/api.odin @@ -0,0 +1,96 @@ +package aes_ct64 + +import "base:intrinsics" +import "core:mem" + +STRIDE :: 4 + +// Context is a keyed AES (ECB) instance. +Context :: struct { + _sk_exp: [120]u64, + _num_rounds: int, + _is_initialized: bool, +} + +// init initializes a context for AES with the provided key. +init :: proc(ctx: ^Context, key: []byte) { + skey: [30]u64 = --- + + ctx._num_rounds = keysched(skey[:], key) + skey_expand(ctx._sk_exp[:], skey[:], ctx._num_rounds) + ctx._is_initialized = true +} + +// encrypt_block sets `dst` to `AES-ECB-Encrypt(src)`. +encrypt_block :: proc(ctx: ^Context, dst, src: []byte) { + assert(ctx._is_initialized) + + q: [8]u64 + load_blockx1(&q, src) + _encrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blockx1(dst, &q) +} + +// encrypt_block sets `dst` to `AES-ECB-Decrypt(src)`. +decrypt_block :: proc(ctx: ^Context, dst, src: []byte) { + assert(ctx._is_initialized) + + q: [8]u64 + load_blockx1(&q, src) + _decrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blockx1(dst, &q) +} + +// encrypt_blocks sets `dst` to `AES-ECB-Encrypt(src[0], .. src[n])`. +encrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) { + assert(ctx._is_initialized) + + q: [8]u64 = --- + src, dst := src, dst + + n := len(src) + for n > 4 { + load_blocks(&q, src[0:4]) + _encrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blocks(dst[0:4], &q) + + src = src[4:] + dst = dst[4:] + n -= 4 + } + if n > 0 { + load_blocks(&q, src) + _encrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blocks(dst, &q) + } +} + +// decrypt_blocks sets dst to `AES-ECB-Decrypt(src[0], .. src[n])`. +decrypt_blocks :: proc(ctx: ^Context, dst, src: [][]byte) { + assert(ctx._is_initialized) + + q: [8]u64 = --- + src, dst := src, dst + + n := len(src) + for n > 4 { + load_blocks(&q, src[0:4]) + _decrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blocks(dst[0:4], &q) + + src = src[4:] + dst = dst[4:] + n -= 4 + } + if n > 0 { + load_blocks(&q, src) + _decrypt(&q, ctx._sk_exp[:], ctx._num_rounds) + store_blocks(dst, &q) + } +} + +// reset sanitizes the Context. The Context must be re-initialized to +// be used again. +reset :: proc(ctx: ^Context) { + mem.zero_explicit(ctx, size_of(ctx)) +} diff --git a/core/crypto/_aes/ct64/ct64.odin b/core/crypto/_aes/ct64/ct64.odin new file mode 100644 index 000000000..f198cab81 --- /dev/null +++ b/core/crypto/_aes/ct64/ct64.odin @@ -0,0 +1,265 @@ +// Copyright (c) 2016 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package aes_ct64 + +import "base:intrinsics" + +// Bitsliced AES for 64-bit general purpose (integer) registers. Each +// invocation will process up to 4 blocks at a time. This implementation +// is derived from the BearSSL ct64 code, and distributed under a 1-clause +// BSD license with permission from the original author. +// +// WARNING: "hic sunt dracones" +// +// This package also deliberately exposes enough internals to be able to +// function as a replacement for `AESENC` and `AESDEC` from AES-NI, to +// allow the implementation of non-AES primitives that use the AES round +// function such as AEGIS and Deoxys-II. This should ONLY be done when +// implementing something other than AES itself. + +sub_bytes :: proc "contextless" (q: ^[8]u64) { + // This S-box implementation is a straightforward translation of + // the circuit described by Boyar and Peralta in "A new + // combinational logic minimization technique with applications + // to cryptology" (https://eprint.iacr.org/2009/191.pdf). + // + // Note that variables x* (input) and s* (output) are numbered + // in "reverse" order (x0 is the high bit, x7 is the low bit). + + x0 := q[7] + x1 := q[6] + x2 := q[5] + x3 := q[4] + x4 := q[3] + x5 := q[2] + x6 := q[1] + x7 := q[0] + + // Top linear transformation. + y14 := x3 ~ x5 + y13 := x0 ~ x6 + y9 := x0 ~ x3 + y8 := x0 ~ x5 + t0 := x1 ~ x2 + y1 := t0 ~ x7 + y4 := y1 ~ x3 + y12 := y13 ~ y14 + y2 := y1 ~ x0 + y5 := y1 ~ x6 + y3 := y5 ~ y8 + t1 := x4 ~ y12 + y15 := t1 ~ x5 + y20 := t1 ~ x1 + y6 := y15 ~ x7 + y10 := y15 ~ t0 + y11 := y20 ~ y9 + y7 := x7 ~ y11 + y17 := y10 ~ y11 + y19 := y10 ~ y8 + y16 := t0 ~ y11 + y21 := y13 ~ y16 + y18 := x0 ~ y16 + + // Non-linear section. + t2 := y12 & y15 + t3 := y3 & y6 + t4 := t3 ~ t2 + t5 := y4 & x7 + t6 := t5 ~ t2 + t7 := y13 & y16 + t8 := y5 & y1 + t9 := t8 ~ t7 + t10 := y2 & y7 + t11 := t10 ~ t7 + t12 := y9 & y11 + t13 := y14 & y17 + t14 := t13 ~ t12 + t15 := y8 & y10 + t16 := t15 ~ t12 + t17 := t4 ~ t14 + t18 := t6 ~ t16 + t19 := t9 ~ t14 + t20 := t11 ~ t16 + t21 := t17 ~ y20 + t22 := t18 ~ y19 + t23 := t19 ~ y21 + t24 := t20 ~ y18 + + t25 := t21 ~ t22 + t26 := t21 & t23 + t27 := t24 ~ t26 + t28 := t25 & t27 + t29 := t28 ~ t22 + t30 := t23 ~ t24 + t31 := t22 ~ t26 + t32 := t31 & t30 + t33 := t32 ~ t24 + t34 := t23 ~ t33 + t35 := t27 ~ t33 + t36 := t24 & t35 + t37 := t36 ~ t34 + t38 := t27 ~ t36 + t39 := t29 & t38 + t40 := t25 ~ t39 + + t41 := t40 ~ t37 + t42 := t29 ~ t33 + t43 := t29 ~ t40 + t44 := t33 ~ t37 + t45 := t42 ~ t41 + z0 := t44 & y15 + z1 := t37 & y6 + z2 := t33 & x7 + z3 := t43 & y16 + z4 := t40 & y1 + z5 := t29 & y7 + z6 := t42 & y11 + z7 := t45 & y17 + z8 := t41 & y10 + z9 := t44 & y12 + z10 := t37 & y3 + z11 := t33 & y4 + z12 := t43 & y13 + z13 := t40 & y5 + z14 := t29 & y2 + z15 := t42 & y9 + z16 := t45 & y14 + z17 := t41 & y8 + + // Bottom linear transformation. + t46 := z15 ~ z16 + t47 := z10 ~ z11 + t48 := z5 ~ z13 + t49 := z9 ~ z10 + t50 := z2 ~ z12 + t51 := z2 ~ z5 + t52 := z7 ~ z8 + t53 := z0 ~ z3 + t54 := z6 ~ z7 + t55 := z16 ~ z17 + t56 := z12 ~ t48 + t57 := t50 ~ t53 + t58 := z4 ~ t46 + t59 := z3 ~ t54 + t60 := t46 ~ t57 + t61 := z14 ~ t57 + t62 := t52 ~ t58 + t63 := t49 ~ t58 + t64 := z4 ~ t59 + t65 := t61 ~ t62 + t66 := z1 ~ t63 + s0 := t59 ~ t63 + s6 := t56 ~ ~t62 + s7 := t48 ~ ~t60 + t67 := t64 ~ t65 + s3 := t53 ~ t66 + s4 := t51 ~ t66 + s5 := t47 ~ t65 + s1 := t64 ~ ~s3 + s2 := t55 ~ ~t67 + + q[7] = s0 + q[6] = s1 + q[5] = s2 + q[4] = s3 + q[3] = s4 + q[2] = s5 + q[1] = s6 + q[0] = s7 +} + +orthogonalize :: proc "contextless" (q: ^[8]u64) { + CL2 :: 0x5555555555555555 + CH2 :: 0xAAAAAAAAAAAAAAAA + q[0], q[1] = (q[0] & CL2) | ((q[1] & CL2) << 1), ((q[0] & CH2) >> 1) | (q[1] & CH2) + q[2], q[3] = (q[2] & CL2) | ((q[3] & CL2) << 1), ((q[2] & CH2) >> 1) | (q[3] & CH2) + q[4], q[5] = (q[4] & CL2) | ((q[5] & CL2) << 1), ((q[4] & CH2) >> 1) | (q[5] & CH2) + q[6], q[7] = (q[6] & CL2) | ((q[7] & CL2) << 1), ((q[6] & CH2) >> 1) | (q[7] & CH2) + + CL4 :: 0x3333333333333333 + CH4 :: 0xCCCCCCCCCCCCCCCC + q[0], q[2] = (q[0] & CL4) | ((q[2] & CL4) << 2), ((q[0] & CH4) >> 2) | (q[2] & CH4) + q[1], q[3] = (q[1] & CL4) | ((q[3] & CL4) << 2), ((q[1] & CH4) >> 2) | (q[3] & CH4) + q[4], q[6] = (q[4] & CL4) | ((q[6] & CL4) << 2), ((q[4] & CH4) >> 2) | (q[6] & CH4) + q[5], q[7] = (q[5] & CL4) | ((q[7] & CL4) << 2), ((q[5] & CH4) >> 2) | (q[7] & CH4) + + CL8 :: 0x0F0F0F0F0F0F0F0F + CH8 :: 0xF0F0F0F0F0F0F0F0 + q[0], q[4] = (q[0] & CL8) | ((q[4] & CL8) << 4), ((q[0] & CH8) >> 4) | (q[4] & CH8) + q[1], q[5] = (q[1] & CL8) | ((q[5] & CL8) << 4), ((q[1] & CH8) >> 4) | (q[5] & CH8) + q[2], q[6] = (q[2] & CL8) | ((q[6] & CL8) << 4), ((q[2] & CH8) >> 4) | (q[6] & CH8) + q[3], q[7] = (q[3] & CL8) | ((q[7] & CL8) << 4), ((q[3] & CH8) >> 4) | (q[7] & CH8) +} + +@(require_results) +interleave_in :: proc "contextless" (w: []u32) -> (q0, q1: u64) #no_bounds_check { + if len(w) < 4 { + intrinsics.trap() + } + x0, x1, x2, x3 := u64(w[0]), u64(w[1]), u64(w[2]), u64(w[3]) + x0 |= (x0 << 16) + x1 |= (x1 << 16) + x2 |= (x2 << 16) + x3 |= (x3 << 16) + x0 &= 0x0000FFFF0000FFFF + x1 &= 0x0000FFFF0000FFFF + x2 &= 0x0000FFFF0000FFFF + x3 &= 0x0000FFFF0000FFFF + x0 |= (x0 << 8) + x1 |= (x1 << 8) + x2 |= (x2 << 8) + x3 |= (x3 << 8) + x0 &= 0x00FF00FF00FF00FF + x1 &= 0x00FF00FF00FF00FF + x2 &= 0x00FF00FF00FF00FF + x3 &= 0x00FF00FF00FF00FF + q0 = x0 | (x2 << 8) + q1 = x1 | (x3 << 8) + return +} + +@(require_results) +interleave_out :: proc "contextless" (q0, q1: u64) -> (w0, w1, w2, w3: u32) { + x0 := q0 & 0x00FF00FF00FF00FF + x1 := q1 & 0x00FF00FF00FF00FF + x2 := (q0 >> 8) & 0x00FF00FF00FF00FF + x3 := (q1 >> 8) & 0x00FF00FF00FF00FF + x0 |= (x0 >> 8) + x1 |= (x1 >> 8) + x2 |= (x2 >> 8) + x3 |= (x3 >> 8) + x0 &= 0x0000FFFF0000FFFF + x1 &= 0x0000FFFF0000FFFF + x2 &= 0x0000FFFF0000FFFF + x3 &= 0x0000FFFF0000FFFF + w0 = u32(x0) | u32(x0 >> 16) + w1 = u32(x1) | u32(x1 >> 16) + w2 = u32(x2) | u32(x2 >> 16) + w3 = u32(x3) | u32(x3 >> 16) + return +} + +@(private) +rotr32 :: #force_inline proc "contextless" (x: u64) -> u64 { + return (x << 32) | (x >> 32) +} diff --git a/core/crypto/_aes/ct64/ct64_dec.odin b/core/crypto/_aes/ct64/ct64_dec.odin new file mode 100644 index 000000000..408ee6002 --- /dev/null +++ b/core/crypto/_aes/ct64/ct64_dec.odin @@ -0,0 +1,135 @@ +// Copyright (c) 2016 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package aes_ct64 + +import "base:intrinsics" + +inv_sub_bytes :: proc "contextless" (q: ^[8]u64) { + // AES S-box is: + // S(x) = A(I(x)) ^ 0x63 + // where I() is inversion in GF(256), and A() is a linear + // transform (0 is formally defined to be its own inverse). + // Since inversion is an involution, the inverse S-box can be + // computed from the S-box as: + // iS(x) = B(S(B(x ^ 0x63)) ^ 0x63) + // where B() is the inverse of A(). Indeed, for any y in GF(256): + // iS(S(y)) = B(A(I(B(A(I(y)) ^ 0x63 ^ 0x63))) ^ 0x63 ^ 0x63) = y + // + // Note: we reuse the implementation of the forward S-box, + // instead of duplicating it here, so that total code size is + // lower. By merging the B() transforms into the S-box circuit + // we could make faster CBC decryption, but CBC decryption is + // already quite faster than CBC encryption because we can + // process four blocks in parallel. + + q0 := ~q[0] + q1 := ~q[1] + q2 := q[2] + q3 := q[3] + q4 := q[4] + q5 := ~q[5] + q6 := ~q[6] + q7 := q[7] + q[7] = q1 ~ q4 ~ q6 + q[6] = q0 ~ q3 ~ q5 + q[5] = q7 ~ q2 ~ q4 + q[4] = q6 ~ q1 ~ q3 + q[3] = q5 ~ q0 ~ q2 + q[2] = q4 ~ q7 ~ q1 + q[1] = q3 ~ q6 ~ q0 + q[0] = q2 ~ q5 ~ q7 + + sub_bytes(q) + + q0 = ~q[0] + q1 = ~q[1] + q2 = q[2] + q3 = q[3] + q4 = q[4] + q5 = ~q[5] + q6 = ~q[6] + q7 = q[7] + q[7] = q1 ~ q4 ~ q6 + q[6] = q0 ~ q3 ~ q5 + q[5] = q7 ~ q2 ~ q4 + q[4] = q6 ~ q1 ~ q3 + q[3] = q5 ~ q0 ~ q2 + q[2] = q4 ~ q7 ~ q1 + q[1] = q3 ~ q6 ~ q0 + q[0] = q2 ~ q5 ~ q7 +} + +inv_shift_rows :: proc "contextless" (q: ^[8]u64) { + for x, i in q { + q[i] = + (x & 0x000000000000FFFF) | + ((x & 0x000000000FFF0000) << 4) | + ((x & 0x00000000F0000000) >> 12) | + ((x & 0x000000FF00000000) << 8) | + ((x & 0x0000FF0000000000) >> 8) | + ((x & 0x000F000000000000) << 12) | + ((x & 0xFFF0000000000000) >> 4) + } +} + +inv_mix_columns :: proc "contextless" (q: ^[8]u64) { + q0 := q[0] + q1 := q[1] + q2 := q[2] + q3 := q[3] + q4 := q[4] + q5 := q[5] + q6 := q[6] + q7 := q[7] + r0 := (q0 >> 16) | (q0 << 48) + r1 := (q1 >> 16) | (q1 << 48) + r2 := (q2 >> 16) | (q2 << 48) + r3 := (q3 >> 16) | (q3 << 48) + r4 := (q4 >> 16) | (q4 << 48) + r5 := (q5 >> 16) | (q5 << 48) + r6 := (q6 >> 16) | (q6 << 48) + r7 := (q7 >> 16) | (q7 << 48) + + q[0] = q5 ~ q6 ~ q7 ~ r0 ~ r5 ~ r7 ~ rotr32(q0 ~ q5 ~ q6 ~ r0 ~ r5) + q[1] = q0 ~ q5 ~ r0 ~ r1 ~ r5 ~ r6 ~ r7 ~ rotr32(q1 ~ q5 ~ q7 ~ r1 ~ r5 ~ r6) + q[2] = q0 ~ q1 ~ q6 ~ r1 ~ r2 ~ r6 ~ r7 ~ rotr32(q0 ~ q2 ~ q6 ~ r2 ~ r6 ~ r7) + q[3] = q0 ~ q1 ~ q2 ~ q5 ~ q6 ~ r0 ~ r2 ~ r3 ~ r5 ~ rotr32(q0 ~ q1 ~ q3 ~ q5 ~ q6 ~ q7 ~ r0 ~ r3 ~ r5 ~ r7) + q[4] = q1 ~ q2 ~ q3 ~ q5 ~ r1 ~ r3 ~ r4 ~ r5 ~ r6 ~ r7 ~ rotr32(q1 ~ q2 ~ q4 ~ q5 ~ q7 ~ r1 ~ r4 ~ r5 ~ r6) + q[5] = q2 ~ q3 ~ q4 ~ q6 ~ r2 ~ r4 ~ r5 ~ r6 ~ r7 ~ rotr32(q2 ~ q3 ~ q5 ~ q6 ~ r2 ~ r5 ~ r6 ~ r7) + q[6] = q3 ~ q4 ~ q5 ~ q7 ~ r3 ~ r5 ~ r6 ~ r7 ~ rotr32(q3 ~ q4 ~ q6 ~ q7 ~ r3 ~ r6 ~ r7) + q[7] = q4 ~ q5 ~ q6 ~ r4 ~ r6 ~ r7 ~ rotr32(q4 ~ q5 ~ q7 ~ r4 ~ r7) +} + +@(private) +_decrypt :: proc "contextless" (q: ^[8]u64, skey: []u64, num_rounds: int) { + add_round_key(q, skey[num_rounds << 3:]) + for u := num_rounds - 1; u > 0; u -= 1 { + inv_shift_rows(q) + inv_sub_bytes(q) + add_round_key(q, skey[u << 3:]) + inv_mix_columns(q) + } + inv_shift_rows(q) + inv_sub_bytes(q) + add_round_key(q, skey) +} diff --git a/core/crypto/_aes/ct64/ct64_enc.odin b/core/crypto/_aes/ct64/ct64_enc.odin new file mode 100644 index 000000000..36d4aebc8 --- /dev/null +++ b/core/crypto/_aes/ct64/ct64_enc.odin @@ -0,0 +1,95 @@ +// Copyright (c) 2016 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package aes_ct64 + +import "base:intrinsics" + +add_round_key :: proc "contextless" (q: ^[8]u64, sk: []u64) #no_bounds_check { + if len(sk) < 8 { + intrinsics.trap() + } + + q[0] ~= sk[0] + q[1] ~= sk[1] + q[2] ~= sk[2] + q[3] ~= sk[3] + q[4] ~= sk[4] + q[5] ~= sk[5] + q[6] ~= sk[6] + q[7] ~= sk[7] +} + +shift_rows :: proc "contextless" (q: ^[8]u64) { + for x, i in q { + q[i] = + (x & 0x000000000000FFFF) | + ((x & 0x00000000FFF00000) >> 4) | + ((x & 0x00000000000F0000) << 12) | + ((x & 0x0000FF0000000000) >> 8) | + ((x & 0x000000FF00000000) << 8) | + ((x & 0xF000000000000000) >> 12) | + ((x & 0x0FFF000000000000) << 4) + } +} + +mix_columns :: proc "contextless" (q: ^[8]u64) { + q0 := q[0] + q1 := q[1] + q2 := q[2] + q3 := q[3] + q4 := q[4] + q5 := q[5] + q6 := q[6] + q7 := q[7] + r0 := (q0 >> 16) | (q0 << 48) + r1 := (q1 >> 16) | (q1 << 48) + r2 := (q2 >> 16) | (q2 << 48) + r3 := (q3 >> 16) | (q3 << 48) + r4 := (q4 >> 16) | (q4 << 48) + r5 := (q5 >> 16) | (q5 << 48) + r6 := (q6 >> 16) | (q6 << 48) + r7 := (q7 >> 16) | (q7 << 48) + + q[0] = q7 ~ r7 ~ r0 ~ rotr32(q0 ~ r0) + q[1] = q0 ~ r0 ~ q7 ~ r7 ~ r1 ~ rotr32(q1 ~ r1) + q[2] = q1 ~ r1 ~ r2 ~ rotr32(q2 ~ r2) + q[3] = q2 ~ r2 ~ q7 ~ r7 ~ r3 ~ rotr32(q3 ~ r3) + q[4] = q3 ~ r3 ~ q7 ~ r7 ~ r4 ~ rotr32(q4 ~ r4) + q[5] = q4 ~ r4 ~ r5 ~ rotr32(q5 ~ r5) + q[6] = q5 ~ r5 ~ r6 ~ rotr32(q6 ~ r6) + q[7] = q6 ~ r6 ~ r7 ~ rotr32(q7 ~ r7) +} + +@(private) +_encrypt :: proc "contextless" (q: ^[8]u64, skey: []u64, num_rounds: int) { + add_round_key(q, skey) + for u in 1 ..< num_rounds { + sub_bytes(q) + shift_rows(q) + mix_columns(q) + add_round_key(q, skey[u << 3:]) + } + sub_bytes(q) + shift_rows(q) + add_round_key(q, skey[num_rounds << 3:]) +} diff --git a/core/crypto/_aes/ct64/ct64_keysched.odin b/core/crypto/_aes/ct64/ct64_keysched.odin new file mode 100644 index 000000000..060a2c03e --- /dev/null +++ b/core/crypto/_aes/ct64/ct64_keysched.odin @@ -0,0 +1,179 @@ +// Copyright (c) 2016 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package aes_ct64 + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:encoding/endian" +import "core:mem" + +@(private, require_results) +sub_word :: proc "contextless" (x: u32) -> u32 { + q := [8]u64{u64(x), 0, 0, 0, 0, 0, 0, 0} + + orthogonalize(&q) + sub_bytes(&q) + orthogonalize(&q) + ret := u32(q[0]) + + mem.zero_explicit(&q[0], size_of(u64)) + + return ret +} + +@(private, require_results) +keysched :: proc(comp_skey: []u64, key: []byte) -> int { + num_rounds, key_len := 0, len(key) + switch key_len { + case _aes.KEY_SIZE_128: + num_rounds = _aes.ROUNDS_128 + case _aes.KEY_SIZE_192: + num_rounds = _aes.ROUNDS_192 + case _aes.KEY_SIZE_256: + num_rounds = _aes.ROUNDS_256 + case: + panic("crypto/aes: invalid AES key size") + } + + skey: [60]u32 = --- + nk, nkf := key_len >> 2, (num_rounds + 1) << 2 + for i in 0 ..< nk { + skey[i] = endian.unchecked_get_u32le(key[i << 2:]) + } + tmp := skey[(key_len >> 2) - 1] + for i, j, k := nk, 0, 0; i < nkf; i += 1 { + if j == 0 { + tmp = (tmp << 24) | (tmp >> 8) + tmp = sub_word(tmp) ~ u32(_aes.RCON[k]) + } else if nk > 6 && j == 4 { + tmp = sub_word(tmp) + } + tmp ~= skey[i - nk] + skey[i] = tmp + if j += 1; j == nk { + j = 0 + k += 1 + } + } + + q: [8]u64 = --- + for i, j := 0, 0; i < nkf; i, j = i + 4, j + 2 { + q[0], q[4] = interleave_in(skey[i:]) + q[1] = q[0] + q[2] = q[0] + q[3] = q[0] + q[5] = q[4] + q[6] = q[4] + q[7] = q[4] + orthogonalize(&q) + comp_skey[j + 0] = + (q[0] & 0x1111111111111111) | + (q[1] & 0x2222222222222222) | + (q[2] & 0x4444444444444444) | + (q[3] & 0x8888888888888888) + comp_skey[j + 1] = + (q[4] & 0x1111111111111111) | + (q[5] & 0x2222222222222222) | + (q[6] & 0x4444444444444444) | + (q[7] & 0x8888888888888888) + } + + mem.zero_explicit(&skey, size_of(skey)) + mem.zero_explicit(&q, size_of(q)) + + return num_rounds +} + +@(private) +skey_expand :: proc "contextless" (skey, comp_skey: []u64, num_rounds: int) { + n := (num_rounds + 1) << 1 + for u, v := 0, 0; u < n; u, v = u + 1, v + 4 { + x0 := comp_skey[u] + x1, x2, x3 := x0, x0, x0 + x0 &= 0x1111111111111111 + x1 &= 0x2222222222222222 + x2 &= 0x4444444444444444 + x3 &= 0x8888888888888888 + x1 >>= 1 + x2 >>= 2 + x3 >>= 3 + skey[v + 0] = (x0 << 4) - x0 + skey[v + 1] = (x1 << 4) - x1 + skey[v + 2] = (x2 << 4) - x2 + skey[v + 3] = (x3 << 4) - x3 + } +} + +orthogonalize_roundkey :: proc "contextless" (qq: []u64, key: []byte) { + if len(qq) < 8 || len(key) != 16 { + intrinsics.trap() + } + + skey: [4]u32 = --- + skey[0] = endian.unchecked_get_u32le(key[0:]) + skey[1] = endian.unchecked_get_u32le(key[4:]) + skey[2] = endian.unchecked_get_u32le(key[8:]) + skey[3] = endian.unchecked_get_u32le(key[12:]) + + q: [8]u64 = --- + q[0], q[4] = interleave_in(skey[:]) + q[1] = q[0] + q[2] = q[0] + q[3] = q[0] + q[5] = q[4] + q[6] = q[4] + q[7] = q[4] + orthogonalize(&q) + + comp_skey: [2]u64 = --- + comp_skey[0] = + (q[0] & 0x1111111111111111) | + (q[1] & 0x2222222222222222) | + (q[2] & 0x4444444444444444) | + (q[3] & 0x8888888888888888) + comp_skey[1] = + (q[4] & 0x1111111111111111) | + (q[5] & 0x2222222222222222) | + (q[6] & 0x4444444444444444) | + (q[7] & 0x8888888888888888) + + for x, u in comp_skey { + x0 := x + x1, x2, x3 := x0, x0, x0 + x0 &= 0x1111111111111111 + x1 &= 0x2222222222222222 + x2 &= 0x4444444444444444 + x3 &= 0x8888888888888888 + x1 >>= 1 + x2 >>= 2 + x3 >>= 3 + qq[u * 4 + 0] = (x0 << 4) - x0 + qq[u * 4 + 1] = (x1 << 4) - x1 + qq[u * 4 + 2] = (x2 << 4) - x2 + qq[u * 4 + 3] = (x3 << 4) - x3 + } + + mem.zero_explicit(&skey, size_of(skey)) + mem.zero_explicit(&q, size_of(q)) + mem.zero_explicit(&comp_skey, size_of(comp_skey)) +} diff --git a/core/crypto/_aes/ct64/helpers.odin b/core/crypto/_aes/ct64/helpers.odin new file mode 100644 index 000000000..169271f6d --- /dev/null +++ b/core/crypto/_aes/ct64/helpers.odin @@ -0,0 +1,75 @@ +package aes_ct64 + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:encoding/endian" + +load_blockx1 :: proc "contextless" (q: ^[8]u64, src: []byte) { + if len(src) != _aes.BLOCK_SIZE { + intrinsics.trap() + } + + w: [4]u32 = --- + w[0] = endian.unchecked_get_u32le(src[0:]) + w[1] = endian.unchecked_get_u32le(src[4:]) + w[2] = endian.unchecked_get_u32le(src[8:]) + w[3] = endian.unchecked_get_u32le(src[12:]) + q[0], q[4] = interleave_in(w[:]) + orthogonalize(q) +} + +store_blockx1 :: proc "contextless" (dst: []byte, q: ^[8]u64) { + if len(dst) != _aes.BLOCK_SIZE { + intrinsics.trap() + } + + orthogonalize(q) + w0, w1, w2, w3 := interleave_out(q[0], q[4]) + endian.unchecked_put_u32le(dst[0:], w0) + endian.unchecked_put_u32le(dst[4:], w1) + endian.unchecked_put_u32le(dst[8:], w2) + endian.unchecked_put_u32le(dst[12:], w3) +} + +load_blocks :: proc "contextless" (q: ^[8]u64, src: [][]byte) { + if n := len(src); n > STRIDE || n == 0 { + intrinsics.trap() + } + + w: [4]u32 = --- + for s, i in src { + if len(s) != _aes.BLOCK_SIZE { + intrinsics.trap() + } + + w[0] = endian.unchecked_get_u32le(s[0:]) + w[1] = endian.unchecked_get_u32le(s[4:]) + w[2] = endian.unchecked_get_u32le(s[8:]) + w[3] = endian.unchecked_get_u32le(s[12:]) + q[i], q[i + 4] = interleave_in(w[:]) + } + orthogonalize(q) +} + +store_blocks :: proc "contextless" (dst: [][]byte, q: ^[8]u64) { + if n := len(dst); n > STRIDE || n == 0 { + intrinsics.trap() + } + + orthogonalize(q) + for d, i in dst { + // Allow storing [0,4] blocks. + if d == nil { + break + } + if len(d) != _aes.BLOCK_SIZE { + intrinsics.trap() + } + + w0, w1, w2, w3 := interleave_out(q[i], q[i + 4]) + endian.unchecked_put_u32le(d[0:], w0) + endian.unchecked_put_u32le(d[4:], w1) + endian.unchecked_put_u32le(d[8:], w2) + endian.unchecked_put_u32le(d[12:], w3) + } +} From 1ade62b630f565d975049aa75935c64db175ef61 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Mon, 27 May 2024 21:25:26 +0900 Subject: [PATCH 031/270] core/crypto/_aes/ct64: Add GHASH --- core/crypto/_aes/aes.odin | 5 ++ core/crypto/_aes/ct64/ghash.odin | 136 +++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 core/crypto/_aes/ct64/ghash.odin diff --git a/core/crypto/_aes/aes.odin b/core/crypto/_aes/aes.odin index 74906fcd4..6b290feb6 100644 --- a/core/crypto/_aes/aes.odin +++ b/core/crypto/_aes/aes.odin @@ -18,5 +18,10 @@ ROUNDS_192 :: 12 // ROUNDS_256 is the number of rounds for AES-256. ROUNDS_256 :: 14 +// GHASH_KEY_SIZE is the GHASH key size in bytes. +GHASH_KEY_SIZE :: 16 +// GHASH_BLOCK_SIZE is the GHASH block size in bytes. +GHASH_BLOCK_SIZE :: 16 + // RCON is the AES keyschedule round constants. RCON := [10]byte{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36} diff --git a/core/crypto/_aes/ct64/ghash.odin b/core/crypto/_aes/ct64/ghash.odin new file mode 100644 index 000000000..21ac2ca97 --- /dev/null +++ b/core/crypto/_aes/ct64/ghash.odin @@ -0,0 +1,136 @@ +// Copyright (c) 2016 Thomas Pornin +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE AUTHORS “AS IS” AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +// GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package aes_ct64 + +import "base:intrinsics" +import "core:crypto/_aes" +import "core:encoding/endian" + +@(private = "file") +bmul64 :: proc "contextless" (x, y: u64) -> u64 { + x0 := x & 0x1111111111111111 + x1 := x & 0x2222222222222222 + x2 := x & 0x4444444444444444 + x3 := x & 0x8888888888888888 + y0 := y & 0x1111111111111111 + y1 := y & 0x2222222222222222 + y2 := y & 0x4444444444444444 + y3 := y & 0x8888888888888888 + z0 := (x0 * y0) ~ (x1 * y3) ~ (x2 * y2) ~ (x3 * y1) + z1 := (x0 * y1) ~ (x1 * y0) ~ (x2 * y3) ~ (x3 * y2) + z2 := (x0 * y2) ~ (x1 * y1) ~ (x2 * y0) ~ (x3 * y3) + z3 := (x0 * y3) ~ (x1 * y2) ~ (x2 * y1) ~ (x3 * y0) + z0 &= 0x1111111111111111 + z1 &= 0x2222222222222222 + z2 &= 0x4444444444444444 + z3 &= 0x8888888888888888 + return z0 | z1 | z2 | z3 +} + +@(private = "file") +rev64 :: proc "contextless" (x: u64) -> u64 { + x := x + x = ((x & 0x5555555555555555) << 1) | ((x >> 1) & 0x5555555555555555) + x = ((x & 0x3333333333333333) << 2) | ((x >> 2) & 0x3333333333333333) + x = ((x & 0x0F0F0F0F0F0F0F0F) << 4) | ((x >> 4) & 0x0F0F0F0F0F0F0F0F) + x = ((x & 0x00FF00FF00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF00FF00FF) + x = ((x & 0x0000FFFF0000FFFF) << 16) | ((x >> 16) & 0x0000FFFF0000FFFF) + return (x << 32) | (x >> 32) +} + +// ghash calculates the GHASH of data, with the key `key`, and input `dst` +// and `data`, and stores the resulting digest in `dst`. +// +// Note: `dst` is both an input and an output, to support easy implementation +// of GCM. +ghash :: proc "contextless" (dst, key, data: []byte) { + if len(dst) != _aes.GHASH_BLOCK_SIZE || len(key) != _aes.GHASH_BLOCK_SIZE { + intrinsics.trap() + } + + buf := data + l := len(buf) + + y1 := endian.unchecked_get_u64be(dst[0:]) + y0 := endian.unchecked_get_u64be(dst[8:]) + h1 := endian.unchecked_get_u64be(key[0:]) + h0 := endian.unchecked_get_u64be(key[8:]) + h0r := rev64(h0) + h1r := rev64(h1) + h2 := h0 ~ h1 + h2r := h0r ~ h1r + + src: []byte + for l > 0 { + if l >= _aes.GHASH_BLOCK_SIZE { + src = buf + buf = buf[_aes.GHASH_BLOCK_SIZE:] + l -= _aes.GHASH_BLOCK_SIZE + } else { + tmp: [_aes.GHASH_BLOCK_SIZE]byte + copy(tmp[:], buf) + src = tmp[:] + l = 0 + } + y1 ~= endian.unchecked_get_u64be(src) + y0 ~= endian.unchecked_get_u64be(src[8:]) + + y0r := rev64(y0) + y1r := rev64(y1) + y2 := y0 ~ y1 + y2r := y0r ~ y1r + + z0 := bmul64(y0, h0) + z1 := bmul64(y1, h1) + z2 := bmul64(y2, h2) + z0h := bmul64(y0r, h0r) + z1h := bmul64(y1r, h1r) + z2h := bmul64(y2r, h2r) + z2 ~= z0 ~ z1 + z2h ~= z0h ~ z1h + z0h = rev64(z0h) >> 1 + z1h = rev64(z1h) >> 1 + z2h = rev64(z2h) >> 1 + + v0 := z0 + v1 := z0h ~ z2 + v2 := z1 ~ z2h + v3 := z1h + + v3 = (v3 << 1) | (v2 >> 63) + v2 = (v2 << 1) | (v1 >> 63) + v1 = (v1 << 1) | (v0 >> 63) + v0 = (v0 << 1) + + v2 ~= v0 ~ (v0 >> 1) ~ (v0 >> 2) ~ (v0 >> 7) + v1 ~= (v0 << 63) ~ (v0 << 62) ~ (v0 << 57) + v3 ~= v1 ~ (v1 >> 1) ~ (v1 >> 2) ~ (v1 >> 7) + v2 ~= (v1 << 63) ~ (v1 << 62) ~ (v1 << 57) + + y0 = v2 + y1 = v3 + } + + endian.unchecked_put_u64be(dst[0:], y1) + endian.unchecked_put_u64be(dst[8:], y0) +} From c751e4b2ebe8a2a32aee09c67d7d27872fc2d5e3 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Sat, 25 May 2024 20:24:39 +0900 Subject: [PATCH 032/270] core/crypto/aes: Add AES implementation --- core/crypto/_aes/aes.odin | 3 +- core/crypto/aes/aes.odin | 22 + core/crypto/aes/aes_ctr.odin | 199 ++++++++ core/crypto/aes/aes_ecb.odin | 57 +++ core/crypto/aes/aes_gcm.odin | 253 ++++++++++ core/crypto/aes/aes_impl.odin | 41 ++ core/crypto/aes/aes_impl_hw_gen.odin | 43 ++ examples/all/all_main.odin | 2 + tests/core/crypto/test_core_crypto.odin | 1 + tests/core/crypto/test_core_crypto_aes.odin | 462 +++++++++++++++++++ tests/core/crypto/test_crypto_benchmark.odin | 60 +++ 11 files changed, 1142 insertions(+), 1 deletion(-) create mode 100644 core/crypto/aes/aes.odin create mode 100644 core/crypto/aes/aes_ctr.odin create mode 100644 core/crypto/aes/aes_ecb.odin create mode 100644 core/crypto/aes/aes_gcm.odin create mode 100644 core/crypto/aes/aes_impl.odin create mode 100644 core/crypto/aes/aes_impl_hw_gen.odin create mode 100644 tests/core/crypto/test_core_crypto_aes.odin diff --git a/core/crypto/_aes/aes.odin b/core/crypto/_aes/aes.odin index 6b290feb6..4f52485d2 100644 --- a/core/crypto/_aes/aes.odin +++ b/core/crypto/_aes/aes.odin @@ -10,7 +10,6 @@ KEY_SIZE_256 :: 32 // BLOCK_SIZE is the AES block size in bytes. BLOCK_SIZE :: 16 - // ROUNDS_128 is the number of rounds for AES-128. ROUNDS_128 :: 10 // ROUNDS_192 is the number of rounds for AES-192. @@ -22,6 +21,8 @@ ROUNDS_256 :: 14 GHASH_KEY_SIZE :: 16 // GHASH_BLOCK_SIZE is the GHASH block size in bytes. GHASH_BLOCK_SIZE :: 16 +// GHASH_TAG_SIZE is the GHASH tag size in bytes. +GHASH_TAG_SIZE :: 16 // RCON is the AES keyschedule round constants. RCON := [10]byte{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36} diff --git a/core/crypto/aes/aes.odin b/core/crypto/aes/aes.odin new file mode 100644 index 000000000..e895c5fe0 --- /dev/null +++ b/core/crypto/aes/aes.odin @@ -0,0 +1,22 @@ +/* +package aes implements the AES block cipher and some common modes. + +See: +- https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf +- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf +- https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf +*/ + +package aes + +import "core:crypto/_aes" + +// KEY_SIZE_128 is the AES-128 key size in bytes. +KEY_SIZE_128 :: _aes.KEY_SIZE_128 +// KEY_SIZE_192 is the AES-192 key size in bytes. +KEY_SIZE_192 :: _aes.KEY_SIZE_192 +// KEY_SIZE_256 is the AES-256 key size in bytes. +KEY_SIZE_256 :: _aes.KEY_SIZE_256 + +// BLOCK_SIZE is the AES block size in bytes. +BLOCK_SIZE :: _aes.BLOCK_SIZE diff --git a/core/crypto/aes/aes_ctr.odin b/core/crypto/aes/aes_ctr.odin new file mode 100644 index 000000000..1821a7bdf --- /dev/null +++ b/core/crypto/aes/aes_ctr.odin @@ -0,0 +1,199 @@ +package aes + +import "core:crypto/_aes/ct64" +import "core:encoding/endian" +import "core:math/bits" +import "core:mem" + +// CTR_IV_SIZE is the size of the CTR mode IV in bytes. +CTR_IV_SIZE :: 16 + +// Context_CTR is a keyed AES-CTR instance. +Context_CTR :: struct { + _impl: Context_Impl, + _buffer: [BLOCK_SIZE]byte, + _off: int, + _ctr_hi: u64, + _ctr_lo: u64, + _is_initialized: bool, +} + +// init_ctr initializes a Context_CTR with the provided key and IV. +init_ctr :: proc(ctx: ^Context_CTR, key, iv: []byte, impl := Implementation.Hardware) { + if len(iv) != CTR_IV_SIZE { + panic("crypto/aes: invalid CTR IV size") + } + + init_impl(&ctx._impl, key, impl) + ctx._off = BLOCK_SIZE + ctx._ctr_hi = endian.unchecked_get_u64be(iv[0:]) + ctx._ctr_lo = endian.unchecked_get_u64be(iv[8:]) + ctx._is_initialized = true +} + +// xor_bytes_ctr XORs each byte in src with bytes taken from the AES-CTR +// keystream, and writes the resulting output to dst. dst and src MUST +// alias exactly or not at all. +xor_bytes_ctr :: proc(ctx: ^Context_CTR, dst, src: []byte) { + assert(ctx._is_initialized) + + // TODO: Enforcing that dst and src alias exactly or not at all + // is a good idea, though odd aliasing should be extremely uncommon. + + src, dst := src, dst + if dst_len := len(dst); dst_len < len(src) { + src = src[:dst_len] + } + + for remaining := len(src); remaining > 0; { + // Process multiple blocks at once + if ctx._off == BLOCK_SIZE { + if nr_blocks := remaining / BLOCK_SIZE; nr_blocks > 0 { + direct_bytes := nr_blocks * BLOCK_SIZE + ctr_blocks(ctx, dst, src, nr_blocks) + remaining -= direct_bytes + if remaining == 0 { + return + } + dst = dst[direct_bytes:] + src = src[direct_bytes:] + } + + // If there is a partial block, generate and buffer 1 block + // worth of keystream. + ctr_blocks(ctx, ctx._buffer[:], nil, 1) + ctx._off = 0 + } + + // Process partial blocks from the buffered keystream. + to_xor := min(BLOCK_SIZE - ctx._off, remaining) + buffered_keystream := ctx._buffer[ctx._off:] + for i := 0; i < to_xor; i = i + 1 { + dst[i] = buffered_keystream[i] ~ src[i] + } + ctx._off += to_xor + dst = dst[to_xor:] + src = src[to_xor:] + remaining -= to_xor + } +} + +// keystream_bytes_ctr fills dst with the raw AES-CTR keystream output. +keystream_bytes_ctr :: proc(ctx: ^Context_CTR, dst: []byte) { + assert(ctx._is_initialized) + + dst := dst + for remaining := len(dst); remaining > 0; { + // Process multiple blocks at once + if ctx._off == BLOCK_SIZE { + if nr_blocks := remaining / BLOCK_SIZE; nr_blocks > 0 { + direct_bytes := nr_blocks * BLOCK_SIZE + ctr_blocks(ctx, dst, nil, nr_blocks) + remaining -= direct_bytes + if remaining == 0 { + return + } + dst = dst[direct_bytes:] + } + + // If there is a partial block, generate and buffer 1 block + // worth of keystream. + ctr_blocks(ctx, ctx._buffer[:], nil, 1) + ctx._off = 0 + } + + // Process partial blocks from the buffered keystream. + to_copy := min(BLOCK_SIZE - ctx._off, remaining) + buffered_keystream := ctx._buffer[ctx._off:] + copy(dst[:to_copy], buffered_keystream[:to_copy]) + ctx._off += to_copy + dst = dst[to_copy:] + remaining -= to_copy + } +} + +// reset_ctr sanitizes the Context_CTR. The Context_CTR must be +// re-initialized to be used again. +reset_ctr :: proc "contextless" (ctx: ^Context_CTR) { + reset_impl(&ctx._impl) + ctx._off = 0 + ctx._ctr_hi = 0 + ctx._ctr_lo = 0 + mem.zero_explicit(&ctx._buffer, size_of(ctx._buffer)) + ctx._is_initialized = false +} + +@(private) +ctr_blocks :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) { + // Use the optimized hardware implementation if available. + if _, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { + ctr_blocks_hw(ctx, dst, src, nr_blocks) + return + } + + // Portable implementation. + ct64_inc_ctr := #force_inline proc "contextless" (dst: []byte, hi, lo: u64) -> (u64, u64) { + endian.unchecked_put_u64be(dst[0:], hi) + endian.unchecked_put_u64be(dst[8:], lo) + + hi, lo := hi, lo + carry: u64 + lo, carry = bits.add_u64(lo, 1, 0) + hi, _ = bits.add_u64(hi, 0, carry) + return hi, lo + } + + impl := &ctx._impl.(ct64.Context) + src, dst := src, dst + nr_blocks := nr_blocks + ctr_hi, ctr_lo := ctx._ctr_hi, ctx._ctr_lo + + tmp: [ct64.STRIDE][BLOCK_SIZE]byte = --- + ctrs: [ct64.STRIDE][]byte = --- + for i in 0 ..< ct64.STRIDE { + ctrs[i] = tmp[i][:] + } + for nr_blocks > 0 { + n := min(ct64.STRIDE, nr_blocks) + blocks := ctrs[:n] + + for i in 0 ..< n { + ctr_hi, ctr_lo = ct64_inc_ctr(blocks[i], ctr_hi, ctr_lo) + } + ct64.encrypt_blocks(impl, blocks, blocks) + + xor_blocks(dst, src, blocks) + + if src != nil { + src = src[n * BLOCK_SIZE:] + } + dst = dst[n * BLOCK_SIZE:] + nr_blocks -= n + } + + // Write back the counter. + ctx._ctr_hi, ctx._ctr_lo = ctr_hi, ctr_lo + + mem.zero_explicit(&tmp, size_of(tmp)) +} + +@(private) +xor_blocks :: #force_inline proc "contextless" (dst, src: []byte, blocks: [][]byte) { + // Note: This would be faster `core:simd` was used, however if + // performance of this implementation matters to where that + // optimization would be worth it, use chacha20poly1305, or a + // CPU that isn't e-waste. + if src != nil { + #no_bounds_check { + for i in 0 ..< len(blocks) { + off := i * BLOCK_SIZE + for j in 0 ..< BLOCK_SIZE { + blocks[i][j] ~= src[off + j] + } + } + } + } + for i in 0 ..< len(blocks) { + copy(dst[i * BLOCK_SIZE:], blocks[i]) + } +} diff --git a/core/crypto/aes/aes_ecb.odin b/core/crypto/aes/aes_ecb.odin new file mode 100644 index 000000000..498429e29 --- /dev/null +++ b/core/crypto/aes/aes_ecb.odin @@ -0,0 +1,57 @@ +package aes + +import "core:crypto/_aes/ct64" + +// Context_ECB is a keyed AES-ECB instance. +// +// WARNING: Using ECB mode is strongly discouraged unless it is being +// used to implement higher level constructs. +Context_ECB :: struct { + _impl: Context_Impl, + _is_initialized: bool, +} + +// init_ecb initializes a Context_ECB with the provided key. +init_ecb :: proc(ctx: ^Context_ECB, key: []byte, impl := Implementation.Hardware) { + init_impl(&ctx._impl, key, impl) + ctx._is_initialized = true +} + +// encrypt_ecb encrypts the BLOCK_SIZE buffer src, and writes the result to dst. +encrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) { + assert(ctx._is_initialized) + + if len(dst) != BLOCK_SIZE || len(src) != BLOCK_SIZE { + panic("crypto/aes: invalid buffer size(s)") + } + + switch &impl in ctx._impl { + case ct64.Context: + ct64.encrypt_block(&impl, dst, src) + case Context_Impl_Hardware: + encrypt_block_hw(&impl, dst, src) + } +} + +// decrypt_ecb decrypts the BLOCK_SIZE buffer src, and writes the result to dst. +decrypt_ecb :: proc(ctx: ^Context_ECB, dst, src: []byte) { + assert(ctx._is_initialized) + + if len(dst) != BLOCK_SIZE || len(src) != BLOCK_SIZE { + panic("crypto/aes: invalid buffer size(s)") + } + + switch &impl in ctx._impl { + case ct64.Context: + ct64.decrypt_block(&impl, dst, src) + case Context_Impl_Hardware: + decrypt_block_hw(&impl, dst, src) + } +} + +// reset_ecb sanitizes the Context_ECB. The Context_ECB must be +// re-initialized to be used again. +reset_ecb :: proc "contextless" (ctx: ^Context_ECB) { + reset_impl(&ctx._impl) + ctx._is_initialized = false +} diff --git a/core/crypto/aes/aes_gcm.odin b/core/crypto/aes/aes_gcm.odin new file mode 100644 index 000000000..66ef48db2 --- /dev/null +++ b/core/crypto/aes/aes_gcm.odin @@ -0,0 +1,253 @@ +package aes + +import "core:crypto" +import "core:crypto/_aes" +import "core:crypto/_aes/ct64" +import "core:encoding/endian" +import "core:mem" + +// GCM_NONCE_SIZE is the size of the GCM nonce in bytes. +GCM_NONCE_SIZE :: 12 +// GCM_TAG_SIZE is the size of a GCM tag in bytes. +GCM_TAG_SIZE :: _aes.GHASH_TAG_SIZE + +@(private) +GCM_A_MAX :: max(u64) / 8 // 2^64 - 1 bits -> bytes +@(private) +GCM_P_MAX :: 0xfffffffe0 // 2^39 - 256 bits -> bytes + +// Context_GCM is a keyed AES-GCM instance. +Context_GCM :: struct { + _impl: Context_Impl, + _is_initialized: bool, +} + +// init_gcm initializes a Context_GCM with the provided key. +init_gcm :: proc(ctx: ^Context_GCM, key: []byte, impl := Implementation.Hardware) { + init_impl(&ctx._impl, key, impl) + ctx._is_initialized = true +} + +// seal_gcm encrypts the plaintext and authenticates the aad and ciphertext, +// with the provided Context_GCM and nonce, stores the output in dst and tag. +// +// dst and plaintext MUST alias exactly or not at all. +seal_gcm :: proc(ctx: ^Context_GCM, dst, tag, nonce, aad, plaintext: []byte) { + assert(ctx._is_initialized) + + gcm_validate_common_slice_sizes(tag, nonce, aad, plaintext) + if len(dst) != len(plaintext) { + panic("crypto/aes: invalid destination ciphertext size") + } + + if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { + gcm_seal_hw(&impl, dst, tag, nonce, aad, plaintext) + return + } + + h: [_aes.GHASH_KEY_SIZE]byte + j0: [_aes.GHASH_BLOCK_SIZE]byte + s: [_aes.GHASH_TAG_SIZE]byte + init_ghash_ct64(ctx, &h, &j0, nonce) + + // Note: Our GHASH implementation handles appending padding. + ct64.ghash(s[:], h[:], aad) + gctr_ct64(ctx, dst, &s, plaintext, &h, nonce, true) + final_ghash_ct64(&s, &h, &j0, len(aad), len(plaintext)) + copy(tag, s[:]) + + mem.zero_explicit(&h, len(h)) + mem.zero_explicit(&j0, len(j0)) +} + +// open_gcm authenticates the aad and ciphertext, and decrypts the ciphertext, +// with the provided Context_GCM, nonce, and tag, and stores the output in dst, +// returning true iff the authentication was successful. If authentication +// fails, the destination buffer will be zeroed. +// +// dst and plaintext MUST alias exactly or not at all. +open_gcm :: proc(ctx: ^Context_GCM, dst, nonce, aad, ciphertext, tag: []byte) -> bool { + assert(ctx._is_initialized) + + gcm_validate_common_slice_sizes(tag, nonce, aad, ciphertext) + if len(dst) != len(ciphertext) { + panic("crypto/aes: invalid destination plaintext size") + } + + if impl, is_hw := ctx._impl.(Context_Impl_Hardware); is_hw { + return gcm_open_hw(&impl, dst, nonce, aad, ciphertext, tag) + } + + h: [_aes.GHASH_KEY_SIZE]byte + j0: [_aes.GHASH_BLOCK_SIZE]byte + s: [_aes.GHASH_TAG_SIZE]byte + init_ghash_ct64(ctx, &h, &j0, nonce) + + ct64.ghash(s[:], h[:], aad) + gctr_ct64(ctx, dst, &s, ciphertext, &h, nonce, false) + final_ghash_ct64(&s, &h, &j0, len(aad), len(ciphertext)) + + ok := crypto.compare_constant_time(s[:], tag) == 1 + if !ok { + mem.zero_explicit(raw_data(dst), len(dst)) + } + + mem.zero_explicit(&h, len(h)) + mem.zero_explicit(&j0, len(j0)) + mem.zero_explicit(&s, len(s)) + + return ok +} + +// reset_ctr sanitizes the Context_GCM. The Context_GCM must be +// re-initialized to be used again. +reset_gcm :: proc "contextless" (ctx: ^Context_GCM) { + reset_impl(&ctx._impl) + ctx._is_initialized = false +} + +@(private) +gcm_validate_common_slice_sizes :: proc(tag, nonce, aad, text: []byte) { + if len(tag) != GCM_TAG_SIZE { + panic("crypto/aes: invalid GCM tag size") + } + + // The specification supports nonces in the range [1, 2^64) bits + // however per NIST SP 800-38D 5.2.1.1: + // + // > For IVs, it is recommended that implementations restrict support + // > to the length of 96 bits, to promote interoperability, efficiency, + // > and simplicity of design. + if len(nonce) != GCM_NONCE_SIZE { + panic("crypto/aes: invalid GCM nonce size") + } + + if aad_len := u64(len(aad)); aad_len > GCM_A_MAX { + panic("crypto/aes: oversized GCM aad") + } + if text_len := u64(len(text)); text_len > GCM_P_MAX { + panic("crypto/aes: oversized GCM src data") + } +} + +@(private = "file") +init_ghash_ct64 :: proc( + ctx: ^Context_GCM, + h: ^[_aes.GHASH_KEY_SIZE]byte, + j0: ^[_aes.GHASH_BLOCK_SIZE]byte, + nonce: []byte, +) { + impl := &ctx._impl.(ct64.Context) + + // 1. Let H = CIPH(k, 0^128) + ct64.encrypt_block(impl, h[:], h[:]) + + // ECB encrypt j0, so that we can just XOR with the tag. In theory + // this could be processed along with the final GCTR block, to + // potentially save a call to AES-ECB, but... just use AES-NI. + copy(j0[:], nonce) + j0[_aes.GHASH_BLOCK_SIZE - 1] = 1 + ct64.encrypt_block(impl, j0[:], j0[:]) +} + +@(private = "file") +final_ghash_ct64 :: proc( + s: ^[_aes.GHASH_BLOCK_SIZE]byte, + h: ^[_aes.GHASH_KEY_SIZE]byte, + j0: ^[_aes.GHASH_BLOCK_SIZE]byte, + a_len: int, + t_len: int, +) { + blk: [_aes.GHASH_BLOCK_SIZE]byte + endian.unchecked_put_u64be(blk[0:], u64(a_len) * 8) + endian.unchecked_put_u64be(blk[8:], u64(t_len) * 8) + + ct64.ghash(s[:], h[:], blk[:]) + for i in 0 ..< len(s) { + s[i] ~= j0[i] + } +} + +@(private = "file") +gctr_ct64 :: proc( + ctx: ^Context_GCM, + dst: []byte, + s: ^[_aes.GHASH_BLOCK_SIZE]byte, + src: []byte, + h: ^[_aes.GHASH_KEY_SIZE]byte, + nonce: []byte, + is_seal: bool, +) { + ct64_inc_ctr32 := #force_inline proc "contextless" (dst: []byte, ctr: u32) -> u32 { + endian.unchecked_put_u32be(dst[12:], ctr) + return ctr + 1 + } + + // 2. Define a block J_0 as follows: + // if len(IV) = 96, then let J0 = IV || 0^31 || 1 + // + // Note: We only support 96 bit IVs. + tmp, tmp2: [ct64.STRIDE][BLOCK_SIZE]byte = ---, --- + ctrs, blks: [ct64.STRIDE][]byte = ---, --- + ctr: u32 = 2 + for i in 0 ..< ct64.STRIDE { + // Setup scratch space for the keystream. + blks[i] = tmp2[i][:] + + // Pre-copy the IV to all the counter blocks. + ctrs[i] = tmp[i][:] + copy(ctrs[i], nonce) + } + + // We stitch the GCTR and GHASH operations together, so that only + // one pass over the ciphertext is required. + + impl := &ctx._impl.(ct64.Context) + src, dst := src, dst + + nr_blocks := len(src) / BLOCK_SIZE + for nr_blocks > 0 { + n := min(ct64.STRIDE, nr_blocks) + l := n * BLOCK_SIZE + + if !is_seal { + ct64.ghash(s[:], h[:], src[:l]) + } + + // The keystream is written to a separate buffer, as we will + // reuse the first 96-bits of each counter. + for i in 0 ..< n { + ctr = ct64_inc_ctr32(ctrs[i], ctr) + } + ct64.encrypt_blocks(impl, blks[:n], ctrs[:n]) + + xor_blocks(dst, src, blks[:n]) + + if is_seal { + ct64.ghash(s[:], h[:], dst[:l]) + } + + src = src[l:] + dst = dst[l:] + nr_blocks -= n + } + if l := len(src); l > 0 { + if !is_seal { + ct64.ghash(s[:], h[:], src[:l]) + } + + ct64_inc_ctr32(ctrs[0], ctr) + ct64.encrypt_block(impl, ctrs[0], ctrs[0]) + + for i in 0 ..< l { + dst[i] = src[i] ~ ctrs[0][i] + } + + if is_seal { + ct64.ghash(s[:], h[:], dst[:l]) + } + } + + mem.zero_explicit(&tmp, size_of(tmp)) + mem.zero_explicit(&tmp2, size_of(tmp2)) +} diff --git a/core/crypto/aes/aes_impl.odin b/core/crypto/aes/aes_impl.odin new file mode 100644 index 000000000..03747f1fb --- /dev/null +++ b/core/crypto/aes/aes_impl.odin @@ -0,0 +1,41 @@ +package aes + +import "core:crypto/_aes/ct64" +import "core:mem" +import "core:reflect" + +@(private) +Context_Impl :: union { + ct64.Context, + Context_Impl_Hardware, +} + +// Implementation is an AES implementation. Most callers will not need +// to use this as the package will automatically select the most performant +// implementation available (See `is_hardware_accelerated()`). +Implementation :: enum { + Portable, + Hardware, +} + +@(private) +init_impl :: proc(ctx: ^Context_Impl, key: []byte, impl: Implementation) { + impl := impl + if !is_hardware_accelerated() { + impl = .Portable + } + + switch impl { + case .Portable: + reflect.set_union_variant_typeid(ctx^, typeid_of(ct64.Context)) + ct64.init(&ctx.(ct64.Context), key) + case .Hardware: + reflect.set_union_variant_typeid(ctx^, typeid_of(Context_Impl_Hardware)) + init_impl_hw(&ctx.(Context_Impl_Hardware), key) + } +} + +@(private) +reset_impl :: proc "contextless" (ctx: ^Context_Impl) { + mem.zero_explicit(ctx, size_of(Context_Impl)) +} diff --git a/core/crypto/aes/aes_impl_hw_gen.odin b/core/crypto/aes/aes_impl_hw_gen.odin new file mode 100644 index 000000000..94815f61c --- /dev/null +++ b/core/crypto/aes/aes_impl_hw_gen.odin @@ -0,0 +1,43 @@ +package aes + +@(private = "file") +ERR_HW_NOT_SUPPORTED :: "crypto/aes: hardware implementation unsupported" + +// is_hardware_accelerated returns true iff hardware accelerated AES +// is supported. +is_hardware_accelerated :: proc "contextless" () -> bool { + return false +} + +@(private) +Context_Impl_Hardware :: struct {} + +@(private) +init_impl_hw :: proc(ctx: ^Context_Impl_Hardware, key: []byte) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +encrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +decrypt_block_hw :: proc(ctx: ^Context_Impl_Hardware, dst, src: []byte) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +ctr_blocks_hw :: proc(ctx: ^Context_CTR, dst, src: []byte, nr_blocks: int) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +gcm_seal_hw :: proc(ctx: ^Context_Impl_Hardware, dst, tag, nonce, aad, plaintext: []byte) { + panic(ERR_HW_NOT_SUPPORTED) +} + +@(private) +gcm_open_hw :: proc(ctx: ^Context_Impl_Hardware, dst, nonce, aad, ciphertext, tag: []byte) -> bool { + panic(ERR_HW_NOT_SUPPORTED) +} diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 1077df1ae..6c3972987 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -25,6 +25,7 @@ import rbtree "core:container/rbtree" import topological_sort "core:container/topological_sort" import crypto "core:crypto" +import aes "core:crypto/aes" import blake2b "core:crypto/blake2b" import blake2s "core:crypto/blake2s" import chacha20 "core:crypto/chacha20" @@ -150,6 +151,7 @@ _ :: rbtree _ :: topological_sort _ :: crypto _ :: crypto_hash +_ :: aes _ :: blake2b _ :: blake2s _ :: chacha20 diff --git a/tests/core/crypto/test_core_crypto.odin b/tests/core/crypto/test_core_crypto.odin index 95db3f292..1f7bcece3 100644 --- a/tests/core/crypto/test_core_crypto.odin +++ b/tests/core/crypto/test_core_crypto.odin @@ -33,6 +33,7 @@ main :: proc() { test_kdf(&t) // After hash/mac tests because those should pass first. test_ecc25519(&t) + test_aes(&t) test_chacha20(&t) test_chacha20poly1305(&t) test_sha3_variants(&t) diff --git a/tests/core/crypto/test_core_crypto_aes.odin b/tests/core/crypto/test_core_crypto_aes.odin new file mode 100644 index 000000000..f33292a53 --- /dev/null +++ b/tests/core/crypto/test_core_crypto_aes.odin @@ -0,0 +1,462 @@ +package test_core_crypto + +import "base:runtime" +import "core:encoding/hex" +import "core:fmt" +import "core:testing" + +import "core:crypto/aes" +import "core:crypto/sha2" + +import tc "tests:common" + +@(test) +test_aes :: proc(t: ^testing.T) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + tc.log(t, "Testing AES") + + impls := make([dynamic]aes.Implementation, 0, 2) + append(&impls, aes.Implementation.Portable) + if aes.is_hardware_accelerated() { + append(&impls, aes.Implementation.Hardware) + } + + for impl in impls { + test_aes_ecb(t, impl) + test_aes_ctr(t, impl) + test_aes_gcm(t, impl) + } +} + +@(test) +test_aes_ecb :: proc(t: ^testing.T, impl: aes.Implementation) { + tc.log(t, fmt.tprintf("Testing AES-ECB/%v", impl)) + + test_vectors := []struct { + key: string, + plaintext: string, + ciphertext: string, + } { + // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + { + "2b7e151628aed2a6abf7158809cf4f3c", + "6bc1bee22e409f96e93d7e117393172a", + "3ad77bb40d7a3660a89ecaf32466ef97", + }, + { + "2b7e151628aed2a6abf7158809cf4f3c", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "f5d3d58503b9699de785895a96fdbaaf", + }, + { + "2b7e151628aed2a6abf7158809cf4f3c", + "30c81c46a35ce411e5fbc1191a0a52ef", + "43b1cd7f598ece23881b00e3ed030688", + }, + { + "2b7e151628aed2a6abf7158809cf4f3c", + "f69f2445df4f9b17ad2b417be66c3710", + "7b0c785e27e8ad3f8223207104725dd4", + }, + { + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "6bc1bee22e409f96e93d7e117393172a", + "bd334f1d6e45f25ff712a214571fa5cc", + }, + { + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "974104846d0ad3ad7734ecb3ecee4eef", + }, + { + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "30c81c46a35ce411e5fbc1191a0a52ef", + "ef7afd2270e2e60adce0ba2face6444e", + }, + { + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "f69f2445df4f9b17ad2b417be66c3710", + "9a4b41ba738d6c72fb16691603c18e0e", + }, + { + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "6bc1bee22e409f96e93d7e117393172a", + "f3eed1bdb5d2a03c064b5a7e3db181f8", + }, + { + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "ae2d8a571e03ac9c9eb76fac45af8e51", + "591ccb10d410ed26dc5ba74a31362870", + }, + { + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "30c81c46a35ce411e5fbc1191a0a52ef", + "b6ed21b99ca6f4f9f153e7b1beafed1d", + }, + { + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "f69f2445df4f9b17ad2b417be66c3710", + "23304b7a39f9f3ff067d8d8f9e24ecc7", + }, + } + for v, _ in test_vectors { + key, _ := hex.decode(transmute([]byte)(v.key), context.temp_allocator) + plaintext, _ := hex.decode(transmute([]byte)(v.plaintext), context.temp_allocator) + ciphertext, _ := hex.decode(transmute([]byte)(v.ciphertext), context.temp_allocator) + + ctx: aes.Context_ECB + dst: [aes.BLOCK_SIZE]byte + aes.init_ecb(&ctx, key, impl) + + aes.encrypt_ecb(&ctx, dst[:], plaintext) + dst_str := string(hex.encode(dst[:], context.temp_allocator)) + tc.expect( + t, + dst_str == v.ciphertext, + fmt.tprintf( + "AES-ECB/%v: Expected: %s for encrypt(%s, %s), but got %s instead", + impl, + v.ciphertext, + v.key, + v.plaintext, + dst_str, + ), + ) + + aes.decrypt_ecb(&ctx, dst[:], ciphertext) + dst_str = string(hex.encode(dst[:], context.temp_allocator)) + tc.expect( + t, + dst_str == v.plaintext, + fmt.tprintf( + "AES-ECB/%v: Expected: %s for decrypt(%s, %s), but got %s instead", + impl, + v.plaintext, + v.key, + v.ciphertext, + dst_str, + ), + ) + } +} + +@(test) +test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { + tc.log(t, fmt.tprintf("Testing AES-CTR/%v", impl)) + + test_vectors := []struct { + key: string, + iv: string, + plaintext: string, + ciphertext: string, + } { + // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf + { + "2b7e151628aed2a6abf7158809cf4f3c", + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "874d6191b620e3261bef6864990db6ce9806f66b7970fdff8617187bb9fffdff5ae4df3edbd5d35e5b4f09020db03eab1e031dda2fbe03d1792170a0f3009cee", + }, + { + "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "1abc932417521ca24f2b0459fe7e6e0b090339ec0aa6faefd5ccc2c6f4ce8e941e36b26bd1ebc670d1bd1d665620abf74f78a7f6d29809585a97daec58c6b050", + }, + { + "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", + "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "601ec313775789a5b7a7f504bbf3d228f443e3ca4d62b59aca84e990cacaf5c52b0930daa23de94ce87017ba2d84988ddfc9c58db67aada613c2dd08457941a6", + }, + } + for v, _ in test_vectors { + key, _ := hex.decode(transmute([]byte)(v.key), context.temp_allocator) + iv, _ := hex.decode(transmute([]byte)(v.iv), context.temp_allocator) + plaintext, _ := hex.decode(transmute([]byte)(v.plaintext), context.temp_allocator) + ciphertext, _ := hex.decode(transmute([]byte)(v.ciphertext), context.temp_allocator) + + dst := make([]byte, len(ciphertext), context.temp_allocator) + + ctx: aes.Context_CTR + aes.init_ctr(&ctx, key, iv, impl) + + aes.xor_bytes_ctr(&ctx, dst, plaintext) + + dst_str := string(hex.encode(dst[:], context.temp_allocator)) + tc.expect( + t, + dst_str == v.ciphertext, + fmt.tprintf( + "AES-CTR/%v: Expected: %s for encrypt(%s, %s, %s), but got %s instead", + impl, + v.ciphertext, + v.key, + v.iv, + v.plaintext, + dst_str, + ), + ) + } + + // Incrementally read 1, 2, 3, ..., 2048 bytes of keystream, and + // compare the SHA-512/256 digest with a known value. Results + // and testcase taken from a known good implementation. + + tmp := make([]byte, 2048, context.temp_allocator) + + ctx: aes.Context_CTR + key: [aes.KEY_SIZE_256]byte + nonce: [aes.CTR_IV_SIZE]byte + aes.init_ctr(&ctx, key[:], nonce[:]) + + h_ctx: sha2.Context_512 + sha2.init_512_256(&h_ctx) + + for i := 1; i < 2048; i = i + 1 { + aes.keystream_bytes_ctr(&ctx, tmp[:i]) + sha2.update(&h_ctx, tmp[:i]) + } + + digest: [32]byte + sha2.final(&h_ctx, digest[:]) + digest_str := string(hex.encode(digest[:], context.temp_allocator)) + + expected_digest_str := "d4445343afeb9d1237f95b10d00358aed4c1d7d57c9fe480cd0afb5e2ffd448c" + tc.expect( + t, + expected_digest_str == digest_str, + fmt.tprintf( + "AES-CTR/%v: Expected %s for keystream digest, but got %s instead", + impl, + expected_digest_str, + digest_str, + ), + ) +} + +@(test) +test_aes_gcm :: proc(t: ^testing.T, impl: aes.Implementation) { + tc.log(t, fmt.tprintf("Testing AES-GCM/%v", impl)) + + // NIST did a reorg of their site, so the source of the test vectors + // is only available from an archive. The commented out tests are + // for non-96-bit IVs which our implementation does not support. + // + // https://csrc.nist.rip/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf + test_vectors := []struct { + key: string, + iv: string, + aad: string, + plaintext: string, + ciphertext: string, + tag: string, + } { + { + "00000000000000000000000000000000", + "000000000000000000000000", + "", + "", + "", + "58e2fccefa7e3061367f1d57a4e7455a", + }, + { + "00000000000000000000000000000000", + "000000000000000000000000", + "", + "00000000000000000000000000000000", + "0388dace60b6a392f328c2b971b2fe78", + "ab6e47d42cec13bdf53a67b21257bddf", + }, + { + "feffe9928665731c6d6a8f9467308308", + "cafebabefacedbaddecaf888", + "", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255", + "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985", + "4d5c2af327cd64a62cf35abd2ba6fab4", + }, + { + "feffe9928665731c6d6a8f9467308308", + "cafebabefacedbaddecaf888", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091", + "5bc94fbc3221a5db94fae95ae7121a47", + }, + /* + { + "feffe9928665731c6d6a8f9467308308", + "cafebabefacedbad", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "61353b4c2806934a777ff51fa22a4755699b2a714fcdc6f83766e5f97b6c742373806900e49f24b22b097544d4896b424989b5e1ebac0f07c23f4598", + "3612d2e79e3b0785561be14aaca2fccb", + }, + { + "feffe9928665731c6d6a8f9467308308", + "9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "8ce24998625615b603a033aca13fb894be9112a5c3a211a8ba262a3cca7e2ca701e4a9a4fba43c90ccdcb281d48c7c6fd62875d2aca417034c34aee5", + "619cc5aefffe0bfa462af43c1699d050", + }, + */ + { + "000000000000000000000000000000000000000000000000", + "000000000000000000000000", + "", + "", + "", + "cd33b28ac773f74ba00ed1f312572435", + }, + { + "000000000000000000000000000000000000000000000000", + "000000000000000000000000", + "", + "00000000000000000000000000000000", + "98e7247c07f0fe411c267e4384b0f600", + "2ff58d80033927ab8ef4d4587514f0fb", + }, + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c", + "cafebabefacedbaddecaf888", + "", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255", + "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710acade256", + "9924a7c8587336bfb118024db8674a14", + }, + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c", + "cafebabefacedbaddecaf888", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "3980ca0b3c00e841eb06fac4872a2757859e1ceaa6efd984628593b40ca1e19c7d773d00c144c525ac619d18c84a3f4718e2448b2fe324d9ccda2710", + "2519498e80f1478f37ba55bd6d27618c", + }, + /* + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c", + "cafebabefacedbad", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "0f10f599ae14a154ed24b36e25324db8c566632ef2bbb34f8347280fc4507057fddc29df9a471f75c66541d4d4dad1c9e93a19a58e8b473fa0f062f7", + "65dcc57fcf623a24094fcca40d3533f8", + }, + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c", + "9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "d27e88681ce3243c4830165a8fdcf9ff1de9a1d8e6b447ef6ef7b79828666e4581e79012af34ddd9e2f037589b292db3e67c036745fa22e7e9b7373b", + "dcf566ff291c25bbb8568fc3d376a6d9", + }, + */ + { + "0000000000000000000000000000000000000000000000000000000000000000", + "000000000000000000000000", + "", + "", + "", + "530f8afbc74536b9a963b4f1c4cb738b", + }, + { + "0000000000000000000000000000000000000000000000000000000000000000", + "000000000000000000000000", + "", + "00000000000000000000000000000000", + "cea7403d4d606b6e074ec5d3baf39d18", + "d0d1c8a799996bf0265b98b5d48ab919", + }, + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", + "cafebabefacedbaddecaf888", + "", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255", + "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015ad", + "b094dac5d93471bdec1a502270e3cc6c", + }, + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", + "cafebabefacedbaddecaf888", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662", + "76fc6ece0f4e1768cddf8853bb2d551b", + }, + /* + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", + "cafebabefacedbad", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "c3762df1ca787d32ae47c13bf19844cbaf1ae14d0b976afac52ff7d79bba9de0feb582d33934a4f0954cc2363bc73f7862ac430e64abe499f47c9b1f", + "3a337dbf46a792c45e454913fe2ea8f2", + }, + { + "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", + "9313225df88406e555909c5aff5269aa6a7a9538534f7da1e4c303d2a318a728c3c0c95156809539fcf0e2429a6b525416aedbf5a0de6a57a637b39b", + "feedfacedeadbeeffeedfacedeadbeefabaddad2", + "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", + "5a8def2f0c9e53f1f75d7853659e2a20eeb2b22aafde6419a058ab4f6f746bf40fc0c3b780f244452da3ebf1c5d82cdea2418997200ef82e44ae7e3f", + "a44a8266ee1c8eb0c8b5d4cf5ae9f19a", + }, + */ + } + for v, _ in test_vectors { + key, _ := hex.decode(transmute([]byte)(v.key), context.temp_allocator) + iv, _ := hex.decode(transmute([]byte)(v.iv), context.temp_allocator) + aad, _ := hex.decode(transmute([]byte)(v.aad), context.temp_allocator) + plaintext, _ := hex.decode(transmute([]byte)(v.plaintext), context.temp_allocator) + ciphertext, _ := hex.decode(transmute([]byte)(v.ciphertext), context.temp_allocator) + tag, _ := hex.decode(transmute([]byte)(v.tag), context.temp_allocator) + + tag_ := make([]byte, len(tag), context.temp_allocator) + dst := make([]byte, len(ciphertext), context.temp_allocator) + + ctx: aes.Context_GCM + aes.init_gcm(&ctx, key, impl) + + aes.seal_gcm(&ctx, dst, tag_, iv, aad, plaintext) + dst_str := string(hex.encode(dst[:], context.temp_allocator)) + tag_str := string(hex.encode(tag_[:], context.temp_allocator)) + + tc.expect( + t, + dst_str == v.ciphertext && tag_str == v.tag, + fmt.tprintf( + "AES-GCM/%v: Expected: (%s, %s) for seal(%s, %s, %s, %s), but got (%s, %s) instead", + impl, + v.ciphertext, + v.tag, + v.key, + v.iv, + v.aad, + v.plaintext, + dst_str, + tag_str, + ), + ) + + ok := aes.open_gcm(&ctx, dst, iv, aad, ciphertext, tag) + dst_str = string(hex.encode(dst[:], context.temp_allocator)) + + tc.expect( + t, + ok && dst_str == v.plaintext, + fmt.tprintf( + "AES-GCM/%v: Expected: (%s, true) for open(%s, %s, %s, %s, %s), but got (%s, %s) instead", + impl, + v.plaintext, + v.key, + v.iv, + v.aad, + v.ciphertext, + v.tag, + dst_str, + ok, + ), + ) + } +} diff --git a/tests/core/crypto/test_crypto_benchmark.odin b/tests/core/crypto/test_crypto_benchmark.odin index cc69cb16d..600dc9ade 100644 --- a/tests/core/crypto/test_crypto_benchmark.odin +++ b/tests/core/crypto/test_crypto_benchmark.odin @@ -6,6 +6,7 @@ import "core:fmt" import "core:testing" import "core:time" +import "core:crypto/aes" import "core:crypto/chacha20" import "core:crypto/chacha20poly1305" import "core:crypto/ed25519" @@ -25,6 +26,7 @@ bench_crypto :: proc(t: ^testing.T) { bench_chacha20(t) bench_poly1305(t) bench_chacha20poly1305(t) + bench_aes256_gcm(t) bench_ed25519(t) bench_x25519(t) } @@ -134,6 +136,26 @@ _benchmark_chacha20poly1305 :: proc( return nil } +_benchmark_aes256_gcm :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + nonce: [aes.GCM_NONCE_SIZE]byte + tag: [aes.GCM_TAG_SIZE]byte = --- + + ctx := transmute(^aes.Context_GCM)context.user_ptr + + for _ in 0 ..= options.rounds { + aes.seal_gcm(ctx, buf, tag[:], nonce[:], nil, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + benchmark_print :: proc(name: string, options: ^time.Benchmark_Options) { fmt.printf( "\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", @@ -221,6 +243,44 @@ bench_chacha20poly1305 :: proc(t: ^testing.T) { benchmark_print(name, options) } +bench_aes256_gcm :: proc(t: ^testing.T) { + name := "AES256-GCM 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_aes256_gcm, + teardown = _teardown_sized_buf, + } + + key := [aes.KEY_SIZE_256]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + ctx: aes.Context_GCM + aes.init_gcm(&ctx, key[:]) + + context.user_ptr = &ctx + + err := time.benchmark(options, context.allocator) + tc.expect(t, err == nil, name) + benchmark_print(name, options) + + name = "AES256-GCM 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + tc.expect(t, err == nil, name) + benchmark_print(name, options) + + name = "AES256-GCM 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + tc.expect(t, err == nil, name) + benchmark_print(name, options) +} + bench_ed25519 :: proc(t: ^testing.T) { iters :: 10000 From 8ae375dbffa344dc6f99fe0dc82f9070559ab137 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 23 May 2024 16:23:54 -0400 Subject: [PATCH 033/270] Move log timestamping out to `do_time_header` proc --- core/log/file_console_logger.odin | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index bcce67578..99ca1f51a 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -73,15 +73,7 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string do_level_header(options, level, &buf) when time.IS_SUPPORTED { - if Full_Timestamp_Opts & options != nil { - fmt.sbprint(&buf, "[") - t := time.now() - y, m, d := time.date(t) - h, min, s := time.clock(t) - if .Date in options { fmt.sbprintf(&buf, "%d-%02d-%02d ", y, m, d) } - if .Time in options { fmt.sbprintf(&buf, "%02d:%02d:%02d", h, min, s) } - fmt.sbprint(&buf, "] ") - } + do_time_header(options, &buf, time.now()) } do_location_header(options, &buf, location) @@ -125,6 +117,19 @@ do_level_header :: proc(opts: Options, level: Level, str: ^strings.Builder) { } } +do_time_header :: proc(opts: Options, buf: ^strings.Builder, t: time.Time) { + when time.IS_SUPPORTED { + if Full_Timestamp_Opts & opts != nil { + fmt.sbprint(buf, "[") + y, m, d := time.date(t) + h, min, s := time.clock(t) + if .Date in opts { fmt.sbprintf(buf, "%d-%02d-%02d ", y, m, d) } + if .Time in opts { fmt.sbprintf(buf, "%02d:%02d:%02d", h, min, s) } + fmt.sbprint(buf, "] ") + } + } +} + do_location_header :: proc(opts: Options, buf: ^strings.Builder, location := #caller_location) { if Location_Header_Opts & opts == nil { return From 7d4da6eaa7cf420b1808652cb44763e259634001 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 23 May 2024 16:29:18 -0400 Subject: [PATCH 034/270] Fix trailing space with only `.Date` log option --- core/log/file_console_logger.odin | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index 99ca1f51a..6843d3a63 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -123,7 +123,12 @@ do_time_header :: proc(opts: Options, buf: ^strings.Builder, t: time.Time) { fmt.sbprint(buf, "[") y, m, d := time.date(t) h, min, s := time.clock(t) - if .Date in opts { fmt.sbprintf(buf, "%d-%02d-%02d ", y, m, d) } + if .Date in opts { + fmt.sbprintf(buf, "%d-%02d-%02d", y, m, d) + if .Time in opts { + fmt.sbprint(buf, " ") + } + } if .Time in opts { fmt.sbprintf(buf, "%02d:%02d:%02d", h, min, s) } fmt.sbprint(buf, "] ") } From 1875e7c36a4aaee71aa39defe0d169104a8030b0 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 15:56:58 -0400 Subject: [PATCH 035/270] Make `log.do_*_header` argument orders consistent --- core/log/file_console_logger.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index 6843d3a63..661f5d408 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -70,7 +70,7 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string 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[:]) - do_level_header(options, level, &buf) + do_level_header(options, &buf, level) when time.IS_SUPPORTED { do_time_header(options, &buf, time.now()) @@ -91,7 +91,7 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text) } -do_level_header :: proc(opts: Options, level: Level, str: ^strings.Builder) { +do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) { RESET :: "\x1b[0m" RED :: "\x1b[31m" From 558c330028e90db9934d82414e7b21374ed58e1c Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 23 May 2024 16:43:26 -0400 Subject: [PATCH 036/270] Add task-stopping functionality to `thread.Pool` --- core/thread/thread_pool.odin | 130 +++++++++++++++++++++++++++++++---- 1 file changed, 115 insertions(+), 15 deletions(-) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index fddcac89e..53f89ea0c 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -44,6 +44,29 @@ Pool :: struct { tasks_done: [dynamic]Task, } +Pool_Thread_Data :: struct { + pool: ^Pool, + task: Task, +} + +@(private="file") +pool_thread_runner :: proc(t: ^Thread) { + data := cast(^Pool_Thread_Data)t.data + pool := data.pool + + for intrinsics.atomic_load(&pool.is_running) { + sync.wait(&pool.sem_available) + + if task, ok := pool_pop_waiting(pool); ok { + data.task = task + pool_do_work(pool, task) + data.task = {} + } + } + + sync.post(&pool.sem_available, 1) +} + // Once initialized, the pool's memory address is not allowed to change until // it is destroyed. // @@ -58,21 +81,11 @@ pool_init :: proc(pool: ^Pool, allocator: mem.Allocator, thread_count: int) { pool.is_running = true for _, i in pool.threads { - t := create(proc(t: ^Thread) { - pool := (^Pool)(t.data) - - for intrinsics.atomic_load(&pool.is_running) { - sync.wait(&pool.sem_available) - - if task, ok := pool_pop_waiting(pool); ok { - pool_do_work(pool, task) - } - } - - sync.post(&pool.sem_available, 1) - }) + t := create(pool_thread_runner) + data := new(Pool_Thread_Data) + data.pool = pool t.user_index = i - t.data = pool + t.data = data pool.threads[i] = t } } @@ -82,6 +95,8 @@ pool_destroy :: proc(pool: ^Pool) { delete(pool.tasks_done) for &t in pool.threads { + data := cast(^Pool_Thread_Data)t.data + free(data, pool.allocator) destroy(t) } @@ -103,7 +118,7 @@ pool_join :: proc(pool: ^Pool) { yield() -started_count: int + started_count: int for started_count < len(pool.threads) { started_count = 0 for t in pool.threads { @@ -138,6 +153,91 @@ pool_add_task :: proc(pool: ^Pool, allocator: mem.Allocator, procedure: Task_Pro sync.post(&pool.sem_available, 1) } +// Forcibly stop a running task by its user index. +// +// This will terminate the underlying thread. Ideally, you should use some +// means of communication to stop a task, as thread termination may leave +// resources unclaimed. +// +// The thread will be restarted to accept new tasks. +// +// Returns true if the task was found and terminated. +pool_stop_task :: proc(pool: ^Pool, user_index: int, exit_code: int = 1) -> bool { + sync.guard(&pool.mutex) + + for t, i in pool.threads { + data := cast(^Pool_Thread_Data)t.data + if data.task.user_index == user_index && data.task.procedure != nil { + terminate(t, exit_code) + + append(&pool.tasks_done, data.task) + intrinsics.atomic_add(&pool.num_done, 1) + intrinsics.atomic_sub(&pool.num_outstanding, 1) + intrinsics.atomic_sub(&pool.num_in_processing, 1) + + destroy(t) + + replacement := create(pool_thread_runner) + replacement.user_index = t.user_index + replacement.data = data + pool.threads[i] = replacement + + start(replacement) + return true + } + } + + return false +} + +// Forcibly stop all running tasks. +// +// The same notes from `pool_stop_task` apply here. +pool_stop_all_tasks :: proc(pool: ^Pool, exit_code: int = 1) { + sync.guard(&pool.mutex) + + for t, i in pool.threads { + data := cast(^Pool_Thread_Data)t.data + if data.task.procedure != nil { + terminate(t, exit_code) + + append(&pool.tasks_done, data.task) + intrinsics.atomic_add(&pool.num_done, 1) + intrinsics.atomic_sub(&pool.num_outstanding, 1) + intrinsics.atomic_sub(&pool.num_in_processing, 1) + + destroy(t) + + replacement := create(pool_thread_runner) + replacement.user_index = t.user_index + replacement.data = data + pool.threads[i] = replacement + + start(replacement) + } + } +} + +// Force the pool to stop all of its threads and put it into a state where +// it will no longer run any more tasks. +// +// The pool must still be destroyed after this. +pool_shutdown :: proc(pool: ^Pool, exit_code: int = 1) { + sync.guard(&pool.mutex) + + for t in pool.threads { + terminate(t, exit_code) + + data := cast(^Pool_Thread_Data)t.data + if data.task.procedure != nil { + append(&pool.tasks_done, data.task) + intrinsics.atomic_add(&pool.num_done, 1) + intrinsics.atomic_sub(&pool.num_outstanding, 1) + intrinsics.atomic_sub(&pool.num_in_processing, 1) + } + } +} + // Number of tasks waiting to be processed. Only informational, mostly for // debugging. Don't rely on this value being consistent with other num_* // values. From 8137b9dd753672d5359828a3170545d856936f10 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 23 May 2024 16:50:21 -0400 Subject: [PATCH 037/270] Add `mem.tracking_allocator_reset` --- core/mem/tracking_allocator.odin | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/mem/tracking_allocator.odin b/core/mem/tracking_allocator.odin index bc624617d..1b57e5fb4 100644 --- a/core/mem/tracking_allocator.odin +++ b/core/mem/tracking_allocator.odin @@ -47,6 +47,7 @@ tracking_allocator_destroy :: proc(t: ^Tracking_Allocator) { } +// Clear only the current allocation data while keeping the totals intact. tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { sync.mutex_lock(&t.mutex) clear(&t.allocation_map) @@ -55,6 +56,19 @@ tracking_allocator_clear :: proc(t: ^Tracking_Allocator) { sync.mutex_unlock(&t.mutex) } +// Reset all of a Tracking Allocator's allocation data back to zero. +tracking_allocator_reset :: proc(t: ^Tracking_Allocator) { + sync.mutex_lock(&t.mutex) + clear(&t.allocation_map) + clear(&t.bad_free_array) + t.total_memory_allocated = 0 + t.total_allocation_count = 0 + t.total_memory_freed = 0 + t.total_free_count = 0 + t.peak_memory_allocated = 0 + t.current_memory_allocated = 0 + sync.mutex_unlock(&t.mutex) +} @(require_results) tracking_allocator :: proc(data: ^Tracking_Allocator) -> Allocator { From fc4f6b87bb777d73b506a16749e934e076a5b8bc Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 15:51:28 -0400 Subject: [PATCH 038/270] Add `core:encoding/ansi` package --- core/encoding/ansi/ansi.odin | 137 ++++++++++++++++++++++++++++++ core/encoding/ansi/doc.odin | 20 +++++ core/log/file_console_logger.odin | 9 +- 3 files changed, 162 insertions(+), 4 deletions(-) create mode 100644 core/encoding/ansi/ansi.odin create mode 100644 core/encoding/ansi/doc.odin diff --git a/core/encoding/ansi/ansi.odin b/core/encoding/ansi/ansi.odin new file mode 100644 index 000000000..5550a1671 --- /dev/null +++ b/core/encoding/ansi/ansi.odin @@ -0,0 +1,137 @@ +package ansi + +BEL :: "\a" // Bell +BS :: "\b" // Backspace +ESC :: "\e" // Escape + +// Fe Escape sequences + +CSI :: ESC + "[" // Control Sequence Introducer +OSC :: ESC + "]" // Operating System Command +ST :: ESC + "\\" // String Terminator + +// CSI sequences + +CUU :: "A" // Cursor Up +CUD :: "B" // Cursor Down +CUF :: "C" // Cursor Forward +CUB :: "D" // Cursor Back +CNL :: "E" // Cursor Next Line +CPL :: "F" // Cursor Previous Line +CHA :: "G" // Cursor Horizontal Absolute +CUP :: "H" // Cursor Position +ED :: "J" // Erase in Display +EL :: "K" // Erase in Line +SU :: "S" // Scroll Up +SD :: "T" // Scroll Down +HVP :: "f" // Horizontal Vertical Position +SGR :: "m" // Select Graphic Rendition +AUX_ON :: "5i" // AUX Port On +AUX_OFF :: "4i" // AUX Port Off +DSR :: "6n" // Device Status Report + +// CSI: private sequences + +SCP :: "s" // Save Current Cursor Position +RCP :: "u" // Restore Saved Cursor Position +DECAWM_ON :: "?7h" // Auto Wrap Mode (Enabled) +DECAWM_OFF :: "?7l" // Auto Wrap Mode (Disabled) +DECTCEM_SHOW :: "?25h" // Text Cursor Enable Mode (Visible) +DECTCEM_HIDE :: "?25l" // Text Cursor Enable Mode (Invisible) + +// SGR sequences + +RESET :: "0" +BOLD :: "1" +FAINT :: "2" +ITALIC :: "3" // Not widely supported. +UNDERLINE :: "4" +BLINK_SLOW :: "5" +BLINK_RAPID :: "6" // Not widely supported. +INVERT :: "7" // Also known as reverse video. +HIDE :: "8" // Not widely supported. +STRIKE :: "9" +FONT_PRIMARY :: "10" +FONT_ALT1 :: "11" +FONT_ALT2 :: "12" +FONT_ALT3 :: "13" +FONT_ALT4 :: "14" +FONT_ALT5 :: "15" +FONT_ALT6 :: "16" +FONT_ALT7 :: "17" +FONT_ALT8 :: "18" +FONT_ALT9 :: "19" +FONT_FRAKTUR :: "20" // Rarely supported. +UNDERLINE_DOUBLE :: "21" // May be interpreted as "disable bold." +NO_BOLD_FAINT :: "22" +NO_ITALIC_BLACKLETTER :: "23" +NO_UNDERLINE :: "24" +NO_BLINK :: "25" +PROPORTIONAL_SPACING :: "26" +NO_REVERSE :: "27" +NO_HIDE :: "28" +NO_STRIKE :: "29" + +FG_BLACK :: "30" +FG_RED :: "31" +FG_GREEN :: "32" +FG_YELLOW :: "33" +FG_BLUE :: "34" +FG_MAGENTA :: "35" +FG_CYAN :: "36" +FG_WHITE :: "37" +FG_COLOR :: "38" +FG_COLOR_8_BIT :: "38;5" // Followed by ";n" where n is in 0..=255 +FG_COLOR_24_BIT :: "38;2" // Followed by ";r;g;b" where r,g,b are in 0..=255 +FG_DEFAULT :: "39" + +BG_BLACK :: "40" +BG_RED :: "41" +BG_GREEN :: "42" +BG_YELLOW :: "43" +BG_BLUE :: "44" +BG_MAGENTA :: "45" +BG_CYAN :: "46" +BG_WHITE :: "47" +BG_COLOR :: "48" +BG_COLOR_8_BIT :: "48;5" // Followed by ";n" where n is in 0..=255 +BG_COLOR_24_BIT :: "48;2" // Followed by ";r;g;b" where r,g,b are in 0..=255 +BG_DEFAULT :: "49" + +NO_PROPORTIONAL_SPACING :: "50" +FRAMED :: "51" +ENCIRCLED :: "52" +OVERLINED :: "53" +NO_FRAME_ENCIRCLE :: "54" +NO_OVERLINE :: "55" + +// SGR: non-standard bright colors + +FG_BRIGHT_BLACK :: "90" // Also known as grey. +FG_BRIGHT_RED :: "91" +FG_BRIGHT_GREEN :: "92" +FG_BRIGHT_YELLOW :: "93" +FG_BRIGHT_BLUE :: "94" +FG_BRIGHT_MAGENTA :: "95" +FG_BRIGHT_CYAN :: "96" +FG_BRIGHT_WHITE :: "97" + +BG_BRIGHT_BLACK :: "100" // Also known as grey. +BG_BRIGHT_RED :: "101" +BG_BRIGHT_GREEN :: "102" +BG_BRIGHT_YELLOW :: "103" +BG_BRIGHT_BLUE :: "104" +BG_BRIGHT_MAGENTA :: "105" +BG_BRIGHT_CYAN :: "106" +BG_BRIGHT_WHITE :: "107" + +// Fp Escape sequences + +DECSC :: ESC + "7" // DEC Save Cursor +DECRC :: ESC + "8" // DEC Restore Cursor + +// OSC sequences + +WINDOW_TITLE :: "2" // Followed by ";" ST. +HYPERLINK :: "8" // Followed by ";[params];" ST. Closed by OSC HYPERLINK ";;" ST. +CLIPBOARD :: "52" // Followed by ";c;" ST. diff --git a/core/encoding/ansi/doc.odin b/core/encoding/ansi/doc.odin new file mode 100644 index 000000000..a0945c581 --- /dev/null +++ b/core/encoding/ansi/doc.odin @@ -0,0 +1,20 @@ +/* +package ansi implements constant references to many widely-supported ANSI +escape codes, primarily used in terminal emulators for enhanced graphics, such +as colors, text styling, and animated displays. + +For example, you can print out a line of cyan text like this: + fmt.println(ansi.CSI + ansi.FG_CYAN + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR) + +Multiple SGR (Select Graphic Rendition) codes can be joined by semicolons: + fmt.println(ansi.CSI + ansi.BOLD + ";" + ansi.FG_BLUE + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR) + +If your terminal supports 24-bit true color mode, you can also do this: + fmt.println(ansi.CSI + ansi.FG_COLOR_24_BIT + ";0;255;255" + ansi.SGR + "Hellope!" + ansi.CSI + ansi.RESET + ansi.SGR) + +For more information, see: + 1. https://en.wikipedia.org/wiki/ANSI_escape_code + 2. https://www.vt100.net/docs/vt102-ug/chapter5.html + 3. https://invisible-island.net/xterm/ctlseqs/ctlseqs.html +*/ +package ansi diff --git a/core/log/file_console_logger.odin b/core/log/file_console_logger.odin index 661f5d408..fb968ccb6 100644 --- a/core/log/file_console_logger.odin +++ b/core/log/file_console_logger.odin @@ -1,6 +1,7 @@ //+build !freestanding package log +import "core:encoding/ansi" import "core:fmt" import "core:strings" import "core:os" @@ -93,10 +94,10 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) { - RESET :: "\x1b[0m" - RED :: "\x1b[31m" - YELLOW :: "\x1b[33m" - DARK_GREY :: "\x1b[90m" + RESET :: ansi.CSI + ansi.RESET + ansi.SGR + RED :: ansi.CSI + ansi.FG_RED + ansi.SGR + YELLOW :: ansi.CSI + ansi.FG_YELLOW + ansi.SGR + DARK_GREY :: ansi.CSI + ansi.FG_BRIGHT_BLACK + ansi.SGR col := RESET switch level { From 50dffaf131452f64a2b28477718850ad2a8b78f2 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 16:29:34 -0400 Subject: [PATCH 039/270] Add `mem.Rollback_Stack` --- core/mem/rollback_stack_allocator.odin | 319 +++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 core/mem/rollback_stack_allocator.odin diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin new file mode 100644 index 000000000..bf397d2c8 --- /dev/null +++ b/core/mem/rollback_stack_allocator.odin @@ -0,0 +1,319 @@ +package mem + +// The Rollback Stack Allocator was designed for the test runner to be fast, +// able to grow, and respect the Tracking Allocator's requirement for +// individual frees. It is not overly concerned with fragmentation, however. +// +// It has support for expansion when configured with a block allocator and +// limited support for out-of-order frees. +// +// Allocation has constant-time best and usual case performance. +// At worst, it is linear according to the number of memory blocks. +// +// Allocation follows a first-fit strategy when there are multiple memory +// blocks. +// +// Freeing has constant-time best and usual case performance. +// At worst, it is linear according to the number of memory blocks and number +// of freed items preceding the last item in a block. +// +// Resizing has constant-time performance, if it's the last item in a block, or +// the new size is smaller. Naturally, this becomes linear-time if there are +// multiple blocks to search for the pointer's owning block. Otherwise, the +// allocator defaults to a combined alloc & free operation internally. +// +// Out-of-order freeing is accomplished by collapsing a run of freed items +// from the last allocation backwards. +// +// Each allocation has an overhead of 8 bytes and any extra bytes to satisfy +// the requested alignment. + +import "base:runtime" + +ROLLBACK_STACK_DEFAULT_BLOCK_SIZE :: 4 * Megabyte + +// This limitation is due to the size of `prev_ptr`, but it is only for the +// head block; any allocation in excess of the allocator's `block_size` is +// valid, so long as the block allocator can handle it. +// +// This is because allocations over the block size are not split up if the item +// within is freed; they are immediately returned to the block allocator. +ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE :: 1 * Gigabyte + + +Rollback_Stack_Header :: bit_field u64 { + prev_offset: int | 32, + is_free: bool | 1, + prev_ptr: int | 31, +} + +Rollback_Stack_Block :: struct { + next_block: ^Rollback_Stack_Block, + last_alloc: rawptr, + offset: int, + buffer: []byte, +} + +Rollback_Stack :: struct { + head: ^Rollback_Stack_Block, + block_size: int, + block_allocator: Allocator, +} + + +@(private="file") +@(require_results) +rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { + start := cast(uintptr)raw_data(block.buffer) + end := cast(uintptr)raw_data(block.buffer) + cast(uintptr)block.offset + return start < cast(uintptr)ptr && cast(uintptr)ptr <= end +} + +@(private="file") +@(require_results) +rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( + parent: ^Rollback_Stack_Block, + block: ^Rollback_Stack_Block, + header: ^Rollback_Stack_Header, + err: Allocator_Error, +) { + for block = stack.head; block != nil; block = block.next_block { + if rb_ptr_in_bounds(block, ptr) { + header = cast(^Rollback_Stack_Header)(cast(uintptr)ptr - size_of(Rollback_Stack_Header)) + return + } + parent = block + } + return nil, nil, nil, .Invalid_Pointer +} + +@(private="file") +@(require_results) +rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( + block: ^Rollback_Stack_Block, + header: ^Rollback_Stack_Header, + ok: bool, +) { + for block = stack.head; block != nil; block = block.next_block { + if block.last_alloc == ptr { + header = cast(^Rollback_Stack_Header)(cast(uintptr)ptr - size_of(Rollback_Stack_Header)) + return block, header, true + } + } + return nil, nil, false +} + +@(private="file") +rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header) { + header := header + for block.offset > 0 && header.is_free { + block.offset = header.prev_offset + block.last_alloc = cast(rawptr)(cast(uintptr)raw_data(block.buffer) + cast(uintptr)header.prev_ptr) + header = cast(^Rollback_Stack_Header)(cast(uintptr)raw_data(block.buffer) + cast(uintptr)header.prev_ptr - size_of(Rollback_Stack_Header)) + } +} + +@(private="file") +@(require_results) +rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { + parent, block, header := rb_find_ptr(stack, ptr) or_return + if header.is_free { + return .Invalid_Pointer + } + header.is_free = true + if block.last_alloc == ptr { + block.offset = header.prev_offset + rb_rollback_block(block, header) + } + if parent != nil && block.offset == 0 { + parent.next_block = block.next_block + runtime.mem_free_with_size(block, size_of(Rollback_Stack_Block) + len(block.buffer), stack.block_allocator) + } + return nil +} + +@(private="file") +rb_free_all :: proc(stack: ^Rollback_Stack) { + for block := stack.head.next_block; block != nil; /**/ { + next_block := block.next_block + runtime.mem_free_with_size(block, size_of(Rollback_Stack_Block) + len(block.buffer), stack.block_allocator) + block = next_block + } + + stack.head.next_block = nil + stack.head.last_alloc = nil + stack.head.offset = 0 +} + +@(private="file") +@(require_results) +rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) { + if ptr != nil { + if block, _, ok := rb_find_last_alloc(stack, ptr); ok { + if block.offset + (size - old_size) < len(block.buffer) { + block.offset += (size - old_size) + #no_bounds_check return (cast([^]byte)ptr)[:size], nil + } + } + } + + result = rb_alloc(stack, size, alignment) or_return + runtime.mem_copy_non_overlapping(raw_data(result), ptr, old_size) + err = rb_free(stack, ptr) + + return +} + +@(private="file") +@(require_results) +rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byte, err: Allocator_Error) { + parent: ^Rollback_Stack_Block + for block := stack.head; /**/; block = block.next_block { + if block == nil { + if stack.block_allocator.procedure == nil { + return nil, .Out_Of_Memory + } + + minimum_size_required := size_of(Rollback_Stack_Header) + size + alignment - 1 + new_block_size := max(minimum_size_required, stack.block_size) + block = rb_make_block(new_block_size, stack.block_allocator) or_return + parent.next_block = block + } + + start := cast(uintptr)raw_data(block.buffer) + cast(uintptr)block.offset + padding := calc_padding_with_header(start, cast(uintptr)alignment, size_of(Rollback_Stack_Header)) + + if block.offset + padding + size > len(block.buffer) { + parent = block + continue + } + + header := cast(^Rollback_Stack_Header)(start + cast(uintptr)padding - size_of(Rollback_Stack_Header)) + ptr := (cast([^]byte)(start + cast(uintptr)padding)) + + header^ = { + prev_offset = block.offset, + prev_ptr = max(0, cast(int)(cast(uintptr)block.last_alloc - cast(uintptr)raw_data(block.buffer))), + is_free = false, + } + + block.last_alloc = ptr + block.offset += padding + size + + if len(block.buffer) > stack.block_size { + // This block exceeds the allocator's standard block size and is considered a singleton. + // Prevent any further allocations on it. + block.offset = len(block.buffer) + } + + #no_bounds_check return ptr[:size], nil + } + + return nil, .Out_Of_Memory +} + +@(private="file") +@(require_results) +rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) { + buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return + + block = cast(^Rollback_Stack_Block)raw_data(buffer) + #no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):] + return +} + + +rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte) { + MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr) + assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.") + + block := cast(^Rollback_Stack_Block)raw_data(buffer) + block^ = {} + #no_bounds_check block.buffer = buffer[size_of(Rollback_Stack_Block):] + + stack^ = {} + stack.head = block + stack.block_size = len(block.buffer) +} + +rollback_stack_init_dynamic :: proc( + stack: ^Rollback_Stack, + block_size := ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, + block_allocator := context.allocator, +) -> Allocator_Error { + assert(block_size >= size_of(Rollback_Stack_Header) + size_of(rawptr), "Rollback Stack Allocator block size is too small.") + assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 1 gigabyte.") + + block := rb_make_block(block_size, block_allocator) or_return + + stack^ = {} + stack.head = block + stack.block_size = block_size + stack.block_allocator = block_allocator + + return nil +} + +rollback_stack_init :: proc { + rollback_stack_init_buffered, + rollback_stack_init_dynamic, +} + +rollback_stack_destroy :: proc(stack: ^Rollback_Stack) { + if stack.block_allocator.procedure != nil { + rb_free_all(stack) + free(stack.head, stack.block_allocator) + } + stack^ = {} +} + +@(require_results) +rollback_stack_allocator :: proc(stack: ^Rollback_Stack) -> Allocator { + return Allocator { + data = stack, + procedure = rollback_stack_allocator_proc, + } +} + +@(require_results) +rollback_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, location := #caller_location, +) -> (result: []byte, err: Allocator_Error) { + stack := cast(^Rollback_Stack)allocator_data + + switch mode { + case .Alloc, .Alloc_Non_Zeroed: + assert(is_power_of_two(cast(uintptr)alignment), "alignment must be a power of two", location) + result = rb_alloc(stack, size, alignment) or_return + + if mode == .Alloc { + zero_slice(result) + } + + case .Free: + err = rb_free(stack, old_memory) + + case .Free_All: + rb_free_all(stack) + + case .Resize, .Resize_Non_Zeroed: + result = rb_resize(stack, old_memory, old_size, size, alignment) or_return + + #no_bounds_check if mode == .Resize && size > old_size { + zero_slice(result[old_size:]) + } + + case .Query_Features: + set := (^Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed} + } + return nil, nil + + case .Query_Info: + return nil, .Mode_Not_Implemented + } + + return +} From 95c2e020fff74f8e2e193db595c25726a8b9a99e Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 19:59:49 -0400 Subject: [PATCH 040/270] Share `libc` signal definitions with more platforms I confirmed that these 3 platforms share the same signal definitions from these sources. Haiku: https://github.com/haiku/haiku/blob/master/headers/posix/signal.h OpenBSD: https://github.com/openbsd/src/blob/master/sys/sys/signal.h NetBSD: http://fxr.watson.org/fxr/source/sys/signal.h?v=NETBSD --- core/c/libc/signal.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/c/libc/signal.odin b/core/c/libc/signal.odin index 186b74d8c..1489779fe 100644 --- a/core/c/libc/signal.odin +++ b/core/c/libc/signal.odin @@ -34,7 +34,7 @@ when ODIN_OS == .Windows { SIGTERM :: 15 } -when ODIN_OS == .Linux || ODIN_OS == .FreeBSD { +when ODIN_OS == .Linux || ODIN_OS == .FreeBSD || ODIN_OS == .Haiku || ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { SIG_ERR :: rawptr(~uintptr(0)) SIG_DFL :: rawptr(uintptr(0)) SIG_IGN :: rawptr(uintptr(1)) From b6c4dfb68d5d7d2ac883eaa18409eb8e7d6f2e9c Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 19:44:19 -0400 Subject: [PATCH 041/270] Refactor the test runner Changes - Support multi-threaded testing. - Support `set_fail_timeout` on all platforms. - Display an animated progress report. - Setup all tests with a context logger. - Give all tests their own separate custom allocators. - Support tracking test memory usage. - Display a summary of the failed tests at the end. - Let users select only specific tests to run. - Support copying failed tests to the clipboard to run again. - Support catching SIGINT (CTRL-C) to cancel early. - Record context in cleanup procs. - Write all log messages to STDERR for easy redirection. - Possibly more I've forgotten. New Options - `-define:test_threads=N`: Specify thread count. - `-define:test_thread_memory=B`: Specify initial memory block size in bytes to each thread. - `-define:test_track_memory=true`: Track the memory usage of individual tests. - `-define:test_fancy=false`: Disable animated progress report. - `-define:test_select=package.test_name,...`: Run only select tests. - `-define:test_clipboard=true`: Copy names of failed tests to the clipboard. - `-define:test_progress_width=24`: Change the width of the animated progress bars. --- core/testing/events.odin | 48 ++ core/testing/logging.odin | 71 +++ core/testing/reporting.odin | 312 +++++++++++ core/testing/runner.odin | 692 +++++++++++++++++++++++-- core/testing/signal_handler.odin | 19 + core/testing/signal_handler_other.odin | 11 + core/testing/testing.odin | 59 ++- 7 files changed, 1141 insertions(+), 71 deletions(-) create mode 100644 core/testing/events.odin create mode 100644 core/testing/logging.odin create mode 100644 core/testing/reporting.odin create mode 100644 core/testing/signal_handler.odin create mode 100644 core/testing/signal_handler_other.odin diff --git a/core/testing/events.odin b/core/testing/events.odin new file mode 100644 index 000000000..bab35aaad --- /dev/null +++ b/core/testing/events.odin @@ -0,0 +1,48 @@ +//+private +package testing + +import "base:runtime" +import "core:sync/chan" +import "core:time" + +Test_State :: enum { + Ready, + Running, + Successful, + Failed, +} + +Update_Channel :: chan.Chan(Channel_Event) +Update_Channel_Sender :: chan.Chan(Channel_Event, .Send) + +Task_Channel :: struct { + channel: Update_Channel, + test_index: int, +} + +Event_New_Test :: struct { + test_index: int, +} + +Event_State_Change :: struct { + new_state: Test_State, +} + +Event_Set_Fail_Timeout :: struct { + at_time: time.Time, + location: runtime.Source_Code_Location, +} + +Event_Log_Message :: struct { + level: runtime.Logger_Level, + text: string, + time: time.Time, + formatted_text: string, +} + +Channel_Event :: union { + Event_New_Test, + Event_State_Change, + Event_Set_Fail_Timeout, + Event_Log_Message, +} diff --git a/core/testing/logging.odin b/core/testing/logging.odin new file mode 100644 index 000000000..5bbbffeae --- /dev/null +++ b/core/testing/logging.odin @@ -0,0 +1,71 @@ +//+private +package testing + +import "base:runtime" +import "core:fmt" +import pkg_log "core:log" +import "core:strings" +import "core:sync/chan" +import "core:time" + +Default_Test_Logger_Opts :: runtime.Logger_Options { + .Level, + .Terminal_Color, + .Short_File_Path, + .Line, + .Procedure, + .Date, .Time, +} + +Log_Message :: struct { + level: runtime.Logger_Level, + text: string, + time: time.Time, + // `text` may be allocated differently, depending on where a log message + // originates from. + allocator: runtime.Allocator, +} + +test_logger_proc :: proc(logger_data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location := #caller_location) { + t := cast(^T)logger_data + + if level >= .Error { + t.error_count += 1 + } + + cloned_text, clone_error := strings.clone(text, t._log_allocator) + assert(clone_error == nil, "Error while cloning string in test thread logger proc.") + + now := time.now() + + chan.send(t.channel, Event_Log_Message { + level = level, + text = cloned_text, + time = now, + formatted_text = format_log_text(level, text, options, location, now, t._log_allocator), + }) +} + +runner_logger_proc :: proc(logger_data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location := #caller_location) { + log_messages := cast(^[dynamic]Log_Message)logger_data + + now := time.now() + + append(log_messages, Log_Message { + level = level, + text = format_log_text(level, text, options, location, now), + time = now, + allocator = context.allocator, + }) +} + +format_log_text :: proc(level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location: runtime.Source_Code_Location, at_time: time.Time, allocator := context.allocator) -> string{ + backing: [1024]byte + buf := strings.builder_from_bytes(backing[:]) + + pkg_log.do_level_header(options, &buf, level) + pkg_log.do_time_header(options, &buf, at_time) + pkg_log.do_location_header(options, &buf, location) + + return fmt.aprintf("%s%s", strings.to_string(buf), text, allocator = allocator) +} diff --git a/core/testing/reporting.odin b/core/testing/reporting.odin new file mode 100644 index 000000000..d06681c2d --- /dev/null +++ b/core/testing/reporting.odin @@ -0,0 +1,312 @@ +//+private +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" + +// Definitions of colors for use in the test runner. +SGR_RESET :: ansi.CSI + ansi.RESET + ansi.SGR +SGR_READY :: ansi.CSI + ansi.FG_BRIGHT_BLACK + ansi.SGR +SGR_RUNNING :: ansi.CSI + ansi.FG_YELLOW + ansi.SGR +SGR_SUCCESS :: ansi.CSI + ansi.FG_GREEN + ansi.SGR +SGR_FAILED :: ansi.CSI + ansi.FG_RED + ansi.SGR + +// More than enough bytes to cover long package names, long test names, dozens +// of ANSI codes, et cetera. +LINE_BUFFER_SIZE :: (PROGRESS_WIDTH * 8 + 256) * runtime.Byte + +PROGRESS_COLUMN_SPACING :: 2 + +Package_Run :: struct { + name: string, + header: string, + + frame_ready: bool, + + redraw_buffer: [LINE_BUFFER_SIZE]byte, + redraw_string: string, + + last_change_state: Test_State, + last_change_name: string, + + tests: []Internal_Test, + test_states: []Test_State, +} + +Report :: struct { + packages: []Package_Run, + packages_by_name: map[string]^Package_Run, + + pkg_column_len: int, + test_column_len: int, + + all_tests: []Internal_Test, + all_test_states: []Test_State, +} + +// Organize all tests by package and sort out test state data. +make_report :: proc(internal_tests: []Internal_Test) -> (report: Report, error: runtime.Allocator_Error) { + assert(len(internal_tests) > 0, "make_report called with no tests") + + packages: [dynamic]Package_Run + + report.all_tests = internal_tests + report.all_test_states = make([]Test_State, len(internal_tests)) or_return + + // First, figure out what belongs where. + #no_bounds_check cur_pkg := internal_tests[0].pkg + pkg_start: int + + // This loop assumes the tests are sorted by package already. + for it, index in internal_tests { + if cur_pkg != it.pkg { + #no_bounds_check { + append(&packages, Package_Run { + name = cur_pkg, + tests = report.all_tests[pkg_start:index], + test_states = report.all_test_states[pkg_start:index], + }) or_return + } + + pkg_start = index + report.pkg_column_len = max(report.pkg_column_len, len(cur_pkg)) + cur_pkg = it.pkg + } + report.test_column_len = max(report.test_column_len, len(it.name)) + } + + // Handle the last package. + #no_bounds_check { + append(&packages, Package_Run { + name = cur_pkg, + header = cur_pkg, + tests = report.all_tests[pkg_start:], + test_states = report.all_test_states[pkg_start:], + }) or_return + } + + report.pkg_column_len = PROGRESS_COLUMN_SPACING + max(report.pkg_column_len, len(cur_pkg)) + + shrink(&packages) or_return + + for &pkg in packages { + pkg.header = fmt.aprintf("%- *[1]s[", pkg.name, report.pkg_column_len) + assert(len(pkg.header) > 0, "Error allocating package header string.") + + // This is safe because the array is done resizing, and it has the same + // lifetime as the map. + report.packages_by_name[pkg.name] = &pkg + } + + // It's okay to discard the dynamic array's allocator information here, + // because its capacity has been shrunk to its length, it was allocated by + // the caller's context allocator, and it will be deallocated by the same. + // + // `delete_slice` is equivalent to `delete_dynamic_array` in this case. + report.packages = packages[:] + + return +} + +destroy_report :: proc(report: ^Report) { + for pkg in report.packages { + delete(pkg.header) + } + + delete(report.packages) + delete(report.packages_by_name) + delete(report.all_test_states) +} + +redraw_package :: proc(w: io.Writer, pkg: ^Package_Run) { + if pkg.frame_ready { + io.write_string(w, pkg.redraw_string) + return + } + + // Write the output line here so we can cache it. + line_builder := strings.builder_from_bytes(pkg.redraw_buffer[:]) + line_writer := strings.to_writer(&line_builder) + + highest_run_index: int + failed_count: int + done_count: int + #no_bounds_check for i := 0; i < len(pkg.test_states); i += 1 { + switch pkg.test_states[i] { + case .Ready: + continue + case .Running: + highest_run_index = max(highest_run_index, i) + case .Successful: + done_count += 1 + case .Failed: + failed_count += 1 + done_count += 1 + } + } + + start := max(0, highest_run_index - (PROGRESS_WIDTH - 1)) + end := min(start + PROGRESS_WIDTH, len(pkg.test_states)) + + // This variable is to keep track of the last ANSI code emitted, in + // order to avoid repeating the same code over in a sequence. + // + // This should help reduce screen flicker. + last_state := Test_State(-1) + + io.write_string(line_writer, pkg.header) + + #no_bounds_check for state in pkg.test_states[start:end] { + switch state { + case .Ready: + if last_state != state { + io.write_string(line_writer, SGR_READY) + last_state = state + } + case .Running: + if last_state != state { + io.write_string(line_writer, SGR_RUNNING) + last_state = state + } + case .Successful: + if last_state != state { + io.write_string(line_writer, SGR_SUCCESS) + last_state = state + } + case .Failed: + if last_state != state { + io.write_string(line_writer, SGR_FAILED) + last_state = state + } + } + io.write_byte(line_writer, '|') + } + + for _ in 0 ..< PROGRESS_WIDTH - (end - start) { + io.write_byte(line_writer, ' ') + } + + io.write_string(line_writer, SGR_RESET + "] ") + + ticker: string + if done_count == len(pkg.test_states) { + ticker = "[package done]" + if failed_count > 0 { + ticker = fmt.tprintf("%s (" + SGR_FAILED + "%i" + SGR_RESET + " failed)", ticker, failed_count) + } + } else { + if len(pkg.last_change_name) == 0 { + #no_bounds_check pkg.last_change_name = pkg.tests[0].name + } + + switch pkg.last_change_state { + case .Ready: + ticker = fmt.tprintf(SGR_READY + "%s" + SGR_RESET, pkg.last_change_name) + case .Running: + ticker = fmt.tprintf(SGR_RUNNING + "%s" + SGR_RESET, pkg.last_change_name) + case .Failed: + ticker = fmt.tprintf(SGR_FAILED + "%s" + SGR_RESET, pkg.last_change_name) + case .Successful: + ticker = fmt.tprintf(SGR_SUCCESS + "%s" + SGR_RESET, pkg.last_change_name) + } + } + + if done_count == len(pkg.test_states) { + fmt.wprintfln(line_writer, " % 4i :: %s", + len(pkg.test_states), + ticker, + ) + } else { + fmt.wprintfln(line_writer, "% 4i/% 4i :: %s", + done_count, + len(pkg.test_states), + ticker, + ) + } + + pkg.redraw_string = strings.to_string(line_builder) + pkg.frame_ready = true + io.write_string(w, pkg.redraw_string) +} + +redraw_report :: proc(w: io.Writer, report: Report) { + // If we print a line longer than the user's terminal can handle, it may + // wrap around, shifting the progress report out of alignment. + // + // There are ways to get the current terminal width, and that would be the + // ideal way to handle this, but it would require system-specific code such + // as setting STDIN to be non-blocking in order to read the response from + // the ANSI DSR escape code, or reading environment variables. + // + // The DECAWM escape codes control whether or not the terminal will wrap + // long lines or overwrite the last visible character. + // This should be fine for now. + // + // Note that we only do this for the animated summary; log messages are + // still perfectly fine to wrap, as they're printed in their own batch, + // whereas the animation depends on each package being only on one line. + // + // Of course, if you resize your terminal while it's printing, things can + // still break... + fmt.wprint(w, ansi.CSI + ansi.DECAWM_OFF) + for &pkg in report.packages { + redraw_package(w, &pkg) + } + fmt.wprint(w, ansi.CSI + ansi.DECAWM_ON) +} + +needs_to_redraw :: proc(report: Report) -> bool { + for pkg in report.packages { + if !pkg.frame_ready { + return true + } + } + + return false +} + +draw_status_bar :: proc(w: io.Writer, threads_string: string, total_done_count, total_test_count: int) { + if total_done_count != total_test_count { + fmt.wprintfln(w, + "%s % 4i/% 4i :: total", + threads_string, + total_done_count, + total_test_count) + } +} + +write_memory_report :: proc(w: io.Writer, tracker: ^mem.Tracking_Allocator, pkg, name: string) { + fmt.wprintf(w, + "<% 10M/% 10M> <% 10M> (% 5i/% 5i) :: %s.%s", + tracker.current_memory_allocated, + tracker.total_memory_allocated, + tracker.peak_memory_allocated, + tracker.total_free_count, + tracker.total_allocation_count, + pkg, + name) + + for ptr, entry in tracker.allocation_map { + fmt.wprintf(w, + "\n +++ leak % 10M @ %p [%s:%i:%s()]", + entry.size, + ptr, + filepath.base(entry.location.file_path), + entry.location.line, + entry.location.procedure) + } + + for entry in tracker.bad_free_array { + fmt.wprintf(w, + "\n +++ bad free @ %p [%s:%i:%s()]", + entry.memory, + filepath.base(entry.location.file_path), + entry.location.line, + entry.location.procedure) + } +} diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 0039f1939..ce5aa112a 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -1,73 +1,675 @@ //+private package testing +import "base:intrinsics" +import "base:runtime" +import "core:bytes" +import "core:encoding/ansi" +import "core:encoding/base64" +import "core:fmt" import "core:io" +import pkg_log "core:log" +import "core:mem" import "core:os" import "core:slice" +import "core:strings" +import "core:sync/chan" +import "core:thread" +import "core:time" + +// Keep `-vet` happy. +base64_encode :: base64.encode +_ :: pkg_log +_ :: strings + +// Specify how many threads to use when running tests. +TEST_THREADS : int : #config(test_threads, 0) +// Track the memory used by each test. +TRACKING_MEMORY : bool : #config(test_track_memory, false) +// Specify how much memory each thread allocator starts with. +PER_THREAD_MEMORY : int : #config(test_thread_memory, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) +// Select a specific set of tests to run by name. +TEST_SELECT : string : #config(test_select, "") +// Show the fancy animated progress report. +FANCY_OUTPUT : bool : #config(test_fancy, true) +// Copy failed tests to the clipboard when done. +USE_CLIPBOARD : bool : #config(test_clipboard, false) +// How many test results to show at a time per package. +PROGRESS_WIDTH : int : #config(test_progress_width, 24) + -reset_t :: proc(t: ^T) { - clear(&t.cleanups) - t.error_count = 0 -} end_t :: proc(t: ^T) { for i := len(t.cleanups)-1; i >= 0; i -= 1 { - c := t.cleanups[i] + #no_bounds_check c := t.cleanups[i] + context = c.ctx c.procedure(c.user_data) } + + delete(t.cleanups) + t.cleanups = {} +} + +Task_Data :: struct { + it: Internal_Test, + t: T, + allocator_index: int, +} + +Task_Timeout :: struct { + test_index: int, + at_time: time.Time, + location: runtime.Source_Code_Location, +} + +run_test_task :: proc(task: thread.Task) { + data := cast(^Task_Data)(task.data) + + chan.send(data.t.channel, Event_New_Test { + test_index = task.user_index, + }) + + chan.send(data.t.channel, Event_State_Change { + new_state = .Running, + }) + + context.logger = { + procedure = test_logger_proc, + data = &data.t, + lowest_level = .Debug if ODIN_DEBUG else .Info, + options = Default_Test_Logger_Opts, + } + + free_all(context.temp_allocator) + + run_internal_test(&data.t, data.it) + + end_t(&data.t) + + new_state : Test_State = .Failed if failed(&data.t) else .Successful + + chan.send(data.t.channel, Event_State_Change { + new_state = new_state, + }) } runner :: proc(internal_tests: []Internal_Test) -> bool { - stream := os.stream_from_handle(os.stdout) - w := io.to_writer(stream) + BATCH_BUFFER_SIZE :: 32 * mem.Kilobyte + POOL_BLOCK_SIZE :: 16 * mem.Kilobyte + CLIPBOARD_BUFFER_SIZE :: 16 * mem.Kilobyte - t := &T{} - t.w = w - reserve(&t.cleanups, 1024) - defer delete(t.cleanups) + BUFFERED_EVENTS_PER_CHANNEL :: 16 + RESERVED_LOG_MESSAGES :: 64 + RESERVED_TEST_FAILURES :: 64 - total_success_count := 0 - total_test_count := len(internal_tests) + ERROR_STRING_TIMEOUT : string : "Test timed out." + ERROR_STRING_UNKNOWN : string : "Test failed for unknown reasons." + OSC_WINDOW_TITLE : string : ansi.OSC + ansi.WINDOW_TITLE + ";Odin test runner (%i/%i)" + ansi.ST - slice.sort_by(internal_tests, proc(a, b: Internal_Test) -> bool { - if a.pkg < b.pkg { - return true + safe_delete_string :: proc(s: string, allocator := context.allocator) { + // Guard against bad frees on static strings. + switch raw_data(s) { + case raw_data(ERROR_STRING_TIMEOUT), raw_data(ERROR_STRING_UNKNOWN): + return + case: + delete(s, allocator) } - return a.name < b.name - }) + } - prev_pkg := "" + stdout := io.to_writer(os.stream_from_handle(os.stdout)) + stderr := io.to_writer(os.stream_from_handle(os.stderr)) + + // -- Prepare test data. + + alloc_error: mem.Allocator_Error + + when TEST_SELECT != "" { + select_internal_tests: [dynamic]Internal_Test + defer delete(select_internal_tests) + + { + index_list := TEST_SELECT + for selector in strings.split_iterator(&index_list, ",") { + // Temp allocator is fine since we just need to identify which test it's referring to. + split_selector := strings.split(selector, ".", context.temp_allocator) + + found := false + switch len(split_selector) { + case 1: + // Only the test name? + #no_bounds_check name := split_selector[0] + find_test_by_name: for it in internal_tests { + if it.name == name { + found = true + _, alloc_error = append(&select_internal_tests, it) + fmt.assertf(alloc_error == nil, "Error appending to select internal tests: %v", alloc_error) + break find_test_by_name + } + } + case 2: + #no_bounds_check pkg := split_selector[0] + #no_bounds_check name := split_selector[1] + find_test_by_pkg_and_name: for it in internal_tests { + if it.pkg == pkg && it.name == name { + found = true + _, alloc_error = append(&select_internal_tests, it) + fmt.assertf(alloc_error == nil, "Error appending to select internal tests: %v", alloc_error) + break find_test_by_pkg_and_name + } + } + } + if !found { + fmt.wprintfln(stderr, "No test found for the name: %q", selector) + } + } + } + + // Intentional shadow with user-specified tests. + internal_tests := select_internal_tests[:] + } + + total_failure_count := 0 + total_success_count := 0 + 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 + } + + if total_test_count == 0 { + // Exit early. + fmt.wprintln(stdout, "No tests to run.") + return true + } for it in internal_tests { - if it.p == nil { - total_test_count -= 1 - continue - } + // NOTE(Feoramund): The old test runner skipped over tests with nil + // procedures, but I couldn't find any case where they occurred. + // This assert stands to prevent any oversight on my part. + fmt.assertf(it.p != nil, "Test %s.%s has procedure.", it.pkg, it.name) + } - free_all(context.temp_allocator) - reset_t(t) - defer end_t(t) - - if prev_pkg != it.pkg { - prev_pkg = it.pkg - logf(t, "[Package: %s]", it.pkg) - } - - logf(t, "[Test: %s]", it.name) - - run_internal_test(t, it) - - if failed(t) { - logf(t, "[%s : FAILURE]", it.name) + slice.stable_sort_by(internal_tests, proc(a, b: Internal_Test) -> bool { + if a.pkg == b.pkg { + return a.name < b.name } else { - logf(t, "[%s : SUCCESS]", it.name) - total_success_count += 1 + return a.pkg < b.pkg + } + }) + + // -- Set thread count. + + when TEST_THREADS == 0 { + thread_count := os.processor_core_count() + } else { + thread_count := max(1, TEST_THREADS) + } + + thread_count = min(thread_count, total_test_count) + + // -- Allocate. + + pool_stack: mem.Rollback_Stack + alloc_error = mem.rollback_stack_init(&pool_stack, POOL_BLOCK_SIZE) + fmt.assertf(alloc_error == nil, "Error allocating memory for thread pool: %v", alloc_error) + defer mem.rollback_stack_destroy(&pool_stack) + + pool: thread.Pool + thread.pool_init(&pool, mem.rollback_stack_allocator(&pool_stack), thread_count) + defer thread.pool_destroy(&pool) + + task_channels: []Task_Channel = --- + task_channels, alloc_error = make([]Task_Channel, thread_count) + fmt.assertf(alloc_error == nil, "Error allocating memory for update channels: %v", alloc_error) + defer delete(task_channels) + + for &task_channel, index in task_channels { + task_channel.channel, alloc_error = chan.create_buffered(Update_Channel, BUFFERED_EVENTS_PER_CHANNEL, context.allocator) + fmt.assertf(alloc_error == nil, "Error allocating memory for update channel #%i: %v", index, alloc_error) + } + defer for &task_channel in task_channels { + chan.destroy(&task_channel.channel) + } + + // This buffer is used to batch writes to STDOUT or STDERR, to help reduce + // screen flickering. + batch_buffer: bytes.Buffer + bytes.buffer_init_allocator(&batch_buffer, 0, BATCH_BUFFER_SIZE) + batch_writer := io.to_writer(bytes.buffer_to_stream(&batch_buffer)) + defer bytes.buffer_destroy(&batch_buffer) + + report: Report = --- + report, alloc_error = make_report(internal_tests) + 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 + + 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) + fmt.assertf(alloc_error == nil, "Error allocating memory for task data slots: %v", alloc_error) + defer delete(task_data_slots) + + safe_heap: mem.Mutex_Allocator + mem.mutex_allocator_init(&safe_heap, context.allocator) + safe_heap_allocator := mem.mutex_allocator(&safe_heap) + + // Tests rotate through these allocators as they finish. + task_allocators: []mem.Rollback_Stack = --- + task_allocators, alloc_error = make([]mem.Rollback_Stack, thread_count) + fmt.assertf(alloc_error == nil, "Error allocating memory for task allocators: %v", alloc_error) + defer delete(task_allocators) + + when TRACKING_MEMORY { + task_memory_trackers: []mem.Tracking_Allocator = --- + task_memory_trackers, alloc_error = make([]mem.Tracking_Allocator, thread_count) + fmt.assertf(alloc_error == nil, "Error allocating memory for memory trackers: %v", alloc_error) + defer delete(task_memory_trackers) + } + + #no_bounds_check for i in 0 ..< thread_count { + alloc_error = mem.rollback_stack_init(&task_allocators[i], PER_THREAD_MEMORY, block_allocator = safe_heap_allocator) + fmt.assertf(alloc_error == nil, "Error allocating memory for task allocator #%i: %v", i, alloc_error) + when TRACKING_MEMORY { + mem.tracking_allocator_init(&task_memory_trackers[i], mem.rollback_stack_allocator(&task_allocators[i])) } } - logf(t, "----------------------------------------") - if total_test_count == 0 { - log(t, "NO TESTS RAN") - } else { - logf(t, "%d/%d SUCCESSFUL", total_success_count, total_test_count) + + defer #no_bounds_check for i in 0 ..< thread_count { + when TRACKING_MEMORY { + mem.tracking_allocator_destroy(&task_memory_trackers[i]) + } + mem.rollback_stack_destroy(&task_allocators[i]) } + + task_timeouts: [dynamic]Task_Timeout = --- + task_timeouts, alloc_error = make([dynamic]Task_Timeout, 0, thread_count) + fmt.assertf(alloc_error == nil, "Error allocating memory for task timeouts: %v", alloc_error) + defer delete(task_timeouts) + + failed_test_reason_map: map[int]string = --- + failed_test_reason_map, alloc_error = make(map[int]string, RESERVED_TEST_FAILURES) + fmt.assertf(alloc_error == nil, "Error allocating memory for failed test reasons: %v", alloc_error) + defer delete(failed_test_reason_map) + + log_messages: [dynamic]Log_Message = --- + log_messages, alloc_error = make([dynamic]Log_Message, 0, RESERVED_LOG_MESSAGES) + fmt.assertf(alloc_error == nil, "Error allocating memory for log message queue: %v", alloc_error) + defer delete(log_messages) + + sorted_failed_test_reasons: [dynamic]int = --- + sorted_failed_test_reasons, alloc_error = make([dynamic]int, 0, RESERVED_TEST_FAILURES) + fmt.assertf(alloc_error == nil, "Error allocating memory for sorted failed test reasons: %v", alloc_error) + defer delete(sorted_failed_test_reasons) + + when USE_CLIPBOARD { + clipboard_buffer: bytes.Buffer + bytes.buffer_init_allocator(&clipboard_buffer, 0, CLIPBOARD_BUFFER_SIZE) + defer bytes.buffer_destroy(&clipboard_buffer) + } + + // -- Setup initial tasks. + + // NOTE(Feoramund): This is the allocator that will be used by threads to + // persist log messages past their lifetimes. It has its own variable name + // in the event it needs to be changed from `safe_heap_allocator` without + // digging through the source to divine everywhere it is used for that. + shared_log_allocator := safe_heap_allocator + + context.allocator = safe_heap_allocator + + context.logger = { + procedure = runner_logger_proc, + data = &log_messages, + lowest_level = .Debug if ODIN_DEBUG else .Info, + options = Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}, + } + + run_index: int + + setup_tasks: for &data, task_index in task_data_slots { + setup_next_test: for run_index < total_test_count { + #no_bounds_check it := internal_tests[run_index] + defer run_index += 1 + + data.it = it + #no_bounds_check data.t.channel = chan.as_send(task_channels[task_index].channel) + data.t._log_allocator = shared_log_allocator + data.allocator_index = task_index + + #no_bounds_check when TRACKING_MEMORY { + task_allocator := mem.tracking_allocator(&task_memory_trackers[task_index]) + } else { + task_allocator := mem.rollback_stack_allocator(&task_allocators[task_index]) + } + + thread.pool_add_task(&pool, task_allocator, run_test_task, &data, run_index) + + continue setup_tasks + } + } + + // -- Run tests. + + setup_signal_handler() + + fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE) + + when FANCY_OUTPUT { + redraw_report(stdout, report) + draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count) + } + + when TRACKING_MEMORY { + pkg_log.info("Memory tracking is enabled. Tests will log their memory usage when complete.") + pkg_log.info("< Final Mem/ Total Mem> < Peak Mem> (#Free/Alloc) :: [package.test_name]") + } + + start_time := time.now() + + thread.pool_start(&pool) + main_loop: for !thread.pool_is_empty(&pool) { + + cycle_pool: for task in thread.pool_pop_done(&pool) { + data := cast(^Task_Data)(task.data) + + when TRACKING_MEMORY { + #no_bounds_check tracker := &task_memory_trackers[data.allocator_index] + + write_memory_report(batch_writer, tracker, data.it.pkg, data.it.name) + + pkg_log.info(bytes.buffer_to_string(&batch_buffer)) + bytes.buffer_reset(&batch_buffer) + + mem.tracking_allocator_reset(tracker) + } + + free_all(task.allocator) + + if run_index < total_test_count { + #no_bounds_check it := internal_tests[run_index] + defer run_index += 1 + + data.it = it + data.t.error_count = 0 + + thread.pool_add_task(&pool, task.allocator, run_test_task, data, run_index) + } + } + + handle_events: for &task_channel in task_channels { + for ev in chan.try_recv(task_channel.channel) { + switch event in ev { + case Event_New_Test: + task_channel.test_index = event.test_index + + case Event_State_Change: + #no_bounds_check report.all_test_states[task_channel.test_index] = event.new_state + + #no_bounds_check it := internal_tests[task_channel.test_index] + #no_bounds_check pkg := report.packages_by_name[it.pkg] + + #partial switch event.new_state { + case .Failed: + if task_channel.test_index not_in failed_test_reason_map { + failed_test_reason_map[task_channel.test_index] = ERROR_STRING_UNKNOWN + } + total_failure_count += 1 + total_done_count += 1 + case .Successful: + total_success_count += 1 + total_done_count += 1 + } + + when ODIN_DEBUG { + pkg_log.debugf("Test #%i %s.%s changed state to %v.", task_channel.test_index, it.pkg, it.name, event.new_state) + } + + pkg.last_change_state = event.new_state + pkg.last_change_name = it.name + pkg.frame_ready = false + + case Event_Set_Fail_Timeout: + _, alloc_error = append(&task_timeouts, Task_Timeout { + test_index = task_channel.test_index, + at_time = event.at_time, + location = event.location, + }) + fmt.assertf(alloc_error == nil, "Error appending to task timeouts: %v", alloc_error) + + case Event_Log_Message: + _, alloc_error = append(&log_messages, Log_Message { + level = event.level, + text = event.formatted_text, + time = event.time, + allocator = shared_log_allocator, + }) + fmt.assertf(alloc_error == nil, "Error appending to log messages: %v", alloc_error) + + if event.level >= .Error { + // Save the message for the final summary. + if old_error, ok := failed_test_reason_map[task_channel.test_index]; ok { + safe_delete_string(old_error, shared_log_allocator) + } + failed_test_reason_map[task_channel.test_index] = event.text + } else { + delete(event.text, shared_log_allocator) + } + } + } + } + + check_timeouts: for i := len(task_timeouts) - 1; i >= 0; i -= 1 { + #no_bounds_check timeout := &task_timeouts[i] + + if time.since(timeout.at_time) < 0 { + continue check_timeouts + } + + defer unordered_remove(&task_timeouts, i) + + #no_bounds_check if report.all_test_states[timeout.test_index] > .Running { + continue check_timeouts + } + + if !thread.pool_stop_task(&pool, timeout.test_index) { + // The task may have stopped a split second after we started + // checking, but we haven't handled the new state yet. + continue check_timeouts + } + + #no_bounds_check report.all_test_states[timeout.test_index] = .Failed + #no_bounds_check it := internal_tests[timeout.test_index] + #no_bounds_check pkg := report.packages_by_name[it.pkg] + pkg.frame_ready = false + + if old_error, ok := failed_test_reason_map[timeout.test_index]; ok { + safe_delete_string(old_error, shared_log_allocator) + } + failed_test_reason_map[timeout.test_index] = ERROR_STRING_TIMEOUT + total_failure_count += 1 + total_done_count += 1 + + now := time.now() + _, alloc_error = append(&log_messages, Log_Message { + level = .Error, + text = format_log_text(.Error, ERROR_STRING_TIMEOUT, Default_Test_Logger_Opts, timeout.location, now), + time = now, + allocator = context.allocator, + }) + fmt.assertf(alloc_error == nil, "Error appending to log messages: %v", alloc_error) + + find_task_data: for &data in task_data_slots { + if data.it.pkg == it.pkg && data.it.name == it.name { + end_t(&data.t) + break find_task_data + } + } + } + + if should_abort() { + fmt.wprintln(stderr, "\nCaught interrupt signal. Stopping all tests.") + thread.pool_shutdown(&pool) + break main_loop + } + + // -- Redraw. + + when FANCY_OUTPUT { + if len(log_messages) == 0 && !needs_to_redraw(report) { + continue main_loop + } + + fmt.wprintf(stdout, ansi_redraw_string, total_done_count, total_test_count) + } else { + if total_done_count != last_done_count { + fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count) + last_done_count = total_done_count + } + + if len(log_messages) == 0 { + continue main_loop + } + } + + // Because each thread has its own messenger channel, log messages + // arrive in chunks that are in-order, but when they're merged with the + // logs from other threads, they become out-of-order. + slice.stable_sort_by(log_messages[:], proc(a, b: Log_Message) -> bool { + return time.diff(a.time, b.time) > 0 + }) + + for message in log_messages { + fmt.wprintln(batch_writer, message.text) + delete(message.text, message.allocator) + } + + fmt.wprint(stderr, bytes.buffer_to_string(&batch_buffer)) + clear(&log_messages) + bytes.buffer_reset(&batch_buffer) + + when FANCY_OUTPUT { + 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)) + bytes.buffer_reset(&batch_buffer) + } + } + + // -- All tests are complete, or the runner has been interrupted. + + thread.pool_join(&pool) + + finished_in := time.since(start_time) + + fmt.wprintf(batch_writer, + "\nFinished %i test%s in %v.", + total_done_count, + "" if total_done_count == 1 else "s", + finished_in) + + if total_done_count != total_test_count { + not_run_count := total_test_count - total_done_count + fmt.wprintf(batch_writer, + " " + SGR_READY + "%i" + SGR_RESET + " %s left undone.", + not_run_count, + "test was" if not_run_count == 1 else "tests were") + } + + if total_success_count == total_test_count { + fmt.wprintfln(batch_writer, + " %s " + SGR_SUCCESS + "successful." + SGR_RESET, + "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 { + fmt.wprintfln(batch_writer, + " %s " + SGR_FAILED + "failed." + SGR_RESET, + "The test" if total_test_count == 1 else "All tests") + } else { + fmt.wprintfln(batch_writer, + " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed.", + total_failure_count, + "" if total_failure_count == 1 else "s") + } + + for test_index in failed_test_reason_map { + _, alloc_error = append(&sorted_failed_test_reasons, test_index) + fmt.assertf(alloc_error == nil, "Error appending to sorted failed test reasons: %v", alloc_error) + } + + slice.sort(sorted_failed_test_reasons[:]) + + for test_index in sorted_failed_test_reasons { + #no_bounds_check last_error := failed_test_reason_map[test_index] + #no_bounds_check it := internal_tests[test_index] + pkg_and_name := fmt.tprintf("%s.%s", it.pkg, it.name) + fmt.wprintfln(batch_writer, " - %- *[1]s\t%s", + pkg_and_name, + report.pkg_column_len + report.test_column_len, + last_error) + safe_delete_string(last_error, shared_log_allocator) + } + + if total_success_count > 0 { + when USE_CLIPBOARD { + clipboard_writer := io.to_writer(bytes.buffer_to_stream(&clipboard_buffer)) + fmt.wprint(clipboard_writer, "-define:test_select=") + for test_index in sorted_failed_test_reasons { + #no_bounds_check it := internal_tests[test_index] + fmt.wprintf(clipboard_writer, "%s.%s,", it.pkg, it.name) + } + + encoded_names := base64_encode(bytes.buffer_to_bytes(&clipboard_buffer), allocator = context.temp_allocator) + + fmt.wprintf(batch_writer, + ansi.OSC + ansi.CLIPBOARD + ";c;%s" + ansi.ST + + "\nThe name%s of the failed test%s been copied to your clipboard.", + encoded_names, + "" if total_failure_count == 1 else "s", + " has" if total_failure_count == 1 else "s have") + } else { + fmt.wprintf(batch_writer, "\nTo run only the failed test%s, use:\n\t-define:test_select=", + "" if total_failure_count == 1 else "s") + for test_index in sorted_failed_test_reasons { + #no_bounds_check it := internal_tests[test_index] + fmt.wprintf(batch_writer, "%s.%s,", it.pkg, it.name) + } + } + + fmt.wprintln(batch_writer) + } + } + + fmt.wprint(batch_writer, ansi.CSI + ansi.DECTCEM_SHOW) + + fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer)) + return total_success_count == total_test_count } diff --git a/core/testing/signal_handler.odin b/core/testing/signal_handler.odin new file mode 100644 index 000000000..2dd0ff192 --- /dev/null +++ b/core/testing/signal_handler.odin @@ -0,0 +1,19 @@ +//+private +//+build windows, linux, darwin, freebsd, openbsd, netbsd, haiku +package testing + +import "base:intrinsics" +import "core:c/libc" + +@(private="file") +abort_flag: libc.sig_atomic_t + +setup_signal_handler :: proc() { + libc.signal(libc.SIGINT, proc "c" (sig: libc.int) { + intrinsics.atomic_add(&abort_flag, 1) + }) +} + +should_abort :: proc() -> bool { + return intrinsics.atomic_load(&abort_flag) > 0 +} diff --git a/core/testing/signal_handler_other.odin b/core/testing/signal_handler_other.odin new file mode 100644 index 000000000..b2e2ea906 --- /dev/null +++ b/core/testing/signal_handler_other.odin @@ -0,0 +1,11 @@ +//+private +//+build js, wasi, freestanding +package testing + +setup_signal_handler :: proc() { + // Do nothing. +} + +should_abort :: proc() -> bool { + return false +} diff --git a/core/testing/testing.odin b/core/testing/testing.odin index a8c5ffa48..30109304d 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -1,10 +1,11 @@ package testing -import "core:fmt" -import "core:io" -import "core:time" import "base:intrinsics" +import "base:runtime" +import pkg_log "core:log" import "core:reflect" +import "core:sync/chan" +import "core:time" _ :: reflect // alias reflect to nothing to force visibility for -vet @@ -22,44 +23,45 @@ Internal_Test :: struct { Internal_Cleanup :: struct { procedure: proc(rawptr), user_data: rawptr, + ctx: runtime.Context, } T :: struct { error_count: int, - w: io.Writer, + channel: Update_Channel_Sender, cleanups: [dynamic]Internal_Cleanup, + // This allocator is shared between the test runner and its threads for + // cloning log strings, so they can outlive the lifetime of individual + // tests during channel transmission. + _log_allocator: runtime.Allocator, + _fail_now: proc() -> !, } +@(deprecated="prefer `log.error`") error :: proc(t: ^T, args: ..any, loc := #caller_location) { - fmt.wprintf(t.w, "%v: ", loc) - fmt.wprintln(t.w, ..args) - t.error_count += 1 + pkg_log.error(..args, location = loc) } +@(deprecated="prefer `log.errorf`") errorf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { - fmt.wprintf(t.w, "%v: ", loc) - fmt.wprintf(t.w, format, ..args) - fmt.wprintln(t.w) - t.error_count += 1 + pkg_log.errorf(format, ..args, location = loc) } fail :: proc(t: ^T, loc := #caller_location) { - error(t, "FAIL", loc=loc) - t.error_count += 1 + pkg_log.error("FAIL", location=loc) } fail_now :: proc(t: ^T, msg := "", loc := #caller_location) { if msg != "" { - error(t, "FAIL:", msg, loc=loc) + pkg_log.error("FAIL:", msg, location=loc) } else { - error(t, "FAIL", loc=loc) + pkg_log.error("FAIL", location=loc) } - t.error_count += 1 if t._fail_now != nil { t._fail_now() } @@ -69,32 +71,34 @@ failed :: proc(t: ^T) -> bool { return t.error_count != 0 } +@(deprecated="prefer `log.info`") log :: proc(t: ^T, args: ..any, loc := #caller_location) { - fmt.wprintln(t.w, ..args) + pkg_log.info(..args, location = loc) } +@(deprecated="prefer `log.infof`") logf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) { - fmt.wprintf(t.w, format, ..args) - fmt.wprintln(t.w) + pkg_log.infof(format, ..args, location = loc) } -// cleanup registers a procedure and user_data, which will be called when the test, and all its subtests, complete -// cleanup procedures will be called in LIFO (last added, first called) order. +// cleanup registers a procedure and user_data, which will be called when the test, and all its subtests, complete. +// Cleanup procedures will be called in LIFO (last added, first called) order. +// Each procedure will use a copy of the context at the time of registering. cleanup :: proc(t: ^T, procedure: proc(rawptr), user_data: rawptr) { - append(&t.cleanups, Internal_Cleanup{procedure, user_data}) + append(&t.cleanups, Internal_Cleanup{procedure, user_data, context}) } expect :: proc(t: ^T, ok: bool, msg: string = "", loc := #caller_location) -> bool { if !ok { - error(t, msg, loc=loc) + pkg_log.error(msg, location=loc) } return ok } expectf :: proc(t: ^T, ok: bool, format: string, args: ..any, loc := #caller_location) -> bool { if !ok { - errorf(t, format, ..args, loc=loc) + pkg_log.errorf(format, ..args, location=loc) } return ok } @@ -102,12 +106,15 @@ expectf :: proc(t: ^T, ok: bool, format: string, args: ..any, loc := #caller_loc expect_value :: proc(t: ^T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) if !ok { - errorf(t, "expected %v, got %v", expected, value, loc=loc) + pkg_log.errorf("expected %v, got %v", expected, value, location=loc) } return ok } set_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) { - _fail_timeout(t, duration, loc) + chan.send(t.channel, Event_Set_Fail_Timeout { + at_time = time.time_add(time.now(), duration), + location = loc, + }) } From d03024088aec0238ed236dd02987f8f8e8734958 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 20:04:49 -0400 Subject: [PATCH 042/270] Remove unneeded code --- core/testing/runner_other.odin | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/testing/runner_other.odin b/core/testing/runner_other.odin index f3271d209..29f338828 100644 --- a/core/testing/runner_other.odin +++ b/core/testing/runner_other.odin @@ -2,13 +2,7 @@ //+build !windows package testing -import "core:time" - run_internal_test :: proc(t: ^T, it: Internal_Test) { // TODO(bill): Catch panics on other platforms it.p(t) } - -_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) { - -} \ No newline at end of file From 87ea4a2652e99f44a286fe6328659fe7e6e5f7b9 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 20:05:43 -0400 Subject: [PATCH 043/270] Temporarily disable Windows-specific test runner I do not have a Windows machine to test the refactored test runner, and I am unsure if it would even run correctly on Windows without this disabled. --- core/testing/runner_windows.odin | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin index 15264355b..23dab4f31 100644 --- a/core/testing/runner_windows.odin +++ b/core/testing/runner_windows.odin @@ -2,6 +2,12 @@ //+build windows package testing +run_internal_test :: proc(t: ^T, it: Internal_Test) { + it.p(t) +} + +// Temporarily disabled during multi-threaded test runner refactor. +/* import win32 "core:sys/windows" import "base:runtime" import "base:intrinsics" @@ -233,3 +239,4 @@ run_internal_test :: proc(t: ^T, it: Internal_Test) { return } +*/ From 852f694bee4469c009cb02a850e02fe1c7165477 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 27 May 2024 20:42:35 -0400 Subject: [PATCH 044/270] Get tests passing again `T` no longer has a writer assigned to it. `test_core_cbor.odin` has global state and is run with `odin test`, so I've set it to use only one thread. --- tests/core/build.bat | 2 +- tests/core/compress/test_core_compress.odin | 8 ++++---- tests/core/text/match/test_core_text_match.odin | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/core/build.bat b/tests/core/build.bat index 7871e52e2..4755e0a44 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -25,7 +25,7 @@ rem %PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe | %PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b %PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b %PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b -%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe || exit /b +%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe -define:test_threads=1 -define:test_fancy=false || exit /b %PATH_TO_ODIN% run encoding/hex %COMMON% -out:test_hex.exe || exit /b %PATH_TO_ODIN% run encoding/base64 %COMMON% -out:test_base64.exe || exit /b diff --git a/tests/core/compress/test_core_compress.odin b/tests/core/compress/test_core_compress.odin index ac7555e9a..db6f3d06b 100644 --- a/tests/core/compress/test_core_compress.odin +++ b/tests/core/compress/test_core_compress.odin @@ -21,7 +21,7 @@ import "core:fmt" import "core:mem" import "core:os" -import "core:io" +// import "core:io" TEST_count := 0 TEST_fail := 0 @@ -45,8 +45,8 @@ when ODIN_TEST { } main :: proc() { - w := io.to_writer(os.stream_from_handle(os.stdout)) - t := testing.T{w=w} + // w := io.to_writer(os.stream_from_handle(os.stdout)) + t := testing.T{}//{w=w} zlib_test(&t) gzip_test(&t) shoco_test(&t) @@ -195,4 +195,4 @@ shoco_test :: proc(t: ^testing.T) { size, err = shoco.decompress(v.compressed[:v.short_sentinel], buffer[:]) expect(t, err == .Stream_Too_Short, "Expected `decompress` to return `Stream_Too_Short` because there was no more data after non-ASCII sentinel.") } -} \ No newline at end of file +} diff --git a/tests/core/text/match/test_core_text_match.odin b/tests/core/text/match/test_core_text_match.odin index b72190f78..eadd17433 100644 --- a/tests/core/text/match/test_core_text_match.odin +++ b/tests/core/text/match/test_core_text_match.odin @@ -14,7 +14,7 @@ failed :: proc(t: ^testing.T, ok: bool, loc := #caller_location) -> bool { TEST_count += 1 if !ok { - fmt.wprintf(t.w, "%v: ", loc) + fmt.printf(/*t.w,*/ "%v: ", loc) t.error_count += 1 TEST_fail += 1 } @@ -25,7 +25,7 @@ failed :: proc(t: ^testing.T, ok: bool, loc := #caller_location) -> bool { expect :: testing.expect logf :: proc(t: ^testing.T, format: string, args: ..any) { - fmt.wprintf(t.w, format, ..args) + fmt.printf(/*t.w,*/ format, ..args) } // find correct byte offsets @@ -380,7 +380,7 @@ main :: proc() { t: testing.T stream := os.stream_from_handle(os.stdout) w := io.to_writer(stream) - t.w = w + // t.w = w test_find(&t) test_match(&t) @@ -396,4 +396,4 @@ main :: proc() { if TEST_fail > 0 { os.exit(1) } -} \ No newline at end of file +} From eb3d6d7d75cf8b2de1bfddf03820ee60ce595b05 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 May 2024 17:16:24 +0200 Subject: [PATCH 045/270] Update `core:image` tests to use new runner. --- tests/core/Makefile | 4 +- tests/core/build.bat | 4 +- tests/core/image/build.bat | 6 +- tests/core/image/test_core_image.odin | 301 ++++++++++---------------- 4 files changed, 120 insertions(+), 195 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 9026ed3d9..a740b3d7c 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,6 +1,6 @@ ODIN=../../odin PYTHON=$(shell which python3) -COMMON=-vet -strict-style +COMMON=-no-bounds-check -vet -strict-style -define:test_track_memory=true COLLECTION=-collection:tests=.. all: all_bsd \ @@ -34,7 +34,7 @@ download_test_assets: $(PYTHON) download_assets.py image_test: - $(ODIN) run image $(COMMON) -out:test_core_image + $(ODIN) test image $(COMMON) -define:test_progress_width=12 -out:test_core_image compress_test: $(ODIN) run compress $(COMMON) -out:test_core_compress diff --git a/tests/core/build.bat b/tests/core/build.bat index 4755e0a44..4d981be11 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -1,5 +1,5 @@ @echo off -set COMMON=-no-bounds-check -vet -strict-style +set COMMON=-no-bounds-check -vet -strict-style -define:test_track_memory=true set COLLECTION=-collection:tests=.. set PATH_TO_ODIN==..\..\odin python3 download_assets.py @@ -42,7 +42,7 @@ echo --- echo --- echo Running core:image tests echo --- -%PATH_TO_ODIN% run image %COMMON% -out:test_core_image.exe || exit /b +%PATH_TO_ODIN% test image %COMMON% -define:test_progress_width=12 -out:test_core_image.exe || exit /b echo --- echo Running core:math tests diff --git a/tests/core/image/build.bat b/tests/core/image/build.bat index 03ee6b9a5..35d1c64e9 100644 --- a/tests/core/image/build.bat +++ b/tests/core/image/build.bat @@ -1,4 +1,2 @@ -@echo off -pushd .. -odin run image -popd \ No newline at end of file +@echo off +odin test . -define:test_track_memory=true -define:test_progress_width=12 -vet -strict-style \ No newline at end of file diff --git a/tests/core/image/test_core_image.odin b/tests/core/image/test_core_image.odin index ae92ca617..e3bef6c9d 100644 --- a/tests/core/image/test_core_image.odin +++ b/tests/core/image/test_core_image.odin @@ -20,49 +20,15 @@ import "core:image/tga" import "core:bytes" import "core:hash" -import "core:fmt" import "core:strings" - import "core:mem" -import "core:os" import "core:time" - import "base:runtime" -TEST_SUITE_PATH :: "assets/PNG" +TEST_SUITE_PATH :: "assets/PNG" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} I_Error :: image.Error -main :: proc() { - t := testing.T{} - png_test(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - PNG_Test :: struct { file: string, tests: []struct { @@ -72,7 +38,6 @@ PNG_Test :: struct { hash: u32, }, } - Default :: image.Options{} Alpha_Add :: image.Options{.alpha_add_if_missing} Premul_Drop :: image.Options{.alpha_premultiply, .alpha_drop_if_present} @@ -82,7 +47,7 @@ Blend_BG_Keep :: image.Options{.blend_background, .alpha_add_if_missing} Return_Metadata :: image.Options{.return_metadata} No_Channel_Expansion :: image.Options{.do_not_expand_channels, .return_metadata} -PNG_Dims :: struct { +PNG_Dims :: struct { width: int, height: int, channels: int, @@ -1430,38 +1395,66 @@ Expected_Text := map[string]map[string]png.Text { } @test -png_test :: proc(t: ^testing.T) { - - total_tests := 0 - total_expected := 235 - - PNG_Suites := [][]PNG_Test{ - Basic_PNG_Tests, - Interlaced_PNG_Tests, - Odd_Sized_PNG_Tests, - PNG_bKGD_Tests, - PNG_tRNS_Tests, - PNG_Filter_Tests, - PNG_Varied_IDAT_Tests, - PNG_ZLIB_Levels_Tests, - PNG_sPAL_Tests, - PNG_Ancillary_Tests, - Corrupt_PNG_Tests, - - No_Postprocesing_Tests, - - } - - for suite in PNG_Suites { - total_tests += run_png_suite(t, suite) - } - - error := fmt.tprintf("Expected %v PNG tests, %v ran.", total_expected, total_tests) - expect(t, total_tests == total_expected, error) +png_test_basic :: proc(t: ^testing.T) { + run_png_suite(t, Basic_PNG_Tests) } -run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { +@test +png_test_interlaced :: proc(t: ^testing.T) { + run_png_suite(t, Interlaced_PNG_Tests) +} +@test +png_test_odd_sized :: proc(t: ^testing.T) { + run_png_suite(t, Odd_Sized_PNG_Tests) +} + +@test +png_test_bKGD :: proc(t: ^testing.T) { + run_png_suite(t, PNG_bKGD_Tests) +} + +@test +png_test_tRNS :: proc(t: ^testing.T) { + run_png_suite(t, PNG_tRNS_Tests) +} + +@test +png_test_sPAL :: proc(t: ^testing.T) { + run_png_suite(t, PNG_sPAL_Tests) +} + +@test +png_test_filter :: proc(t: ^testing.T) { + run_png_suite(t, PNG_Filter_Tests) +} + +@test +png_test_varied_idat :: proc(t: ^testing.T) { + run_png_suite(t, PNG_Varied_IDAT_Tests) +} + +@test +png_test_zlib_levels :: proc(t: ^testing.T) { + run_png_suite(t, PNG_ZLIB_Levels_Tests) +} + +@test +png_test_ancillary :: proc(t: ^testing.T) { + run_png_suite(t, PNG_Ancillary_Tests) +} + +@test +png_test_corrupt :: proc(t: ^testing.T) { + run_png_suite(t, Corrupt_PNG_Tests) +} + +@test +png_test_no_postproc :: proc(t: ^testing.T) { + run_png_suite(t, No_Postprocesing_Tests) +} + +run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { context = runtime.default_context() for file in suite { @@ -1472,9 +1465,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { count := 0 for test in file.tests { - count += 1 - subtotal += 1 - passed := false + count += 1 track: mem.Tracking_Allocator mem.tracking_allocator_init(&track, context.allocator) @@ -1482,29 +1473,21 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { img, err = png.load(test_file, test.options) - error := fmt.tprintf("%v failed with %v.", test_file, err) - - passed = (test.expected_error == nil && err == nil) || (test.expected_error == err) - - expect(t, passed, error) + passed := (test.expected_error == nil && err == nil) || (test.expected_error == err) + testing.expectf(t, passed, "%v failed with %v.", test_file, err) if err == nil { // No point in running the other tests if it didn't load. pixels := bytes.buffer_to_bytes(&img.pixels) // This struct compare fails at -opt:2 if PNG_Dims is not #packed. - - dims := PNG_Dims{img.width, img.height, img.channels, img.depth} - error = fmt.tprintf("%v has %v, expected: %v.", file.file, dims, test.dims) - + dims := PNG_Dims{img.width, img.height, img.channels, img.depth} dims_pass := test.dims == dims - expect(t, dims_pass, error) - + testing.expectf(t, dims_pass, "%v has %v, expected: %v.", file.file, dims, test.dims) passed &= dims_pass - png_hash := hash.crc32(pixels) - error = fmt.tprintf("%v test %v hash is %08x, expected %08x with %v.", file.file, count, png_hash, test.hash, test.options) - expect(t, test.hash == png_hash, error) + png_hash := hash.crc32(pixels) + testing.expectf(t, test.hash == png_hash, "%v test %v hash is %08x, expected %08x with %v.", file.file, count, png_hash, test.hash, test.options) passed &= test.hash == png_hash @@ -1515,19 +1498,16 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { defer bytes.buffer_destroy(&qoi_buffer) qoi_save_err := qoi.save(&qoi_buffer, img) - error = fmt.tprintf("%v test %v QOI save failed with %v.", file.file, count, qoi_save_err) - expect(t, qoi_save_err == nil, error) + testing.expectf(t, qoi_save_err == nil, "%v test %v QOI save failed with %v.", file.file, count, qoi_save_err) if qoi_save_err == nil { qoi_img, qoi_load_err := qoi.load(qoi_buffer.buf[:]) defer qoi.destroy(qoi_img) - error = fmt.tprintf("%v test %v QOI load failed with %v.", file.file, count, qoi_load_err) - expect(t, qoi_load_err == nil, error) + testing.expectf(t, qoi_load_err == nil, "%v test %v QOI load failed with %v.", file.file, count, qoi_load_err) qoi_hash := hash.crc32(qoi_img.pixels.buf[:]) - error = fmt.tprintf("%v test %v QOI load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, qoi_hash, png_hash, test.options) - expect(t, qoi_hash == png_hash, error) + testing.expectf(t, qoi_hash == png_hash, "%v test %v QOI load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, qoi_hash, png_hash, test.options) } } @@ -1537,19 +1517,15 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { defer bytes.buffer_destroy(&tga_buffer) tga_save_err := tga.save(&tga_buffer, img) - error = fmt.tprintf("%v test %v TGA save failed with %v.", file.file, count, tga_save_err) - expect(t, tga_save_err == nil, error) - + testing.expectf(t, tga_save_err == nil, "%v test %v TGA save failed with %v.", file.file, count, tga_save_err) if tga_save_err == nil { tga_img, tga_load_err := tga.load(tga_buffer.buf[:]) defer tga.destroy(tga_img) - error = fmt.tprintf("%v test %v TGA load failed with %v.", file.file, count, tga_load_err) - expect(t, tga_load_err == nil, error) + testing.expectf(t, tga_load_err == nil, "%v test %v TGA load failed with %v.", file.file, count, tga_load_err) tga_hash := hash.crc32(tga_img.pixels.buf[:]) - error = fmt.tprintf("%v test %v TGA load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, tga_hash, png_hash, test.options) - expect(t, tga_hash == png_hash, error) + testing.expectf(t, tga_hash == png_hash, "%v test %v TGA load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, tga_hash, png_hash, test.options) } } @@ -1558,22 +1534,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { pbm_buf, pbm_save_err := pbm.save_to_buffer(img) defer delete(pbm_buf) - error = fmt.tprintf("%v test %v PBM save failed with %v.", file.file, count, pbm_save_err) - expect(t, pbm_save_err == nil, error) + testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v.", file.file, count, pbm_save_err) if pbm_save_err == nil { // Try to load it again. pbm_img, pbm_load_err := pbm.load(pbm_buf) defer pbm.destroy(pbm_img) - error = fmt.tprintf("%v test %v PBM load failed with %v.", file.file, count, pbm_load_err) - expect(t, pbm_load_err == nil, error) + testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v.", file.file, count, pbm_load_err) if pbm_load_err == nil { pbm_hash := hash.crc32(pbm_img.pixels.buf[:]) - - error = fmt.tprintf("%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options) - expect(t, pbm_hash == png_hash, error) + testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options) } } } @@ -1587,22 +1559,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { pbm_buf, pbm_save_err := pbm.save_to_buffer(img, pbm_info) defer delete(pbm_buf) - error = fmt.tprintf("%v test %v PBM save failed with %v.", file.file, count, pbm_save_err) - expect(t, pbm_save_err == nil, error) + testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v.", file.file, count, pbm_save_err) if pbm_save_err == nil { // Try to load it again. pbm_img, pbm_load_err := pbm.load(pbm_buf) defer pbm.destroy(pbm_img) - error = fmt.tprintf("%v test %v PBM load failed with %v.", file.file, count, pbm_load_err) - expect(t, pbm_load_err == nil, error) + testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v.", file.file, count, pbm_load_err) if pbm_load_err == nil { pbm_hash := hash.crc32(pbm_img.pixels.buf[:]) - - error = fmt.tprintf("%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options) - expect(t, pbm_hash == png_hash, error) + testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options) } } } @@ -1655,21 +1623,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { float_pbm_buf, float_pbm_save_err := pbm.save_to_buffer(float_img, pbm_info) defer delete(float_pbm_buf) - error = fmt.tprintf("%v test %v save as PFM failed with %v", file.file, count, float_pbm_save_err) - expect(t, float_pbm_save_err == nil, error) + testing.expectf(t, float_pbm_save_err == nil, "%v test %v save as PFM failed with %v", file.file, count, float_pbm_save_err) if float_pbm_save_err == nil { // Load float image and compare. float_pbm_img, float_pbm_load_err := pbm.load(float_pbm_buf) defer pbm.destroy(float_pbm_img) - error = fmt.tprintf("%v test %v PFM load failed with %v", file.file, count, float_pbm_load_err) - expect(t, float_pbm_load_err == nil, error) + testing.expectf(t, float_pbm_load_err == nil, "%v test %v PFM load failed with %v", file.file, count, float_pbm_load_err) load_float := mem.slice_data_cast([]f32, float_pbm_img.pixels.buf[:]) - error = fmt.tprintf("%v test %v PFM load returned %v floats, expected %v", file.file, count, len(load_float), len(orig_float)) - expect(t, len(load_float) == len(orig_float), error) + testing.expectf(t, len(load_float) == len(orig_float), "%v test %v PFM load returned %v floats, expected %v", file.file, count, len(load_float), len(orig_float)) // Compare floats equal := true @@ -1679,15 +1644,13 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { break } } - error = fmt.tprintf("%v test %v PFM loaded floats to match", file.file, count) - expect(t, equal, error) + testing.expectf(t, equal, "%v test %v PFM loaded floats to match", file.file, count) } } } } if .return_metadata in test.options { - if v, ok := img.metadata.(^image.PNG_Info); ok { for c in v.chunks { #partial switch(c.header.type) { @@ -1696,8 +1659,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { case "pp0n2c16", "pp0n6a08": gamma, gamma_ok := png.gamma(c) expected_gamma := f32(1.0) - error = fmt.tprintf("%v test %v gAMA is %v, expected %v.", file.file, count, gamma, expected_gamma) - expect(t, gamma == expected_gamma && gamma_ok, error) + testing.expectf(t, gamma == expected_gamma && gamma_ok, "%v test %v gAMA is %v, expected %v.", file.file, count, gamma, expected_gamma) } case .PLTE: switch(file.file) { @@ -1705,8 +1667,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { plte, plte_ok := png.plte(c) expected_plte_len := u16(216) - error = fmt.tprintf("%v test %v PLTE length is %v, expected %v.", file.file, count, plte.used, expected_plte_len) - expect(t, expected_plte_len == plte.used && plte_ok, error) + testing.expectf(t, expected_plte_len == plte.used && plte_ok, "%v test %v PLTE length is %v, expected %v.", file.file, count, plte.used, expected_plte_len) } case .sPLT: switch(file.file) { @@ -1714,12 +1675,10 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { splt, splt_ok := png.splt(c) expected_splt_len := u16(216) - error = fmt.tprintf("%v test %v sPLT length is %v, expected %v.", file.file, count, splt.used, expected_splt_len) - expect(t, expected_splt_len == splt.used && splt_ok, error) + testing.expectf(t, expected_splt_len == splt.used && splt_ok, "%v test %v sPLT length is %v, expected %v.", file.file, count, splt.used, expected_splt_len) expected_splt_name := "six-cube" - error = fmt.tprintf("%v test %v sPLT name is %v, expected %v.", file.file, count, splt.name, expected_splt_name) - expect(t, expected_splt_name == splt.name && splt_ok, error) + testing.expectf(t, expected_splt_name == splt.name && splt_ok, "%v test %v sPLT name is %v, expected %v.", file.file, count, splt.name, expected_splt_name) png.splt_destroy(splt) } @@ -1733,48 +1692,37 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { g = png.CIE_1931{x = 0.3000, y = 0.6000}, b = png.CIE_1931{x = 0.1500, y = 0.0600}, } - error = fmt.tprintf("%v test %v cHRM is %v, expected %v.", file.file, count, chrm, expected_chrm) - expect(t, expected_chrm == chrm && chrm_ok, error) + testing.expectf(t, expected_chrm == chrm && chrm_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, chrm, expected_chrm) } case .pHYs: phys, phys_ok := png.phys(c) - phys_err := "%v test %v cHRM is %v, expected %v." switch (file.file) { case "cdfn2c08": expected_phys := png.pHYs{ppu_x = 1, ppu_y = 4, unit = .Unknown} - error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys && phys_ok, error) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys) case "cdhn2c08": expected_phys := png.pHYs{ppu_x = 4, ppu_y = 1, unit = .Unknown} - error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys && phys_ok, error) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys) case "cdsn2c08": expected_phys := png.pHYs{ppu_x = 1, ppu_y = 1, unit = .Unknown} - error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys && phys_ok, error) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys) case "cdun2c08": expected_phys := png.pHYs{ppu_x = 1000, ppu_y = 1000, unit = .Meter} - error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys && phys_ok, error) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys) } case .hIST: hist, hist_ok := png.hist(c) - hist_err := "%v test %v hIST has %v entries, expected %v." switch (file.file) { case "ch1n3p04": - error = fmt.tprintf(hist_err, file.file, count, hist.used, 15) - expect(t, hist.used == 15 && hist_ok, error) + testing.expectf(t, hist.used == 15 && hist_ok, "%v test %v hIST has %v entries, expected %v.", file.file, count, hist.used, 15) case "ch2n3p08": - error = fmt.tprintf(hist_err, file.file, count, hist.used, 256) - expect(t, hist.used == 256 && hist_ok, error) + testing.expectf(t, hist.used == 256 && hist_ok, "%v test %v hIST has %v entries, expected %v.", file.file, count, hist.used, 256) } case .tIME: png_time, png_time_ok := png.time(c) - time_err := "%v test %v tIME was %v, expected %v." expected_time: png.tIME core_time, core_time_ok := png.core_time(c) - time_core_err := "%v test %v tIME->core:time is %v, expected %v." expected_core: time.Time switch(file.file) { @@ -1789,14 +1737,10 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { expected_core = time.Time{_nsec = 946684799000000000} } - error = fmt.tprintf(time_err, file.file, count, png_time, expected_time) - expect(t, png_time == expected_time && png_time_ok, error) - - error = fmt.tprintf(time_core_err, file.file, count, core_time, expected_core) - expect(t, core_time == expected_core && core_time_ok, error) + testing.expectf(t, png_time == expected_time && png_time_ok, "%v test %v tIME was %v, expected %v.", file.file, count, png_time, expected_time) + testing.expectf(t, core_time == expected_core && core_time_ok, "%v test %v tIME->core:time is %v, expected %v.", file.file, count, core_time, expected_core) case .sBIT: sbit, sbit_ok := png.sbit(c) - sbit_err := "%v test %v sBIT was %v, expected %v." expected_sbit: [4]u8 switch (file.file) { @@ -1815,8 +1759,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { case "cdfn2c08", "cdhn2c08", "cdsn2c08", "cdun2c08", "ch1n3p04", "basn3p04": expected_sbit = [4]u8{ 4, 4, 4, 0} } - error = fmt.tprintf(sbit_err, file.file, count, sbit, expected_sbit) - expect(t, sbit == expected_sbit && sbit_ok, error) + testing.expectf(t, sbit == expected_sbit && sbit_ok, "%v test %v sBIT was %v, expected %v.", file.file, count, sbit, expected_sbit) case .tEXt, .zTXt: text, text_ok := png.text(c) defer png.text_destroy(text) @@ -1828,8 +1771,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test_text := Expected_Text[file.file][text.keyword].text - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text.text, test_text) - expect(t, text.text == test_text && text_ok, error) + testing.expectf(t, text.text == test_text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text.text, test_text) } } } @@ -1842,74 +1784,59 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - expect(t, text.language == test.language && text_ok, error) - expect(t, text.keyword_localized == test.keyword_localized && text_ok, error) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) } } case "ctfn0g04": // international UTF-8, finnish if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - expect(t, text.text == test.text && text_ok, error) - expect(t, text.language == test.language && text_ok, error) - expect(t, text.keyword_localized == test.keyword_localized && text_ok, error) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) } } case "ctgn0g04": // international UTF-8, greek if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - expect(t, text.text == test.text && text_ok, error) - expect(t, text.language == test.language && text_ok, error) - expect(t, text.keyword_localized == test.keyword_localized && text_ok, error) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) } } case "cthn0g04": // international UTF-8, hindi if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - expect(t, text.text == test.text && text_ok, error) - expect(t, text.language == test.language && text_ok, error) - expect(t, text.keyword_localized == test.keyword_localized && text_ok, error) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) } } case "ctjn0g04": // international UTF-8, japanese if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - error = fmt.tprintf("%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - expect(t, text.text == test.text && text_ok, error) - expect(t, text.language == test.language && text_ok, error) - expect(t, text.keyword_localized == test.keyword_localized && text_ok, error) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) } } } case .eXIf: if file.file == "exif2c08" { // chunk with jpeg exif data exif, exif_ok := png.exif(c) - error = fmt.tprintf("%v test %v eXIf byte order '%v', expected 'big_endian'.", file.file, count, exif.byte_order) - error_len := fmt.tprintf("%v test %v eXIf data length '%v', expected '%v'.", file.file, len(exif.data), 978) - expect(t, exif.byte_order == .big_endian && exif_ok, error) - expect(t, len(exif.data) == 978 && exif_ok, error_len) + testing.expectf(t, exif.byte_order == .big_endian && exif_ok, "%v test %v eXIf byte order '%v', expected 'big_endian'.", file.file, count, exif.byte_order) + testing.expectf(t, len(exif.data) == 978 && exif_ok, "%v test %v eXIf data length '%v', expected '%v'.", file.file, len(exif.data), 978) } } } } } } - png.destroy(img) - - for _, v in track.allocation_map { - error = fmt.tprintf("%v test %v leaked %v bytes @ loc %v.", file.file, count, v.size, v.location) - expect(t, false, error) - } } } - - return -} +} \ No newline at end of file From 22c092f846b0a7654b71d55c4e37980cd155f781 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 May 2024 17:22:02 +0200 Subject: [PATCH 046/270] Delete duplicated flag. --- tests/core/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index a740b3d7c..4fb046449 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -46,10 +46,10 @@ strings_test: $(ODIN) run strings $(COMMON) -out:test_core_strings hash_test: - $(ODIN) run hash $(COMMON) -o:speed -no-bounds-check -out:test_hash + $(ODIN) run hash $(COMMON) -o:speed -out:test_hash crypto_test: - $(ODIN) run crypto $(COMMON) $(COLLECTION) -o:speed -no-bounds-check -out:test_crypto + $(ODIN) run crypto $(COMMON) $(COLLECTION) -o:speed -out:test_crypto noise_test: $(ODIN) run math/noise $(COMMON) -out:test_noise From e3181c13c66b68ae2c0788d43417316a45b58b1d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 May 2024 17:53:27 +0200 Subject: [PATCH 047/270] Update `core:compress` tests --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/compress/test_core_compress.odin | 101 ++++---------------- 3 files changed, 18 insertions(+), 87 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 4fb046449..ef76bbbb8 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -37,7 +37,7 @@ image_test: $(ODIN) test image $(COMMON) -define:test_progress_width=12 -out:test_core_image compress_test: - $(ODIN) run compress $(COMMON) -out:test_core_compress + $(ODIN) test compress $(COMMON) -define:test_progress_width=3 -out:test_core_compress container_test: $(ODIN) run container $(COMMON) $(COLLECTION) -out:test_core_container diff --git a/tests/core/build.bat b/tests/core/build.bat index 4d981be11..e33b49531 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -6,7 +6,7 @@ python3 download_assets.py echo --- echo Running core:compress tests echo --- -%PATH_TO_ODIN% run compress %COMMON% -out:test_core_compress.exe || exit /b +%PATH_TO_ODIN% test compress %COMMON% -define:test_progress_width=3 -out:test_core_compress.exe || exit /b echo --- echo Running core:container tests diff --git a/tests/core/compress/test_core_compress.odin b/tests/core/compress/test_core_compress.odin index db6f3d06b..4ab63ae67 100644 --- a/tests/core/compress/test_core_compress.odin +++ b/tests/core/compress/test_core_compress.odin @@ -15,47 +15,7 @@ import "core:testing" import "core:compress/zlib" import "core:compress/gzip" import "core:compress/shoco" - import "core:bytes" -import "core:fmt" - -import "core:mem" -import "core:os" -// import "core:io" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - // w := io.to_writer(os.stream_from_handle(os.stdout)) - t := testing.T{}//{w=w} - zlib_test(&t) - gzip_test(&t) - shoco_test(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} @test zlib_test :: proc(t: ^testing.T) { @@ -80,26 +40,14 @@ zlib_test :: proc(t: ^testing.T) { } buf: bytes.Buffer + err := zlib.inflate(ODIN_DEMO, &buf) - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - err := zlib.inflate(ODIN_DEMO, &buf) - - expect(t, err == nil, "ZLIB failed to decompress ODIN_DEMO") + testing.expect(t, err == nil, "ZLIB failed to decompress ODIN_DEMO") s := bytes.buffer_to_string(&buf) - expect(t, s[68] == 240 && s[69] == 159 && s[70] == 152, "ZLIB result should've contained 😃 at position 68.") - - expect(t, len(s) == 438, "ZLIB result has an unexpected length.") - + testing.expect(t, s[68] == 240 && s[69] == 159 && s[70] == 152, "ZLIB result should've contained 😃 at position 68.") + testing.expect(t, len(s) == 438, "ZLIB result has an unexpected length.") bytes.buffer_destroy(&buf) - - for _, v in track.allocation_map { - error := fmt.tprintf("ZLIB test leaked %v bytes", v.size) - expect(t, false, error) - } } @test @@ -117,24 +65,12 @@ gzip_test :: proc(t: ^testing.T) { } buf: bytes.Buffer + err := gzip.load(TEST, &buf) - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - err := gzip.load(TEST, &buf) // , 438); - - expect(t, err == nil, "GZIP failed to decompress TEST") - s := bytes.buffer_to_string(&buf) - - expect(t, s == "payload", "GZIP result wasn't 'payload'") + testing.expect(t, err == nil, "GZIP failed to decompress TEST") + testing.expect(t, bytes.buffer_to_string(&buf) == "payload", "GZIP result wasn't 'payload'") bytes.buffer_destroy(&buf) - - for _, v in track.allocation_map { - error := fmt.tprintf("GZIP test leaked %v bytes", v.size) - expect(t, false, error) - } } @test @@ -168,31 +104,26 @@ shoco_test :: proc(t: ^testing.T) { defer delete(buffer) size, err := shoco.decompress(v.compressed, buffer[:]) - msg := fmt.tprintf("Expected `decompress` to return `nil`, got %v", err) - expect(t, err == nil, msg) + testing.expectf(t, err == nil, "Expected `decompress` to return `nil`, got %v", err) - msg = fmt.tprintf("Decompressed %v bytes into %v. Expected to decompress into %v bytes.", len(v.compressed), size, expected_raw) - expect(t, size == expected_raw, msg) - expect(t, string(buffer[:size]) == string(v.raw), "Decompressed contents don't match.") + testing.expectf(t, size == expected_raw, "Decompressed %v bytes into %v. Expected to decompress into %v bytes", len(v.compressed), size, expected_raw) + testing.expect(t, string(buffer[:size]) == string(v.raw), "Decompressed contents don't match") size, err = shoco.compress(string(v.raw), buffer[:]) - expect(t, err == nil, "Expected `compress` to return `nil`.") + testing.expect(t, err == nil, "Expected `compress` to return `nil`.") - msg = fmt.tprintf("Compressed %v bytes into %v. Expected to compress into %v bytes.", expected_raw, size, expected_compressed) - expect(t, size == expected_compressed, msg) + testing.expectf(t, size == expected_compressed, "Compressed %v bytes into %v. Expected to compress into %v bytes", expected_raw, size, expected_compressed) size, err = shoco.decompress(v.compressed, buffer[:expected_raw - 10]) - msg = fmt.tprintf("Decompressing into too small a buffer returned %v, expected `.Output_Too_Short`", err) - expect(t, err == .Output_Too_Short, msg) + testing.expectf(t, err == .Output_Too_Short, "Decompressing into too small a buffer returned %v, expected `.Output_Too_Short`", err) size, err = shoco.compress(string(v.raw), buffer[:expected_compressed - 10]) - msg = fmt.tprintf("Compressing into too small a buffer returned %v, expected `.Output_Too_Short`", err) - expect(t, err == .Output_Too_Short, msg) + testing.expectf(t, err == .Output_Too_Short, "Compressing into too small a buffer returned %v, expected `.Output_Too_Short`", err) size, err = shoco.decompress(v.compressed[:v.short_pack], buffer[:]) - expect(t, err == .Stream_Too_Short, "Expected `decompress` to return `Stream_Too_Short` because there was no more data after selecting a pack.") + testing.expectf(t, err == .Stream_Too_Short, "Insufficient data after pack returned %v, expected `.Stream_Too_Short`", err) size, err = shoco.decompress(v.compressed[:v.short_sentinel], buffer[:]) - expect(t, err == .Stream_Too_Short, "Expected `decompress` to return `Stream_Too_Short` because there was no more data after non-ASCII sentinel.") + testing.expectf(t, err == .Stream_Too_Short, "No more data after non-ASCII sentinel returned %v, expected `.Stream_Too_Short`", err) } } From a463e282db7931226c85b7562a4b4a716399b21b Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 May 2024 19:35:03 +0200 Subject: [PATCH 048/270] Update `core:container` tests --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/container/test_core_avl.odin | 71 +++++++-------- tests/core/container/test_core_container.odin | 26 ------ tests/core/container/test_core_rbtree.odin | 90 ++++++++++--------- .../core/container/test_core_small_array.odin | 67 +++++++------- 6 files changed, 119 insertions(+), 139 deletions(-) delete mode 100644 tests/core/container/test_core_container.odin diff --git a/tests/core/Makefile b/tests/core/Makefile index ef76bbbb8..fff46fb69 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -40,7 +40,7 @@ compress_test: $(ODIN) test compress $(COMMON) -define:test_progress_width=3 -out:test_core_compress container_test: - $(ODIN) run container $(COMMON) $(COLLECTION) -out:test_core_container + $(ODIN) test container $(COMMON) -define:test_progress_width=4 -out:test_core_container strings_test: $(ODIN) run strings $(COMMON) -out:test_core_strings diff --git a/tests/core/build.bat b/tests/core/build.bat index e33b49531..07432a657 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -11,7 +11,7 @@ echo --- echo --- echo Running core:container tests echo --- -%PATH_TO_ODIN% run container %COMMON% %COLLECTION% -out:test_core_container.exe || exit /b +%PATH_TO_ODIN% test container %COMMON% define:test_progress_width=4 -out:test_core_container.exe || exit /b echo --- echo Running core:crypto tests diff --git a/tests/core/container/test_core_avl.odin b/tests/core/container/test_core_avl.odin index 2244ab7f6..37b21a6be 100644 --- a/tests/core/container/test_core_avl.odin +++ b/tests/core/container/test_core_avl.odin @@ -4,22 +4,21 @@ import "core:container/avl" import "core:math/rand" import "core:slice" import "core:testing" -import "core:fmt" -import tc "tests:common" +import "core:log" @(test) test_avl :: proc(t: ^testing.T) { - tc.log(t, fmt.tprintf("Testing avl, using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", random_seed, random_seed)) + log.infof("Testing avl, using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", random_seed, random_seed) // Initialization. tree: avl.Tree(int) avl.init(&tree, slice.cmp_proc(int)) - tc.expect(t, avl.len(&tree) == 0, "empty: len should be 0") - tc.expect(t, avl.first(&tree) == nil, "empty: first should be nil") - tc.expect(t, avl.last(&tree) == nil, "empty: last should be nil") + testing.expect(t, avl.len(&tree) == 0, "empty: len should be 0") + testing.expect(t, avl.first(&tree) == nil, "empty: first should be nil") + testing.expect(t, avl.last(&tree) == nil, "empty: last should be nil") iter := avl.iterator(&tree, avl.Direction.Forward) - tc.expect(t, avl.iterator_get(&iter) == nil, "empty/iterator: first node should be nil") + testing.expect(t, avl.iterator_get(&iter) == nil, "empty/iterator: first node should be nil") r: rand.Rand rand.init(&r, random_seed) @@ -27,30 +26,32 @@ test_avl :: proc(t: ^testing.T) { // Test insertion. NR_INSERTS :: 32 + 1 // Ensure at least 1 collision. inserted_map := make(map[int]^avl.Node(int)) + defer delete(inserted_map) for i := 0; i < NR_INSERTS; i += 1 { v := int(rand.uint32(&r) & 0x1f) existing_node, in_map := inserted_map[v] n, ok, _ := avl.find_or_insert(&tree, v) - tc.expect(t, in_map != ok, "insert: ok should match inverse of map lookup") + testing.expect(t, in_map != ok, "insert: ok should match inverse of map lookup") if ok { inserted_map[v] = n } else { - tc.expect(t, existing_node == n, "insert: expecting existing node") + testing.expect(t, existing_node == n, "insert: expecting existing node") } } nrEntries := len(inserted_map) - tc.expect(t, avl.len(&tree) == nrEntries, "insert: len after") + testing.expect(t, avl.len(&tree) == nrEntries, "insert: len after") validate_avl(t, &tree) // Ensure that all entries can be found. for k, v in inserted_map { - tc.expect(t, v == avl.find(&tree, k), "Find(): Node") - tc.expect(t, k == v.value, "Find(): Node value") + testing.expect(t, v == avl.find(&tree, k), "Find(): Node") + testing.expect(t, k == v.value, "Find(): Node value") } // Test the forward/backward iterators. inserted_values: [dynamic]int + defer delete(inserted_values) for k in inserted_map { append(&inserted_values, k) } @@ -60,38 +61,38 @@ test_avl :: proc(t: ^testing.T) { visited: int for node in avl.iterator_next(&iter) { v, idx := node.value, visited - tc.expect(t, inserted_values[idx] == v, "iterator/forward: value") - tc.expect(t, node == avl.iterator_get(&iter), "iterator/forward: get") + testing.expect(t, inserted_values[idx] == v, "iterator/forward: value") + testing.expect(t, node == avl.iterator_get(&iter), "iterator/forward: get") visited += 1 } - tc.expect(t, visited == nrEntries, "iterator/forward: visited") + testing.expect(t, visited == nrEntries, "iterator/forward: visited") slice.reverse(inserted_values[:]) iter = avl.iterator(&tree, avl.Direction.Backward) visited = 0 for node in avl.iterator_next(&iter) { v, idx := node.value, visited - tc.expect(t, inserted_values[idx] == v, "iterator/backward: value") + testing.expect(t, inserted_values[idx] == v, "iterator/backward: value") visited += 1 } - tc.expect(t, visited == nrEntries, "iterator/backward: visited") + testing.expect(t, visited == nrEntries, "iterator/backward: visited") // Test removal. rand.shuffle(inserted_values[:], &r) for v, i in inserted_values { node := avl.find(&tree, v) - tc.expect(t, node != nil, "remove: find (pre)") + testing.expect(t, node != nil, "remove: find (pre)") ok := avl.remove(&tree, v) - tc.expect(t, ok, "remove: succeeds") - tc.expect(t, nrEntries - (i + 1) == avl.len(&tree), "remove: len (post)") + testing.expect(t, ok, "remove: succeeds") + testing.expect(t, nrEntries - (i + 1) == avl.len(&tree), "remove: len (post)") validate_avl(t, &tree) - tc.expect(t, nil == avl.find(&tree, v), "remove: find (post") + testing.expect(t, nil == avl.find(&tree, v), "remove: find (post") } - tc.expect(t, avl.len(&tree) == 0, "remove: len should be 0") - tc.expect(t, avl.first(&tree) == nil, "remove: first should be nil") - tc.expect(t, avl.last(&tree) == nil, "remove: last should be nil") + testing.expect(t, avl.len(&tree) == 0, "remove: len should be 0") + testing.expect(t, avl.first(&tree) == nil, "remove: first should be nil") + testing.expect(t, avl.last(&tree) == nil, "remove: last should be nil") // Refill the tree. for v in inserted_values { @@ -104,25 +105,25 @@ test_avl :: proc(t: ^testing.T) { v := node.value ok := avl.iterator_remove(&iter) - tc.expect(t, ok, "iterator/remove: success") + testing.expect(t, ok, "iterator/remove: success") ok = avl.iterator_remove(&iter) - tc.expect(t, !ok, "iterator/remove: redundant removes should fail") + testing.expect(t, !ok, "iterator/remove: redundant removes should fail") - tc.expect(t, avl.find(&tree, v) == nil, "iterator/remove: node should be gone") - tc.expect(t, avl.iterator_get(&iter) == nil, "iterator/remove: get should return nil") + testing.expect(t, avl.find(&tree, v) == nil, "iterator/remove: node should be gone") + testing.expect(t, avl.iterator_get(&iter) == nil, "iterator/remove: get should return nil") // Ensure that iterator_next still works. node, ok = avl.iterator_next(&iter) - tc.expect(t, ok == (avl.len(&tree) > 0), "iterator/remove: next should return false") - tc.expect(t, node == avl.first(&tree), "iterator/remove: next should return first") + testing.expect(t, ok == (avl.len(&tree) > 0), "iterator/remove: next should return false") + testing.expect(t, node == avl.first(&tree), "iterator/remove: next should return first") validate_avl(t, &tree) } - tc.expect(t, avl.len(&tree) == nrEntries - 1, "iterator/remove: len should drop by 1") + testing.expect(t, avl.len(&tree) == nrEntries - 1, "iterator/remove: len should drop by 1") avl.destroy(&tree) - tc.expect(t, avl.len(&tree) == 0, "destroy: len should be 0") + testing.expect(t, avl.len(&tree) == 0, "destroy: len should be 0") } @(private) @@ -141,10 +142,10 @@ tree_check_invariants :: proc( } // Validate the parent pointer. - tc.expect(t, parent == node._parent, "invalid parent pointer") + testing.expect(t, parent == node._parent, "invalid parent pointer") // Validate that the balance factor is -1, 0, 1. - tc.expect( + testing.expect( t, node._balance == -1 || node._balance == 0 || node._balance == 1, "invalid balance factor", @@ -155,7 +156,7 @@ tree_check_invariants :: proc( r_height := tree_check_invariants(t, tree, node._right, node) // Validate the AVL invariant and the balance factor. - tc.expect(t, int(node._balance) == r_height - l_height, "AVL balance factor invariant violated") + testing.expect(t, int(node._balance) == r_height - l_height, "AVL balance factor invariant violated") if l_height > r_height { return l_height + 1 } diff --git a/tests/core/container/test_core_container.odin b/tests/core/container/test_core_container.odin deleted file mode 100644 index 7dd4a3628..000000000 --- a/tests/core/container/test_core_container.odin +++ /dev/null @@ -1,26 +0,0 @@ -package test_core_container - -import "core:fmt" -import "core:testing" - -import tc "tests:common" - -expect_equal :: proc(t: ^testing.T, the_slice, expected: []int, loc := #caller_location) { - _eq :: proc(a, b: []int) -> bool { - if len(a) != len(b) do return false - for a, i in a { - if b[i] != a do return false - } - return true - } - tc.expect(t, _eq(the_slice, expected), fmt.tprintf("Expected %v, got %v\n", the_slice, expected), loc) -} - -main :: proc() { - t := testing.T{} - - test_avl(&t) - test_rbtree(&t) - test_small_array(&t) - tc.report(&t) -} diff --git a/tests/core/container/test_core_rbtree.odin b/tests/core/container/test_core_rbtree.odin index 89742b1d0..a4151d120 100644 --- a/tests/core/container/test_core_rbtree.odin +++ b/tests/core/container/test_core_rbtree.odin @@ -3,14 +3,16 @@ package test_core_container import rb "core:container/rbtree" import "core:math/rand" import "core:testing" -import "core:fmt" import "base:intrinsics" import "core:mem" import "core:slice" -import tc "tests:common" +import "core:log" -RANDOM_SEED :: #config(RANDOM_SEED, 0) -random_seed := u64(intrinsics.read_cycle_counter()) when RANDOM_SEED == 0 else u64(RANDOM_SEED) +@(private) +_RANDOM_SEED :: #config(RANDOM_SEED, u64(0)) + +// Exported +random_seed := u64(intrinsics.read_cycle_counter()) when _RANDOM_SEED == 0 else u64(_RANDOM_SEED) test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { track: mem.Tracking_Allocator @@ -21,15 +23,15 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { r: rand.Rand rand.init(&r, random_seed) - tc.log(t, fmt.tprintf("Testing Red-Black Tree($Key=%v,$Value=%v), using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", type_info_of(Key), type_info_of(Value), random_seed, random_seed)) + log.infof("Testing Red-Black Tree($Key=%v,$Value=%v), using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", type_info_of(Key), type_info_of(Value), random_seed, random_seed) tree: rb.Tree(Key, Value) rb.init(&tree) - tc.expect(t, rb.len(&tree) == 0, "empty: len should be 0") - tc.expect(t, rb.first(&tree) == nil, "empty: first should be nil") - tc.expect(t, rb.last(&tree) == nil, "empty: last should be nil") + testing.expect(t, rb.len(&tree) == 0, "empty: len should be 0") + testing.expect(t, rb.first(&tree) == nil, "empty: first should be nil") + testing.expect(t, rb.last(&tree) == nil, "empty: last should be nil") iter := rb.iterator(&tree, .Forward) - tc.expect(t, rb.iterator_get(&iter) == nil, "empty/iterator: first node should be nil") + testing.expect(t, rb.iterator_get(&iter) == nil, "empty/iterator: first node should be nil") // Test insertion. NR_INSERTS :: 32 + 1 // Ensure at least 1 collision. @@ -45,27 +47,27 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { existing_node, in_map := inserted_map[k] n, inserted, _ := rb.find_or_insert(&tree, k, v) - tc.expect(t, in_map != inserted, "insert: inserted should match inverse of map lookup") + testing.expect(t, in_map != inserted, "insert: inserted should match inverse of map lookup") if inserted { inserted_map[k] = n } else { - tc.expect(t, existing_node == n, "insert: expecting existing node") + testing.expect(t, existing_node == n, "insert: expecting existing node") } } entry_count := len(inserted_map) - tc.expect(t, rb.len(&tree) == entry_count, "insert: len after") + testing.expect(t, rb.len(&tree) == entry_count, "insert: len after") validate_rbtree(t, &tree) first := rb.first(&tree) last := rb.last(&tree) - tc.expect(t, first != nil && first.key == min_key, fmt.tprintf("insert: first should be present with key %v", min_key)) - tc.expect(t, last != nil && last.key == max_key, fmt.tprintf("insert: last should be present with key %v", max_key)) + testing.expectf(t, first != nil && first.key == min_key, "insert: first should be present with key %v", min_key) + testing.expectf(t, last != nil && last.key == max_key, "insert: last should be present with key %v", max_key) // Ensure that all entries can be found. for k, v in inserted_map { - tc.expect(t, v == rb.find(&tree, k), "Find(): Node") - tc.expect(t, k == v.key, "Find(): Node key") + testing.expect(t, v == rb.find(&tree, k), "Find(): Node") + testing.expect(t, k == v.key, "Find(): Node key") } // Test the forward/backward iterators. @@ -79,21 +81,21 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { visited: int for node in rb.iterator_next(&iter) { k, idx := node.key, visited - tc.expect(t, inserted_keys[idx] == k, "iterator/forward: key") - tc.expect(t, node == rb.iterator_get(&iter), "iterator/forward: get") + testing.expect(t, inserted_keys[idx] == k, "iterator/forward: key") + testing.expect(t, node == rb.iterator_get(&iter), "iterator/forward: get") visited += 1 } - tc.expect(t, visited == entry_count, "iterator/forward: visited") + testing.expect(t, visited == entry_count, "iterator/forward: visited") slice.reverse(inserted_keys[:]) iter = rb.iterator(&tree, rb.Direction.Backward) visited = 0 for node in rb.iterator_next(&iter) { k, idx := node.key, visited - tc.expect(t, inserted_keys[idx] == k, "iterator/backward: key") + testing.expect(t, inserted_keys[idx] == k, "iterator/backward: key") visited += 1 } - tc.expect(t, visited == entry_count, "iterator/backward: visited") + testing.expect(t, visited == entry_count, "iterator/backward: visited") // Test removal (and on_remove callback) rand.shuffle(inserted_keys[:], &r) @@ -104,19 +106,19 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { } for k, i in inserted_keys { node := rb.find(&tree, k) - tc.expect(t, node != nil, "remove: find (pre)") + testing.expect(t, node != nil, "remove: find (pre)") ok := rb.remove(&tree, k) - tc.expect(t, ok, "remove: succeeds") - tc.expect(t, entry_count - (i + 1) == rb.len(&tree), "remove: len (post)") + testing.expect(t, ok, "remove: succeeds") + testing.expect(t, entry_count - (i + 1) == rb.len(&tree), "remove: len (post)") validate_rbtree(t, &tree) - tc.expect(t, nil == rb.find(&tree, k), "remove: find (post") + testing.expect(t, nil == rb.find(&tree, k), "remove: find (post") } - tc.expect(t, rb.len(&tree) == 0, "remove: len should be 0") - tc.expect(t, callback_count == 0, fmt.tprintf("remove: on_remove should've been called %v times, it was %v", entry_count, callback_count)) - tc.expect(t, rb.first(&tree) == nil, "remove: first should be nil") - tc.expect(t, rb.last(&tree) == nil, "remove: last should be nil") + testing.expect(t, rb.len(&tree) == 0, "remove: len should be 0") + testing.expectf(t, callback_count == 0, "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count) + testing.expect(t, rb.first(&tree) == nil, "remove: first should be nil") + testing.expect(t, rb.last(&tree) == nil, "remove: last should be nil") // Refill the tree. for k in inserted_keys { @@ -130,32 +132,32 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { k := node.key ok := rb.iterator_remove(&iter) - tc.expect(t, ok, "iterator/remove: success") + testing.expect(t, ok, "iterator/remove: success") ok = rb.iterator_remove(&iter) - tc.expect(t, !ok, "iterator/remove: redundant removes should fail") + testing.expect(t, !ok, "iterator/remove: redundant removes should fail") - tc.expect(t, rb.find(&tree, k) == nil, "iterator/remove: node should be gone") - tc.expect(t, rb.iterator_get(&iter) == nil, "iterator/remove: get should return nil") + testing.expect(t, rb.find(&tree, k) == nil, "iterator/remove: node should be gone") + testing.expect(t, rb.iterator_get(&iter) == nil, "iterator/remove: get should return nil") // Ensure that iterator_next still works. node, ok = rb.iterator_next(&iter) - tc.expect(t, ok == (rb.len(&tree) > 0), "iterator/remove: next should return false") - tc.expect(t, node == rb.first(&tree), "iterator/remove: next should return first") + testing.expect(t, ok == (rb.len(&tree) > 0), "iterator/remove: next should return false") + testing.expect(t, node == rb.first(&tree), "iterator/remove: next should return first") validate_rbtree(t, &tree) } - tc.expect(t, rb.len(&tree) == entry_count - 1, "iterator/remove: len should drop by 1") + testing.expect(t, rb.len(&tree) == entry_count - 1, "iterator/remove: len should drop by 1") rb.destroy(&tree) - tc.expect(t, rb.len(&tree) == 0, "destroy: len should be 0") - tc.expect(t, callback_count == 0, fmt.tprintf("remove: on_remove should've been called %v times, it was %v", entry_count, callback_count)) + testing.expect(t, rb.len(&tree) == 0, "destroy: len should be 0") + testing.expectf(t, callback_count == 0, "remove: on_remove should've been called %v times, it was %v", entry_count, callback_count) // print_tree_node(tree._root) delete(inserted_map) delete(inserted_keys) - tc.expect(t, len(track.allocation_map) == 0, fmt.tprintf("Expected 0 leaks, have %v", len(track.allocation_map))) - tc.expect(t, len(track.bad_free_array) == 0, fmt.tprintf("Expected 0 bad frees, have %v", len(track.bad_free_array))) + testing.expectf(t, len(track.allocation_map) == 0, "Expected 0 leaks, have %v", len(track.allocation_map)) + testing.expectf(t, len(track.bad_free_array) == 0, "Expected 0 bad frees, have %v", len(track.bad_free_array)) return } @@ -194,7 +196,7 @@ validate_rbtree :: proc(t: ^testing.T, tree: ^$T/rb.Tree($Key, $Value)) { } verify_rbtree_propery_1 :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value)) { - tc.expect(t, rb.node_color(n) == .Black || rb.node_color(n) == .Red, "Property #1: Each node is either red or black.") + testing.expect(t, rb.node_color(n) == .Black || rb.node_color(n) == .Red, "Property #1: Each node is either red or black.") if n == nil { return } @@ -203,14 +205,14 @@ verify_rbtree_propery_1 :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value)) { } verify_rbtree_propery_2 :: proc(t: ^testing.T, root: ^$N/rb.Node($Key, $Value)) { - tc.expect(t, rb.node_color(root) == .Black, "Property #2: Root node should be black.") + testing.expect(t, rb.node_color(root) == .Black, "Property #2: Root node should be black.") } verify_rbtree_propery_4 :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Value)) { if rb.node_color(n) == .Red { // A red node's left, right and parent should be black all_black := rb.node_color(n._left) == .Black && rb.node_color(n._right) == .Black && rb.node_color(n._parent) == .Black - tc.expect(t, all_black, "Property #3: Red node's children + parent must be black.") + testing.expect(t, all_black, "Property #3: Red node's children + parent must be black.") } if n == nil { return @@ -233,7 +235,7 @@ verify_rbtree_propery_5_helper :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Valu if path_black_count^ == -1 { path_black_count^ = black_count } else { - tc.expect(t, black_count == path_black_count^, "Property #5: Paths from a node to its leaves contain same black count.") + testing.expect(t, black_count == path_black_count^, "Property #5: Paths from a node to its leaves contain same black count.") } return } diff --git a/tests/core/container/test_core_small_array.odin b/tests/core/container/test_core_small_array.odin index 78998de16..580df793e 100644 --- a/tests/core/container/test_core_small_array.odin +++ b/tests/core/container/test_core_small_array.odin @@ -3,44 +3,47 @@ package test_core_container import "core:testing" import "core:container/small_array" -import tc "tests:common" - -@(test) -test_small_array :: proc(t: ^testing.T) { - tc.log(t, "Testing small_array") - - test_small_array_removes(t) - test_small_array_inject_at(t) -} - @(test) test_small_array_removes :: proc(t: ^testing.T) { - array: small_array.Small_Array(10, int) - small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + array: small_array.Small_Array(10, int) + small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - small_array.ordered_remove(&array, 0) - expect_equal(t, small_array.slice(&array), []int { 1, 2, 3, 4, 5, 6, 7, 8, 9 }) - small_array.ordered_remove(&array, 5) - expect_equal(t, small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 8, 9 }) - small_array.ordered_remove(&array, 6) - expect_equal(t, small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 9 }) - small_array.unordered_remove(&array, 0) - expect_equal(t, small_array.slice(&array), []int { 9, 2, 3, 4, 5, 7 }) - small_array.unordered_remove(&array, 2) - expect_equal(t, small_array.slice(&array), []int { 9, 2, 7, 4, 5 }) - small_array.unordered_remove(&array, 4) - expect_equal(t, small_array.slice(&array), []int { 9, 2, 7, 4 }) + small_array.ordered_remove(&array, 0) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2, 3, 4, 5, 6, 7, 8, 9 })) + small_array.ordered_remove(&array, 5) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 8, 9 })) + small_array.ordered_remove(&array, 6) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2, 3, 4, 5, 7, 9 })) + small_array.unordered_remove(&array, 0) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 9, 2, 3, 4, 5, 7 })) + small_array.unordered_remove(&array, 2) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 9, 2, 7, 4, 5 })) + small_array.unordered_remove(&array, 4) + testing.expect(t, slice_equal(small_array.slice(&array), []int { 9, 2, 7, 4 })) } @(test) test_small_array_inject_at :: proc(t: ^testing.T) { - array: small_array.Small_Array(13, int) - small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + array: small_array.Small_Array(13, int) + small_array.append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - tc.expect(t, small_array.inject_at(&array, 0, 0), "Expected to be able to inject into small array") - expect_equal(t, small_array.slice(&array), []int { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }) - tc.expect(t, small_array.inject_at(&array, 0, 5), "Expected to be able to inject into small array") - expect_equal(t, small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9 }) - tc.expect(t, small_array.inject_at(&array, 0, small_array.len(array)), "Expected to be able to inject into small array") - expect_equal(t, small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 0 }) + testing.expect(t, small_array.inject_at(&array, 0, 0), "Expected to be able to inject into small array") + testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })) + testing.expect(t, small_array.inject_at(&array, 0, 5), "Expected to be able to inject into small array") + testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9 })) + testing.expect(t, small_array.inject_at(&array, 0, small_array.len(array)), "Expected to be able to inject into small array") + testing.expect(t, slice_equal(small_array.slice(&array), []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 0 })) +} + +slice_equal :: proc(a, b: []int) -> bool { + if len(a) != len(b) { + return false + } + + for a, i in a { + if b[i] != a { + return false + } + } + return true } From a0b2ea6d6e327dfc345b0f07c6188ae64abc2d95 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 May 2024 22:12:33 +0200 Subject: [PATCH 049/270] Update `tests\core\crypto` --- tests/core/Makefile | 6 +- tests/core/build.bat | 2 +- tests/core/crypto/test_core_crypto.odin | 96 ++---- tests/core/crypto/test_core_crypto_aes.odin | 125 ++++---- .../crypto/test_core_crypto_ecc25519.odin | 293 +++++++----------- tests/core/crypto/test_core_crypto_hash.odin | 98 +++--- tests/core/crypto/test_core_crypto_kdf.odin | 54 ++-- tests/core/crypto/test_core_crypto_mac.odin | 82 ++--- .../test_core_crypto_sha3_variants.odin | 96 +++--- tests/core/crypto/test_crypto_benchmark.odin | 68 ++-- tests/core/image/test_core_image.odin | 12 +- 11 files changed, 355 insertions(+), 577 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index fff46fb69..4dede0370 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -42,15 +42,15 @@ compress_test: container_test: $(ODIN) test container $(COMMON) -define:test_progress_width=4 -out:test_core_container +crypto_test: + $(ODIN) test crypto $(COMMON) -define:test_progress_width=18 -o:speed -out:test_crypto + strings_test: $(ODIN) run strings $(COMMON) -out:test_core_strings hash_test: $(ODIN) run hash $(COMMON) -o:speed -out:test_hash -crypto_test: - $(ODIN) run crypto $(COMMON) $(COLLECTION) -o:speed -out:test_crypto - noise_test: $(ODIN) run math/noise $(COMMON) -out:test_noise diff --git a/tests/core/build.bat b/tests/core/build.bat index 07432a657..fc5e2dc3f 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -16,7 +16,7 @@ echo --- echo --- echo Running core:crypto tests echo --- -%PATH_TO_ODIN% run crypto %COMMON% %COLLECTION% -out:test_crypto.exe || exit /b +%PATH_TO_ODIN% test crypto %COMMON% define:test_progress_width=18 -o:speed -out:test_crypto.exe || exit /b echo --- echo Running core:encoding tests diff --git a/tests/core/crypto/test_core_crypto.odin b/tests/core/crypto/test_core_crypto.odin index 1f7bcece3..f3f76646b 100644 --- a/tests/core/crypto/test_core_crypto.odin +++ b/tests/core/crypto/test_core_crypto.odin @@ -13,41 +13,20 @@ package test_core_crypto */ import "core:encoding/hex" -import "core:fmt" import "core:mem" import "core:testing" +import "base:runtime" +import "core:log" import "core:crypto" import "core:crypto/chacha20" import "core:crypto/chacha20poly1305" -import tc "tests:common" - -main :: proc() { - t := testing.T{} - - test_rand_bytes(&t) - - test_hash(&t) - test_mac(&t) - test_kdf(&t) // After hash/mac tests because those should pass first. - test_ecc25519(&t) - - test_aes(&t) - test_chacha20(&t) - test_chacha20poly1305(&t) - test_sha3_variants(&t) - - bench_crypto(&t) - - tc.report(&t) -} - _PLAINTEXT_SUNSCREEN_STR := "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it." @(test) test_chacha20 :: proc(t: ^testing.T) { - tc.log(t, "Testing (X)ChaCha20") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() // Test cases taken from RFC 8439, and draft-irtf-cfrg-xchacha-03 plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR) @@ -90,14 +69,12 @@ test_chacha20 :: proc(t: ^testing.T) { chacha20.xor_bytes(&ctx, derived_ciphertext[:], plaintext[:]) derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_ciphertext_str == ciphertext_str, - fmt.tprintf( - "Expected %s for xor_bytes(plaintext_str), but got %s instead", - ciphertext_str, - derived_ciphertext_str, - ), + "Expected %s for xor_bytes(plaintext_str), but got %s instead", + ciphertext_str, + derived_ciphertext_str, ) xkey := [chacha20.KEY_SIZE]byte { @@ -137,21 +114,17 @@ test_chacha20 :: proc(t: ^testing.T) { chacha20.xor_bytes(&ctx, derived_ciphertext[:], plaintext[:]) derived_ciphertext_str = string(hex.encode(derived_ciphertext[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_ciphertext_str == xciphertext_str, - fmt.tprintf( - "Expected %s for xor_bytes(plaintext_str), but got %s instead", - xciphertext_str, - derived_ciphertext_str, - ), + "Expected %s for xor_bytes(plaintext_str), but got %s instead", + xciphertext_str, + derived_ciphertext_str, ) } @(test) test_chacha20poly1305 :: proc(t: ^testing.T) { - tc.log(t, "Testing chacha20poly1205") - plaintext := transmute([]byte)(_PLAINTEXT_SUNSCREEN_STR) aad := [12]byte { @@ -209,25 +182,21 @@ test_chacha20poly1305 :: proc(t: ^testing.T) { ) derived_ciphertext_str := string(hex.encode(derived_ciphertext[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_ciphertext_str == ciphertext_str, - fmt.tprintf( - "Expected ciphertext %s for encrypt(aad, plaintext), but got %s instead", - ciphertext_str, - derived_ciphertext_str, - ), + "Expected ciphertext %s for encrypt(aad, plaintext), but got %s instead", + ciphertext_str, + derived_ciphertext_str, ) derived_tag_str := string(hex.encode(derived_tag[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_tag_str == tag_str, - fmt.tprintf( - "Expected tag %s for encrypt(aad, plaintext), but got %s instead", - tag_str, - derived_tag_str, - ), + "Expected tag %s for encrypt(aad, plaintext), but got %s instead", + tag_str, + derived_tag_str, ) derived_plaintext: [114]byte @@ -240,15 +209,13 @@ test_chacha20poly1305 :: proc(t: ^testing.T) { ciphertext[:], ) derived_plaintext_str := string(derived_plaintext[:]) - tc.expect(t, ok, "Expected true for decrypt(tag, aad, ciphertext)") - tc.expect( + testing.expect(t, ok, "Expected true for decrypt(tag, aad, ciphertext)") + testing.expectf( t, derived_plaintext_str == _PLAINTEXT_SUNSCREEN_STR, - fmt.tprintf( - "Expected plaintext %s for decrypt(tag, aad, ciphertext), but got %s instead", - _PLAINTEXT_SUNSCREEN_STR, - derived_plaintext_str, - ), + "Expected plaintext %s for decrypt(tag, aad, ciphertext), but got %s instead", + _PLAINTEXT_SUNSCREEN_STR, + derived_plaintext_str, ) derived_ciphertext[0] ~= 0xa5 @@ -260,7 +227,7 @@ test_chacha20poly1305 :: proc(t: ^testing.T) { aad[:], derived_ciphertext[:], ) - tc.expect(t, !ok, "Expected false for decrypt(tag, aad, corrupted_ciphertext)") + testing.expect(t, !ok, "Expected false for decrypt(tag, aad, corrupted_ciphertext)") aad[0] ~= 0xa5 ok = chacha20poly1305.decrypt( @@ -271,15 +238,13 @@ test_chacha20poly1305 :: proc(t: ^testing.T) { aad[:], ciphertext[:], ) - tc.expect(t, !ok, "Expected false for decrypt(tag, corrupted_aad, ciphertext)") + testing.expect(t, !ok, "Expected false for decrypt(tag, corrupted_aad, ciphertext)") } @(test) test_rand_bytes :: proc(t: ^testing.T) { - tc.log(t, "Testing rand_bytes") - if !crypto.HAS_RAND_BYTES { - tc.log(t, "rand_bytes not supported - skipping") + log.info("rand_bytes not supported - skipping") return } @@ -307,10 +272,5 @@ test_rand_bytes :: proc(t: ^testing.T) { break } } - - tc.expect( - t, - seems_ok, - "Expected to randomize the head and tail of the buffer within a handful of attempts", - ) + testing.expect(t, seems_ok, "Expected to randomize the head and tail of the buffer within a handful of attempts") } diff --git a/tests/core/crypto/test_core_crypto_aes.odin b/tests/core/crypto/test_core_crypto_aes.odin index f33292a53..8b96b0ae9 100644 --- a/tests/core/crypto/test_core_crypto_aes.odin +++ b/tests/core/crypto/test_core_crypto_aes.odin @@ -2,19 +2,17 @@ package test_core_crypto import "base:runtime" import "core:encoding/hex" -import "core:fmt" +import "core:log" import "core:testing" import "core:crypto/aes" import "core:crypto/sha2" -import tc "tests:common" - @(test) test_aes :: proc(t: ^testing.T) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - tc.log(t, "Testing AES") + log.info("Testing AES") impls := make([dynamic]aes.Implementation, 0, 2) append(&impls, aes.Implementation.Portable) @@ -29,9 +27,8 @@ test_aes :: proc(t: ^testing.T) { } } -@(test) test_aes_ecb :: proc(t: ^testing.T, impl: aes.Implementation) { - tc.log(t, fmt.tprintf("Testing AES-ECB/%v", impl)) + log.infof("Testing AES-ECB/%v", impl) test_vectors := []struct { key: string, @@ -111,39 +108,34 @@ test_aes_ecb :: proc(t: ^testing.T, impl: aes.Implementation) { aes.encrypt_ecb(&ctx, dst[:], plaintext) dst_str := string(hex.encode(dst[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.ciphertext, - fmt.tprintf( - "AES-ECB/%v: Expected: %s for encrypt(%s, %s), but got %s instead", - impl, - v.ciphertext, - v.key, - v.plaintext, - dst_str, - ), + "AES-ECB/%v: Expected: %s for encrypt(%s, %s), but got %s instead", + impl, + v.ciphertext, + v.key, + v.plaintext, + dst_str, ) aes.decrypt_ecb(&ctx, dst[:], ciphertext) dst_str = string(hex.encode(dst[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.plaintext, - fmt.tprintf( - "AES-ECB/%v: Expected: %s for decrypt(%s, %s), but got %s instead", - impl, - v.plaintext, - v.key, - v.ciphertext, - dst_str, - ), + "AES-ECB/%v: Expected: %s for decrypt(%s, %s), but got %s instead", + impl, + v.plaintext, + v.key, + v.ciphertext, + dst_str, ) } } -@(test) test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { - tc.log(t, fmt.tprintf("Testing AES-CTR/%v", impl)) + log.infof("Testing AES-CTR/%v", impl) test_vectors := []struct { key: string, @@ -185,18 +177,16 @@ test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { aes.xor_bytes_ctr(&ctx, dst, plaintext) dst_str := string(hex.encode(dst[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.ciphertext, - fmt.tprintf( - "AES-CTR/%v: Expected: %s for encrypt(%s, %s, %s), but got %s instead", - impl, - v.ciphertext, - v.key, - v.iv, - v.plaintext, - dst_str, - ), + "AES-CTR/%v: Expected: %s for encrypt(%s, %s, %s), but got %s instead", + impl, + v.ciphertext, + v.key, + v.iv, + v.plaintext, + dst_str, ) } @@ -224,21 +214,18 @@ test_aes_ctr :: proc(t: ^testing.T, impl: aes.Implementation) { digest_str := string(hex.encode(digest[:], context.temp_allocator)) expected_digest_str := "d4445343afeb9d1237f95b10d00358aed4c1d7d57c9fe480cd0afb5e2ffd448c" - tc.expect( + testing.expectf( t, expected_digest_str == digest_str, - fmt.tprintf( - "AES-CTR/%v: Expected %s for keystream digest, but got %s instead", - impl, - expected_digest_str, - digest_str, - ), + "AES-CTR/%v: Expected %s for keystream digest, but got %s instead", + impl, + expected_digest_str, + digest_str, ) } -@(test) test_aes_gcm :: proc(t: ^testing.T, impl: aes.Implementation) { - tc.log(t, fmt.tprintf("Testing AES-GCM/%v", impl)) + log.infof("Testing AES-GCM/%v", impl) // NIST did a reorg of their site, so the source of the test vectors // is only available from an archive. The commented out tests are @@ -422,41 +409,37 @@ test_aes_gcm :: proc(t: ^testing.T, impl: aes.Implementation) { dst_str := string(hex.encode(dst[:], context.temp_allocator)) tag_str := string(hex.encode(tag_[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.ciphertext && tag_str == v.tag, - fmt.tprintf( - "AES-GCM/%v: Expected: (%s, %s) for seal(%s, %s, %s, %s), but got (%s, %s) instead", - impl, - v.ciphertext, - v.tag, - v.key, - v.iv, - v.aad, - v.plaintext, - dst_str, - tag_str, - ), + "AES-GCM/%v: Expected: (%s, %s) for seal(%s, %s, %s, %s), but got (%s, %s) instead", + impl, + v.ciphertext, + v.tag, + v.key, + v.iv, + v.aad, + v.plaintext, + dst_str, + tag_str, ) ok := aes.open_gcm(&ctx, dst, iv, aad, ciphertext, tag) dst_str = string(hex.encode(dst[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, ok && dst_str == v.plaintext, - fmt.tprintf( - "AES-GCM/%v: Expected: (%s, true) for open(%s, %s, %s, %s, %s), but got (%s, %s) instead", - impl, - v.plaintext, - v.key, - v.iv, - v.aad, - v.ciphertext, - v.tag, - dst_str, - ok, - ), + "AES-GCM/%v: Expected: (%s, true) for open(%s, %s, %s, %s, %s), but got (%s, %s) instead", + impl, + v.plaintext, + v.key, + v.iv, + v.aad, + v.ciphertext, + v.tag, + dst_str, + ok, ) } } diff --git a/tests/core/crypto/test_core_crypto_ecc25519.odin b/tests/core/crypto/test_core_crypto_ecc25519.odin index 5ea008f90..baf4a1a38 100644 --- a/tests/core/crypto/test_core_crypto_ecc25519.odin +++ b/tests/core/crypto/test_core_crypto_ecc25519.odin @@ -1,8 +1,6 @@ package test_core_crypto -import "base:runtime" import "core:encoding/hex" -import "core:fmt" import "core:testing" import field "core:crypto/_fiat/field_curve25519" @@ -10,25 +8,8 @@ import "core:crypto/ed25519" import "core:crypto/ristretto255" import "core:crypto/x25519" -import tc "tests:common" - -@(test) -test_ecc25519 :: proc(t: ^testing.T) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - tc.log(t, "Testing curve25519 ECC") - - test_sqrt_ratio_m1(t) - test_ristretto255(t) - - test_ed25519(t) - test_x25519(t) -} - @(test) test_sqrt_ratio_m1 :: proc(t: ^testing.T) { - tc.log(t, "Testing sqrt_ratio_m1") - test_vectors := []struct { u: string, v: string, @@ -90,25 +71,21 @@ test_sqrt_ratio_m1 :: proc(t: ^testing.T) { field.fe_relax_cast(&vee), ) - tc.expect( + testing.expectf( t, (was_square == 1) == v.was_square && field.fe_equal_bytes(&r, r_) == 1, - fmt.tprintf( - "Expected (%v, %s) for SQRT_RATIO_M1(%s, %s), got %s", - v.was_square, - v.r, - v.u, - v.v, - fe_str(&r), - ), + "Expected (%v, %s) for SQRT_RATIO_M1(%s, %s), got %s", + v.was_square, + v.r, + v.u, + v.v, + fe_str(&r), ) } } @(test) test_ristretto255 :: proc(t: ^testing.T) { - tc.log(t, "Testing ristretto255") - ge_gen: ristretto255.Group_Element ristretto255.ge_generator(&ge_gen) @@ -158,7 +135,7 @@ test_ristretto255 :: proc(t: ^testing.T) { ge: ristretto255.Group_Element ok := ristretto255.ge_set_bytes(&ge, b) - tc.expect(t, !ok, fmt.tprintf("Expected false for %s", x)) + testing.expectf(t, !ok, "Expected false for %s", x) } generator_multiples := []string { @@ -185,22 +162,20 @@ test_ristretto255 :: proc(t: ^testing.T) { ge := &ges[i] ok := ristretto255.ge_set_bytes(ge, b) - tc.expect(t, ok, fmt.tprintf("Expected true for %s", x)) + testing.expectf(t, ok, "Expected true for %s", x) x_check := ge_str(ge) - tc.expect( + testing.expectf( t, x == x_check, - fmt.tprintf( - "Expected %s (round-trip) but got %s instead", - x, - x_check, - ), + "Expected %s (round-trip) but got %s instead", + x, + x_check, ) if i == 1 { - tc.expect( + testing.expect( t, ristretto255.ge_equal(ge, &ge_gen) == 1, "Expected element 1 to be the generator", @@ -217,41 +192,35 @@ test_ristretto255 :: proc(t: ^testing.T) { ristretto255.ge_scalarmult_generator(&ge_check, &sc) x_check := ge_str(&ge_check) - tc.expect( + testing.expectf( t, x_check == generator_multiples[i], - fmt.tprintf( - "Expected %s for G * %d (specialized), got %s", - generator_multiples[i], - i, - x_check, - ), + "Expected %s for G * %d (specialized), got %s", + generator_multiples[i], + i, + x_check, ) ristretto255.ge_scalarmult(&ge_check, &ges[1], &sc) x_check = ge_str(&ge_check) - tc.expect( + testing.expectf( t, x_check == generator_multiples[i], - fmt.tprintf( - "Expected %s for G * %d (generic), got %s (slow compare)", - generator_multiples[i], - i, - x_check, - ), + "Expected %s for G * %d (generic), got %s (slow compare)", + generator_multiples[i], + i, + x_check, ) ristretto255.ge_scalarmult_vartime(&ge_check, &ges[1], &sc) x_check = ge_str(&ge_check) - tc.expect( + testing.expectf( t, x_check == generator_multiples[i], - fmt.tprintf( - "Expected %s for G * %d (generic vartime), got %s (slow compare)", - generator_multiples[i], - i, - x_check, - ), + "Expected %s for G * %d (generic vartime), got %s (slow compare)", + generator_multiples[i], + i, + x_check, ) switch i { @@ -261,28 +230,24 @@ test_ristretto255 :: proc(t: ^testing.T) { ristretto255.ge_add(&ge_check, ge_prev, &ge_gen) x_check = ge_str(&ge_check) - tc.expect( + testing.expectf( t, x_check == generator_multiples[i], - fmt.tprintf( - "Expected %s for ges[%d] + ges[%d], got %s (slow compare)", - generator_multiples[i], - i-1, - 1, - x_check, - ), + "Expected %s for ges[%d] + ges[%d], got %s (slow compare)", + generator_multiples[i], + i-1, + 1, + x_check, ) - tc.expect( + testing.expectf( t, ristretto255.ge_equal(&ges[i], &ge_check) == 1, - fmt.tprintf( - "Expected %s for ges[%d] + ges[%d], got %s (fast compare)", - generator_multiples[i], - i-1, - 1, - x_check, - ), + "Expected %s for ges[%d] + ges[%d], got %s (fast compare)", + generator_multiples[i], + i-1, + 1, + x_check, ) } } @@ -344,22 +309,18 @@ test_ristretto255 :: proc(t: ^testing.T) { ristretto255.ge_set_wide_bytes(&ge, in_bytes) ge_check := ge_str(&ge) - tc.expect( + testing.expectf( t, ge_check == v.output, - fmt.tprintf( - "Expected %s for %s, got %s", - v.output, - ge_check, - ), + "Expected %s for %s, got %s", + v.output, + ge_check, ) } } @(test) test_ed25519 :: proc(t: ^testing.T) { - tc.log(t, "Testing ed25519") - test_vectors_rfc := []struct { priv_key: string, pub_key: string, @@ -401,87 +362,73 @@ test_ed25519 :: proc(t: ^testing.T) { priv_key: ed25519.Private_Key ok := ed25519.private_key_set_bytes(&priv_key, priv_bytes) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected %s to be a valid private key", - v.priv_key, - ), + "Expected %s to be a valid private key", + v.priv_key, ) key_bytes: [32]byte ed25519.private_key_bytes(&priv_key, key_bytes[:]) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected private key %s round-trip, got %s", - v.priv_key, - string(hex.encode(key_bytes[:], context.temp_allocator)), - ), + "Expected private key %s round-trip, got %s", + v.priv_key, + string(hex.encode(key_bytes[:], context.temp_allocator)), ) pub_key: ed25519.Public_Key ok = ed25519.public_key_set_bytes(&pub_key, pub_bytes) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected %s to be a valid public key (priv->pub: %s)", - v.pub_key, - string(hex.encode(priv_key._pub_key._b[:], context.temp_allocator)), - ), + "Expected %s to be a valid public key (priv->pub: %s)", + v.pub_key, + string(hex.encode(priv_key._pub_key._b[:], context.temp_allocator)), ) ed25519.public_key_bytes(&pub_key, key_bytes[:]) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected public key %s round-trip, got %s", - v.pub_key, - string(hex.encode(key_bytes[:], context.temp_allocator)), - ), + "Expected public key %s round-trip, got %s", + v.pub_key, + string(hex.encode(key_bytes[:], context.temp_allocator)), ) sig: [ed25519.SIGNATURE_SIZE]byte ed25519.sign(&priv_key, msg_bytes, sig[:]) x := string(hex.encode(sig[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, x == v.sig, - fmt.tprintf( - "Expected %s for sign(%s, %s), got %s", - v.sig, - v.priv_key, - v.msg, - x, - ), + "Expected %s for sign(%s, %s), got %s", + v.sig, + v.priv_key, + v.msg, + x, ) ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected true for verify(%s, %s, %s)", - v.pub_key, - v.msg, - v.sig, - ), + "Expected true for verify(%s, %s, %s)", + v.pub_key, + v.msg, + v.sig, ) ok = ed25519.verify(&priv_key._pub_key, msg_bytes, sig_bytes) - tc.expect( + testing.expectf( t, ok, - fmt.tprintf( - "Expected true for verify(pub(%s), %s %s)", - v.priv_key, - v.msg, - v.sig, - ), + "Expected true for verify(pub(%s), %s %s)", + v.priv_key, + v.msg, + v.sig, ) // Corrupt the message and make sure verification fails. @@ -493,15 +440,13 @@ test_ed25519 :: proc(t: ^testing.T) { msg_bytes[0] = msg_bytes[0] ~ 69 } ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes) - tc.expect( + testing.expectf( t, ok == false, - fmt.tprintf( - "Expected false for verify(%s, %s (corrupted), %s)", - v.pub_key, - v.msg, - v.sig, - ), + "Expected false for verify(%s, %s (corrupted), %s)", + v.pub_key, + v.msg, + v.sig, ) } @@ -634,15 +579,13 @@ test_ed25519 :: proc(t: ^testing.T) { pub_key: ed25519.Public_Key ok := ed25519.public_key_set_bytes(&pub_key, pub_bytes) - tc.expect( + testing.expectf( t, ok == v.pub_key_ok, - fmt.tprintf( - "speccheck/%d: Expected %s to be a (in)valid public key, got %v", - i, - v.pub_key, - ok, - ), + "speccheck/%d: Expected %s to be a (in)valid public key, got %v", + i, + v.pub_key, + ok, ) // If A is rejected for being non-canonical, skip signature check. @@ -651,17 +594,15 @@ test_ed25519 :: proc(t: ^testing.T) { } ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes) - tc.expect( + testing.expectf( t, ok == v.sig_ok, - fmt.tprintf( - "speccheck/%d Expected %v for verify(%s, %s, %s)", - i, - v.sig_ok, - v.pub_key, - v.msg, - v.sig, - ), + "speccheck/%d Expected %v for verify(%s, %s, %s)", + i, + v.sig_ok, + v.pub_key, + v.msg, + v.sig, ) // If the signature is accepted, skip the relaxed signature check. @@ -670,25 +611,21 @@ test_ed25519 :: proc(t: ^testing.T) { } ok = ed25519.verify(&pub_key, msg_bytes, sig_bytes, true) - tc.expect( + testing.expectf( t, ok == v.sig_ok_relaxed, - fmt.tprintf( - "speccheck/%d Expected %v for verify(%s, %s, %s, true)", - i, - v.sig_ok_relaxed, - v.pub_key, - v.msg, - v.sig, - ), + "speccheck/%d Expected %v for verify(%s, %s, %s, true)", + i, + v.sig_ok_relaxed, + v.pub_key, + v.msg, + v.sig, ) } } @(test) test_x25519 :: proc(t: ^testing.T) { - tc.log(t, "Testing X25519") - // Local copy of this so that the base point doesn't need to be exported. _BASE_POINT: [32]byte = { 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -720,17 +657,15 @@ test_x25519 :: proc(t: ^testing.T) { x25519.scalarmult(derived_point[:], scalar[:], point[:]) derived_point_str := string(hex.encode(derived_point[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_point_str == v.product, - fmt.tprintf( - "Expected %s for %s * %s, but got %s instead", - v.product, - v.scalar, - v.point, - derived_point_str, - ), - ) + "Expected %s for %s * %s, but got %s instead", + v.product, + v.scalar, + v.point, + derived_point_str, + ) // Abuse the test vectors to sanity-check the scalar-basepoint multiply. p1, p2: [x25519.POINT_SIZE]byte @@ -738,15 +673,13 @@ test_x25519 :: proc(t: ^testing.T) { x25519.scalarmult(p2[:], scalar[:], _BASE_POINT[:]) p1_str := string(hex.encode(p1[:], context.temp_allocator)) p2_str := string(hex.encode(p2[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, p1_str == p2_str, - fmt.tprintf( - "Expected %s for %s * basepoint, but got %s instead", - p2_str, - v.scalar, - p1_str, - ), + "Expected %s for %s * basepoint, but got %s instead", + p2_str, + v.scalar, + p1_str, ) } } @@ -763,4 +696,4 @@ fe_str :: proc(fe: ^field.Tight_Field_Element) -> string { b: [32]byte field.fe_to_bytes(&b, fe) return string(hex.encode(b[:], context.temp_allocator)) -} +} \ No newline at end of file diff --git a/tests/core/crypto/test_core_crypto_hash.odin b/tests/core/crypto/test_core_crypto_hash.odin index c4e8e8dd7..9a9d0cc76 100644 --- a/tests/core/crypto/test_core_crypto_hash.odin +++ b/tests/core/crypto/test_core_crypto_hash.odin @@ -3,23 +3,17 @@ package test_core_crypto import "base:runtime" import "core:bytes" import "core:encoding/hex" -import "core:fmt" import "core:strings" import "core:testing" - import "core:crypto/hash" -import tc "tests:common" - @(test) test_hash :: proc(t: ^testing.T) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - tc.log(t, "Testing Hashes") - // TODO: // - Stick the test vectors in a JSON file or something. - data_1_000_000_a := strings.repeat("a", 1_000_000) + data_1_000_000_a := strings.repeat("a", 1_000_000, context.temp_allocator) digest: [hash.MAX_DIGEST_SIZE]byte test_vectors := []struct{ @@ -496,16 +490,14 @@ test_hash :: proc(t: ^testing.T) { dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.hash, - fmt.tprintf( - "%s/incremental: Expected: %s for input of %s, but got %s instead", - algo_name, - v.hash, - v.str, - dst_str, - ), + "%s/incremental: Expected: %s for input of %s, but got %s instead", + algo_name, + v.hash, + v.str, + dst_str, ) } @@ -521,25 +513,21 @@ test_hash :: proc(t: ^testing.T) { // still correct. digest_sz := hash.DIGEST_SIZES[algo] block_sz := hash.BLOCK_SIZES[algo] - tc.expect( + testing.expectf( t, digest_sz <= hash.MAX_DIGEST_SIZE, - fmt.tprintf( - "%s: Digest size %d exceeds max %d", - algo_name, - digest_sz, - hash.MAX_DIGEST_SIZE, - ), + "%s: Digest size %d exceeds max %d", + algo_name, + digest_sz, + hash.MAX_DIGEST_SIZE, ) - tc.expect( + testing.expectf( t, block_sz <= hash.MAX_BLOCK_SIZE, - fmt.tprintf( - "%s: Block size %d exceeds max %d", - algo_name, - block_sz, - hash.MAX_BLOCK_SIZE, - ), + "%s: Block size %d exceeds max %d", + algo_name, + block_sz, + hash.MAX_BLOCK_SIZE, ) // Exercise most of the happy-path for the high level interface. @@ -553,15 +541,13 @@ test_hash :: proc(t: ^testing.T) { a_str := string(hex.encode(digest_a, context.temp_allocator)) b_str := string(hex.encode(digest_b, context.temp_allocator)) - tc.expect( + testing.expectf( t, a_str == b_str, - fmt.tprintf( - "%s/cmp: Expected: %s (hash_stream) == %s (hash_bytes)", - algo_name, - a_str, - b_str, - ), + "%s/cmp: Expected: %s (hash_stream) == %s (hash_bytes)", + algo_name, + a_str, + b_str, ) // Exercise the rolling digest functionality, which also covers @@ -571,25 +557,21 @@ test_hash :: proc(t: ^testing.T) { api_algo := hash.algorithm(&ctx) api_digest_size := hash.digest_size(&ctx) - tc.expect( + testing.expectf( t, algo == api_algo, - fmt.tprintf( - "%s/algorithm: Expected: %v but got %v instead", - algo_name, - algo, - api_algo, - ), + "%s/algorithm: Expected: %v but got %v instead", + algo_name, + algo, + api_algo, ) - tc.expect( + testing.expectf( t, hash.DIGEST_SIZES[algo] == api_digest_size, - fmt.tprintf( - "%s/digest_size: Expected: %d but got %d instead", - algo_name, - hash.DIGEST_SIZES[algo], - api_digest_size, - ), + "%s/digest_size: Expected: %d but got %d instead", + algo_name, + hash.DIGEST_SIZES[algo], + api_digest_size, ) hash.update(&ctx, digest_a) @@ -604,16 +586,14 @@ test_hash :: proc(t: ^testing.T) { b_str = string(hex.encode(digest_b, context.temp_allocator)) c_str := string(hex.encode(digest_c, context.temp_allocator)) - tc.expect( + testing.expectf( t, a_str == b_str && b_str == c_str, - fmt.tprintf( - "%s/rolling: Expected: %s (first) == %s (second) == %s (third)", - algo_name, - a_str, - b_str, - c_str, - ), + "%s/rolling: Expected: %s (first) == %s (second) == %s (third)", + algo_name, + a_str, + b_str, + c_str, ) } -} +} \ No newline at end of file diff --git a/tests/core/crypto/test_core_crypto_kdf.odin b/tests/core/crypto/test_core_crypto_kdf.odin index 73177d8be..247529e65 100644 --- a/tests/core/crypto/test_core_crypto_kdf.odin +++ b/tests/core/crypto/test_core_crypto_kdf.odin @@ -2,28 +2,14 @@ package test_core_crypto import "base:runtime" import "core:encoding/hex" -import "core:fmt" import "core:testing" - import "core:crypto/hash" import "core:crypto/hkdf" import "core:crypto/pbkdf2" -import tc "tests:common" - -@(test) -test_kdf :: proc(t: ^testing.T) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - tc.log(t, "Testing KDFs") - - test_hkdf(t) - test_pbkdf2(t) -} - @(test) test_hkdf :: proc(t: ^testing.T) { - tc.log(t, "Testing HKDF") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() tmp: [128]byte // Good enough. @@ -70,25 +56,23 @@ test_hkdf :: proc(t: ^testing.T) { dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.okm, - fmt.tprintf( - "HKDF-%s: Expected: %s for input of (%s, %s, %s), but got %s instead", - algo_name, - v.okm, - v.ikm, - v.salt, - v.info, - dst_str, - ), + "HKDF-%s: Expected: %s for input of (%s, %s, %s), but got %s instead", + algo_name, + v.okm, + v.ikm, + v.salt, + v.info, + dst_str, ) } } @(test) test_pbkdf2 :: proc(t: ^testing.T) { - tc.log(t, "Testing PBKDF2") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() tmp: [64]byte // 512-bits is enough for every output for now. @@ -174,18 +158,16 @@ test_pbkdf2 :: proc(t: ^testing.T) { dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.dk, - fmt.tprintf( - "HMAC-%s: Expected: %s for input of (%s, %s, %d), but got %s instead", - algo_name, - v.dk, - v.password, - v.salt, - v.iterations, - dst_str, - ), + "HMAC-%s: Expected: %s for input of (%s, %s, %d), but got %s instead", + algo_name, + v.dk, + v.password, + v.salt, + v.iterations, + dst_str, ) } } diff --git a/tests/core/crypto/test_core_crypto_mac.odin b/tests/core/crypto/test_core_crypto_mac.odin index f2eeacb19..ed95ba0ad 100644 --- a/tests/core/crypto/test_core_crypto_mac.odin +++ b/tests/core/crypto/test_core_crypto_mac.odin @@ -2,30 +2,17 @@ package test_core_crypto import "base:runtime" import "core:encoding/hex" -import "core:fmt" import "core:mem" import "core:testing" - import "core:crypto/hash" import "core:crypto/hmac" import "core:crypto/poly1305" import "core:crypto/siphash" -import tc "tests:common" - -@(test) -test_mac :: proc(t: ^testing.T) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - tc.log(t, "Testing MACs") - - test_hmac(t) - test_poly1305(t) - test_siphash_2_4(t) -} - @(test) test_hmac :: proc(t: ^testing.T) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + // Test cases pulled out of RFC 6234, note that HMAC is a generic // construct so as long as the underlying hash is correct and all // the code paths are covered the implementation is "fine", so @@ -86,40 +73,36 @@ test_hmac :: proc(t: ^testing.T) { msg_str := string(hex.encode(msg, context.temp_allocator)) dst_str := string(hex.encode(dst[:tag_len], context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == expected_str, - fmt.tprintf( - "%s/incremental: Expected: %s for input of %s - %s, but got %s instead", - algo_name, - tags_sha256[i], - key_str, - msg_str, - dst_str, - ), + "%s/incremental: Expected: %s for input of %s - %s, but got %s instead", + algo_name, + tags_sha256[i], + key_str, + msg_str, + dst_str, ) hmac.sum(algo, dst, msg, key) oneshot_str := string(hex.encode(dst[:tag_len], context.temp_allocator)) - tc.expect( + testing.expectf( t, oneshot_str == expected_str, - fmt.tprintf( - "%s/oneshot: Expected: %s for input of %s - %s, but got %s instead", - algo_name, - tags_sha256[i], - key_str, - msg_str, - oneshot_str, - ), + "%s/oneshot: Expected: %s for input of %s - %s, but got %s instead", + algo_name, + tags_sha256[i], + key_str, + msg_str, + oneshot_str, ) } } @(test) test_poly1305 :: proc(t: ^testing.T) { - tc.log(t, "Testing poly1305") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() // Test cases taken from poly1305-donna. key := [poly1305.KEY_SIZE]byte { @@ -157,16 +140,17 @@ test_poly1305 :: proc(t: ^testing.T) { // Verify - oneshot + compare ok := poly1305.verify(tag[:], msg[:], key[:]) - tc.expect(t, ok, "oneshot verify call failed") + testing.expect(t, ok, "oneshot verify call failed") // Sum - oneshot derived_tag: [poly1305.TAG_SIZE]byte poly1305.sum(derived_tag[:], msg[:], key[:]) derived_tag_str := string(hex.encode(derived_tag[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_tag_str == tag_str, - fmt.tprintf("Expected %s for sum(msg, key), but got %s instead", tag_str, derived_tag_str), + "Expected %s for sum(msg, key), but got %s instead", + tag_str, derived_tag_str, ) // Incremental @@ -182,21 +166,16 @@ test_poly1305 :: proc(t: ^testing.T) { } poly1305.final(&ctx, derived_tag[:]) derived_tag_str = string(hex.encode(derived_tag[:], context.temp_allocator)) - tc.expect( + testing.expectf( t, derived_tag_str == tag_str, - fmt.tprintf( - "Expected %s for init/update/final - incremental, but got %s instead", - tag_str, - derived_tag_str, - ), + "Expected %s for init/update/final - incremental, but got %s instead", + tag_str, derived_tag_str, ) } @(test) test_siphash_2_4 :: proc(t: ^testing.T) { - tc.log(t, "Testing SipHash-2-4") - // Test vectors from // https://github.com/veorq/SipHash/blob/master/vectors.h test_vectors := [?]u64 { @@ -225,6 +204,7 @@ test_siphash_2_4 :: proc(t: ^testing.T) { for i in 0 ..< len(test_vectors) { data := make([]byte, i) + defer delete(data) for j in 0 ..< i { data[j] = byte(j) } @@ -232,15 +212,13 @@ test_siphash_2_4 :: proc(t: ^testing.T) { vector := test_vectors[i] computed := siphash.sum_2_4(data[:], key[:]) - tc.expect( + testing.expectf( t, computed == vector, - fmt.tprintf( - "Expected: 0x%x for input of %v, but got 0x%x instead", - vector, - data, - computed, - ), + "Expected: 0x%x for input of %v, but got 0x%x instead", + vector, + data, + computed, ) } } diff --git a/tests/core/crypto/test_core_crypto_sha3_variants.odin b/tests/core/crypto/test_core_crypto_sha3_variants.odin index 8e44996bc..c11868e72 100644 --- a/tests/core/crypto/test_core_crypto_sha3_variants.odin +++ b/tests/core/crypto/test_core_crypto_sha3_variants.odin @@ -2,30 +2,14 @@ package test_core_crypto import "base:runtime" import "core:encoding/hex" -import "core:fmt" import "core:testing" - import "core:crypto/kmac" import "core:crypto/shake" import "core:crypto/tuplehash" -import tc "tests:common" - -@(test) -test_sha3_variants :: proc(t: ^testing.T) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - tc.log(t, "Testing SHA3 derived functions") - - test_shake(t) - test_cshake(t) - test_tuplehash(t) - test_kmac(t) -} - @(test) test_shake :: proc(t: ^testing.T) { - tc.log(t, "Testing SHAKE") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() test_vectors := []struct { sec_strength: int, @@ -67,23 +51,21 @@ test_shake :: proc(t: ^testing.T) { dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.output, - fmt.tprintf( - "SHAKE%d: Expected: %s for input of %s, but got %s instead", - v.sec_strength, - v.output, - v.str, - dst_str, - ), + "SHAKE%d: Expected: %s for input of %s, but got %s instead", + v.sec_strength, + v.output, + v.str, + dst_str, ) } } @(test) test_cshake :: proc(t: ^testing.T) { - tc.log(t, "Testing cSHAKE") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() test_vectors := []struct { sec_strength: int, @@ -135,29 +117,27 @@ test_cshake :: proc(t: ^testing.T) { shake.init_cshake_256(&ctx, domainsep) } - data, _ := hex.decode(transmute([]byte)(v.str)) + data, _ := hex.decode(transmute([]byte)(v.str), context.temp_allocator) shake.write(&ctx, data) shake.read(&ctx, dst) dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.output, - fmt.tprintf( - "cSHAKE%d: Expected: %s for input of %s, but got %s instead", - v.sec_strength, - v.output, - v.str, - dst_str, - ), + "cSHAKE%d: Expected: %s for input of %s, but got %s instead", + v.sec_strength, + v.output, + v.str, + dst_str, ) } } @(test) test_tuplehash :: proc(t: ^testing.T) { - tc.log(t, "Testing TupleHash(XOF)") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() test_vectors := []struct { sec_strength: int, @@ -317,7 +297,7 @@ test_tuplehash :: proc(t: ^testing.T) { } for e in v.tuple { - data, _ := hex.decode(transmute([]byte)(e)) + data, _ := hex.decode(transmute([]byte)(e), context.temp_allocator) tuplehash.write_element(&ctx, data) } @@ -332,24 +312,22 @@ test_tuplehash :: proc(t: ^testing.T) { dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.output, - fmt.tprintf( - "TupleHash%s%d: Expected: %s for input of %v, but got %s instead", - suffix, - v.sec_strength, - v.output, - v.tuple, - dst_str, - ), + "TupleHash%s%d: Expected: %s for input of %v, but got %s instead", + suffix, + v.sec_strength, + v.output, + v.tuple, + dst_str, ) } } @(test) test_kmac :: proc(t:^testing.T) { - tc.log(t, "Testing KMAC") + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() test_vectors := []struct { sec_strength: int, @@ -410,7 +388,7 @@ test_kmac :: proc(t:^testing.T) { for v in test_vectors { dst := make([]byte, len(v.output) / 2, context.temp_allocator) - key, _ := hex.decode(transmute([]byte)(v.key)) + key, _ := hex.decode(transmute([]byte)(v.key), context.temp_allocator) domainsep := transmute([]byte)(v.domainsep) ctx: kmac.Context @@ -421,24 +399,22 @@ test_kmac :: proc(t:^testing.T) { kmac.init_256(&ctx, key, domainsep) } - data, _ := hex.decode(transmute([]byte)(v.msg)) + data, _ := hex.decode(transmute([]byte)(v.msg), context.temp_allocator) kmac.update(&ctx, data) kmac.final(&ctx, dst) dst_str := string(hex.encode(dst, context.temp_allocator)) - tc.expect( + testing.expectf( t, dst_str == v.output, - fmt.tprintf( - "KMAC%d: Expected: %s for input of (%s, %s, %s), but got %s instead", - v.sec_strength, - v.output, - v.key, - v.domainsep, - v.msg, - dst_str, - ), + "KMAC%d: Expected: %s for input of (%s, %s, %s), but got %s instead", + v.sec_strength, + v.output, + v.key, + v.domainsep, + v.msg, + dst_str, ) } -} +} \ No newline at end of file diff --git a/tests/core/crypto/test_crypto_benchmark.odin b/tests/core/crypto/test_crypto_benchmark.odin index 600dc9ade..5bfc579bd 100644 --- a/tests/core/crypto/test_crypto_benchmark.odin +++ b/tests/core/crypto/test_crypto_benchmark.odin @@ -2,7 +2,7 @@ package test_core_crypto import "base:runtime" import "core:encoding/hex" -import "core:fmt" +import "core:log" import "core:testing" import "core:time" @@ -13,16 +13,11 @@ import "core:crypto/ed25519" import "core:crypto/poly1305" import "core:crypto/x25519" -import tc "tests:common" - // Cryptographic primitive benchmarks. @(test) bench_crypto :: proc(t: ^testing.T) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - - fmt.println("Starting benchmarks:") - bench_chacha20(t) bench_poly1305(t) bench_chacha20poly1305(t) @@ -157,8 +152,8 @@ _benchmark_aes256_gcm :: proc( } benchmark_print :: proc(name: string, options: ^time.Benchmark_Options) { - fmt.printf( - "\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", + log.infof( + "\n\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s", name, options.rounds, options.processed, @@ -179,19 +174,19 @@ bench_chacha20 :: proc(t: ^testing.T) { } err := time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) name = "ChaCha20 1024 bytes" options.bytes = 1024 err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) name = "ChaCha20 65536 bytes" options.bytes = 65536 err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) } @@ -206,13 +201,13 @@ bench_poly1305 :: proc(t: ^testing.T) { } err := time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) name = "Poly1305 1024 zero bytes" options.bytes = 1024 err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) } @@ -227,19 +222,19 @@ bench_chacha20poly1305 :: proc(t: ^testing.T) { } err := time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) name = "chacha20poly1305 1024 bytes" options.bytes = 1024 err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) name = "chacha20poly1305 65536 bytes" options.bytes = 65536 err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) } @@ -265,19 +260,19 @@ bench_aes256_gcm :: proc(t: ^testing.T) { context.user_ptr = &ctx err := time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) name = "AES256-GCM 1024 bytes" options.bytes = 1024 err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) name = "AES256-GCM 65536 bytes" options.bytes = 65536 err = time.benchmark(options, context.allocator) - tc.expect(t, err == nil, name) + testing.expect(t, err == nil, name) benchmark_print(name, options) } @@ -293,12 +288,9 @@ bench_ed25519 :: proc(t: ^testing.T) { assert(ok, "private key should deserialize") } elapsed := time.since(start) - tc.log( - t, - fmt.tprintf( - "ed25519.private_key_set_bytes: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ), + log.infof( + "ed25519.private_key_set_bytes: ~%f us/op", + time.duration_microseconds(elapsed) / iters, ) pub_bytes := priv_key._pub_key._b[:] // "I know what I am doing" @@ -309,12 +301,9 @@ bench_ed25519 :: proc(t: ^testing.T) { assert(ok, "public key should deserialize") } elapsed = time.since(start) - tc.log( - t, - fmt.tprintf( - "ed25519.public_key_set_bytes: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ), + log.infof( + "ed25519.public_key_set_bytes: ~%f us/op", + time.duration_microseconds(elapsed) / iters, ) msg := "Got a job for you, 621." @@ -325,7 +314,10 @@ bench_ed25519 :: proc(t: ^testing.T) { ed25519.sign(&priv_key, msg_bytes, sig_bytes[:]) } elapsed = time.since(start) - tc.log(t, fmt.tprintf("ed25519.sign: ~%f us/op", time.duration_microseconds(elapsed) / iters)) + log.infof( + "ed25519.sign: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) start = time.now() for i := 0; i < iters; i = i + 1 { @@ -333,9 +325,9 @@ bench_ed25519 :: proc(t: ^testing.T) { assert(ok, "signature should validate") } elapsed = time.since(start) - tc.log( - t, - fmt.tprintf("ed25519.verify: ~%f us/op", time.duration_microseconds(elapsed) / iters), + log.infof( + "ed25519.verify: ~%f us/op", + time.duration_microseconds(elapsed) / iters, ) } @@ -354,8 +346,8 @@ bench_x25519 :: proc(t: ^testing.T) { } elapsed := time.since(start) - tc.log( - t, - fmt.tprintf("x25519.scalarmult: ~%f us/op", time.duration_microseconds(elapsed) / iters), + log.infof( + "x25519.scalarmult: ~%f us/op", + time.duration_microseconds(elapsed) / iters, ) } diff --git a/tests/core/image/test_core_image.odin b/tests/core/image/test_core_image.odin index e3bef6c9d..7fc04ce6b 100644 --- a/tests/core/image/test_core_image.odin +++ b/tests/core/image/test_core_image.odin @@ -23,9 +23,8 @@ import "core:hash" import "core:strings" import "core:mem" import "core:time" -import "base:runtime" -TEST_SUITE_PATH :: "assets/PNG" +TEST_SUITE_PATH :: ODIN_ROOT + "tests/core/assets/PNG/" I_Error :: image.Error @@ -1455,10 +1454,9 @@ png_test_no_postproc :: proc(t: ^testing.T) { } run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { - context = runtime.default_context() - for file in suite { - test_file := strings.concatenate({TEST_SUITE_PATH, "/", file.file, ".png"}, context.temp_allocator) + test_file := strings.concatenate({TEST_SUITE_PATH, file.file, ".png"}) + defer delete(test_file) img: ^png.Image err: png.Error @@ -1467,10 +1465,6 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { for test in file.tests { count += 1 - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - img, err = png.load(test_file, test.options) passed := (test.expected_error == nil && err == nil) || (test.expected_error == err) From 568b746c9897ea4b343d454309d6d1275a4a5320 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 16:39:20 -0400 Subject: [PATCH 050/270] Fix indentation --- core/mem/rollback_stack_allocator.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index bf397d2c8..6fa86ab0b 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -292,7 +292,7 @@ rollback_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mo } case .Free: - err = rb_free(stack, old_memory) + err = rb_free(stack, old_memory) case .Free_All: rb_free_all(stack) From 0f675fa4368253e8ebdf9dad325bbba2101ecd22 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 19:36:20 -0400 Subject: [PATCH 051/270] Use `uintptr` where applicable in `mem.Rollback_Stack` --- core/mem/rollback_stack_allocator.odin | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index 6fa86ab0b..104ad0e95 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -38,19 +38,19 @@ ROLLBACK_STACK_DEFAULT_BLOCK_SIZE :: 4 * Megabyte // // This is because allocations over the block size are not split up if the item // within is freed; they are immediately returned to the block allocator. -ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE :: 1 * Gigabyte +ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE :: 2 * Gigabyte Rollback_Stack_Header :: bit_field u64 { - prev_offset: int | 32, - is_free: bool | 1, - prev_ptr: int | 31, + prev_offset: uintptr | 32, + is_free: bool | 1, + prev_ptr: uintptr | 31, } Rollback_Stack_Block :: struct { next_block: ^Rollback_Stack_Block, last_alloc: rawptr, - offset: int, + offset: uintptr, buffer: []byte, } @@ -65,7 +65,7 @@ Rollback_Stack :: struct { @(require_results) rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { start := cast(uintptr)raw_data(block.buffer) - end := cast(uintptr)raw_data(block.buffer) + cast(uintptr)block.offset + end := cast(uintptr)raw_data(block.buffer) + block.offset return start < cast(uintptr)ptr && cast(uintptr)ptr <= end } @@ -150,8 +150,8 @@ rb_free_all :: proc(stack: ^Rollback_Stack) { rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) { if ptr != nil { if block, _, ok := rb_find_last_alloc(stack, ptr); ok { - if block.offset + (size - old_size) < len(block.buffer) { - block.offset += (size - old_size) + if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) { + block.offset += cast(uintptr)size - cast(uintptr)old_size #no_bounds_check return (cast([^]byte)ptr)[:size], nil } } @@ -180,10 +180,10 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt parent.next_block = block } - start := cast(uintptr)raw_data(block.buffer) + cast(uintptr)block.offset - padding := calc_padding_with_header(start, cast(uintptr)alignment, size_of(Rollback_Stack_Header)) + start := cast(uintptr)raw_data(block.buffer) + block.offset + padding := cast(uintptr)calc_padding_with_header(start, cast(uintptr)alignment, size_of(Rollback_Stack_Header)) - if block.offset + padding + size > len(block.buffer) { + if block.offset + padding + cast(uintptr)size > cast(uintptr)len(block.buffer) { parent = block continue } @@ -193,17 +193,17 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt header^ = { prev_offset = block.offset, - prev_ptr = max(0, cast(int)(cast(uintptr)block.last_alloc - cast(uintptr)raw_data(block.buffer))), + prev_ptr = uintptr(0) if block.last_alloc == nil else cast(uintptr)block.last_alloc - cast(uintptr)raw_data(block.buffer), is_free = false, } block.last_alloc = ptr - block.offset += padding + size + block.offset += padding + cast(uintptr)size if len(block.buffer) > stack.block_size { // This block exceeds the allocator's standard block size and is considered a singleton. // Prevent any further allocations on it. - block.offset = len(block.buffer) + block.offset = cast(uintptr)len(block.buffer) } #no_bounds_check return ptr[:size], nil @@ -242,7 +242,7 @@ rollback_stack_init_dynamic :: proc( block_allocator := context.allocator, ) -> Allocator_Error { assert(block_size >= size_of(Rollback_Stack_Header) + size_of(rawptr), "Rollback Stack Allocator block size is too small.") - assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 1 gigabyte.") + assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.") block := rb_make_block(block_size, block_allocator) or_return From 09ef08f0354cd68ca589e3c6d094f08d08a48d78 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 18:23:05 -0400 Subject: [PATCH 052/270] Add more sanity checking to `mem.Rollback_Stack` --- core/mem/rollback_stack_allocator.odin | 31 +++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index 104ad0e95..a3f6647cf 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -150,6 +150,10 @@ rb_free_all :: proc(stack: ^Rollback_Stack) { rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) { if ptr != nil { if block, _, ok := rb_find_last_alloc(stack, ptr); ok { + // `block.offset` should never underflow because it is contingent + // on `old_size` in the first place, assuming sane arguments. + assert(block.offset >= cast(uintptr)old_size, "Rollback Stack Allocator received invalid `old_size`.") + if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) { block.offset += cast(uintptr)size - cast(uintptr)old_size #no_bounds_check return (cast([^]byte)ptr)[:size], nil @@ -169,6 +173,10 @@ rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byte, err: Allocator_Error) { parent: ^Rollback_Stack_Block for block := stack.head; /**/; block = block.next_block { + when !ODIN_DISABLE_ASSERT { + allocated_new_block: bool + } + if block == nil { if stack.block_allocator.procedure == nil { return nil, .Out_Of_Memory @@ -178,12 +186,20 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt new_block_size := max(minimum_size_required, stack.block_size) block = rb_make_block(new_block_size, stack.block_allocator) or_return parent.next_block = block + when !ODIN_DISABLE_ASSERT { + allocated_new_block = true + } } start := cast(uintptr)raw_data(block.buffer) + block.offset padding := cast(uintptr)calc_padding_with_header(start, cast(uintptr)alignment, size_of(Rollback_Stack_Header)) if block.offset + padding + cast(uintptr)size > cast(uintptr)len(block.buffer) { + when !ODIN_DISABLE_ASSERT { + if allocated_new_block { + panic("Rollback Stack Allocator allocated a new block but did not use it.") + } + } parent = block continue } @@ -223,9 +239,9 @@ rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stac } -rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte) { +rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, location := #caller_location) { MIN_SIZE :: size_of(Rollback_Stack_Block) + size_of(Rollback_Stack_Header) + size_of(rawptr) - assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.") + assert(len(buffer) >= MIN_SIZE, "User-provided buffer to Rollback Stack Allocator is too small.", location) block := cast(^Rollback_Stack_Block)raw_data(buffer) block^ = {} @@ -240,9 +256,10 @@ rollback_stack_init_dynamic :: proc( stack: ^Rollback_Stack, block_size := ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, block_allocator := context.allocator, + location := #caller_location, ) -> Allocator_Error { - assert(block_size >= size_of(Rollback_Stack_Header) + size_of(rawptr), "Rollback Stack Allocator block size is too small.") - assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.") + assert(block_size >= size_of(Rollback_Stack_Header) + size_of(rawptr), "Rollback Stack Allocator block size is too small.", location) + assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.", location) block := rb_make_block(block_size, block_allocator) or_return @@ -284,7 +301,8 @@ rollback_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mo switch mode { case .Alloc, .Alloc_Non_Zeroed: - assert(is_power_of_two(cast(uintptr)alignment), "alignment must be a power of two", location) + assert(size >= 0, "Size must be positive or zero.", location) + assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location) result = rb_alloc(stack, size, alignment) or_return if mode == .Alloc { @@ -298,6 +316,9 @@ rollback_stack_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mo rb_free_all(stack) case .Resize, .Resize_Non_Zeroed: + assert(size >= 0, "Size must be positive or zero.", location) + assert(old_size >= 0, "Old size must be positive or zero.", location) + assert(is_power_of_two(cast(uintptr)alignment), "Alignment must be a power of two.", location) result = rb_resize(stack, old_memory, old_size, size, alignment) or_return #no_bounds_check if mode == .Resize && size > old_size { From 1afc235359334aa2b7df0e4b4c79386763076e75 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 19:14:28 -0400 Subject: [PATCH 053/270] Use plain sort for `internal_tests` --- core/testing/runner.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index ce5aa112a..1a9fc3ddc 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -192,7 +192,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { fmt.assertf(it.p != nil, "Test %s.%s has procedure.", it.pkg, it.name) } - slice.stable_sort_by(internal_tests, proc(a, b: Internal_Test) -> bool { + slice.sort_by(internal_tests, proc(a, b: Internal_Test) -> bool { if a.pkg == b.pkg { return a.name < b.name } else { From eadfbb13185a94a30d87c52eb50e83fcec8e0d52 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 19:49:56 -0400 Subject: [PATCH 054/270] Forbid singleton allocations from shrinking their block offset --- core/mem/rollback_stack_allocator.odin | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index a3f6647cf..b86f514ec 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -155,7 +155,11 @@ rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment assert(block.offset >= cast(uintptr)old_size, "Rollback Stack Allocator received invalid `old_size`.") if block.offset + cast(uintptr)size - cast(uintptr)old_size < cast(uintptr)len(block.buffer) { - block.offset += cast(uintptr)size - cast(uintptr)old_size + // Prevent singleton allocations from fragmenting by forbidding + // them to shrink, removing the possibility of overflow bugs. + if len(block.buffer) <= stack.block_size { + block.offset += cast(uintptr)size - cast(uintptr)old_size + } #no_bounds_check return (cast([^]byte)ptr)[:size], nil } } From dffc3af86c1c101e1a7c465478ae5454f811fb89 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 19:55:45 -0400 Subject: [PATCH 055/270] Remove `safe_heap_allocator` from test runner I was under the impression that the default `context.allocator` was not thread-safe, but I've been told that this is not the case. --- core/testing/runner.odin | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 1a9fc3ddc..3a024d090 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -277,10 +277,6 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { fmt.assertf(alloc_error == nil, "Error allocating memory for task data slots: %v", alloc_error) defer delete(task_data_slots) - safe_heap: mem.Mutex_Allocator - mem.mutex_allocator_init(&safe_heap, context.allocator) - safe_heap_allocator := mem.mutex_allocator(&safe_heap) - // Tests rotate through these allocators as they finish. task_allocators: []mem.Rollback_Stack = --- task_allocators, alloc_error = make([]mem.Rollback_Stack, thread_count) @@ -295,7 +291,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } #no_bounds_check for i in 0 ..< thread_count { - alloc_error = mem.rollback_stack_init(&task_allocators[i], PER_THREAD_MEMORY, block_allocator = safe_heap_allocator) + alloc_error = mem.rollback_stack_init(&task_allocators[i], PER_THREAD_MEMORY) fmt.assertf(alloc_error == nil, "Error allocating memory for task allocator #%i: %v", i, alloc_error) when TRACKING_MEMORY { mem.tracking_allocator_init(&task_memory_trackers[i], mem.rollback_stack_allocator(&task_allocators[i])) @@ -339,11 +335,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { // NOTE(Feoramund): This is the allocator that will be used by threads to // persist log messages past their lifetimes. It has its own variable name - // in the event it needs to be changed from `safe_heap_allocator` without + // in the event it needs to be changed from `context.allocator` without // digging through the source to divine everywhere it is used for that. - shared_log_allocator := safe_heap_allocator - - context.allocator = safe_heap_allocator + shared_log_allocator := context.allocator context.logger = { procedure = runner_logger_proc, From 89d8df28bec7b5aebaac1e38ef642754ef57e1f1 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 28 May 2024 19:58:35 -0400 Subject: [PATCH 056/270] Combine multi-line attributes onto one line --- core/mem/rollback_stack_allocator.odin | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index b86f514ec..75b3cd745 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -61,16 +61,14 @@ Rollback_Stack :: struct { } -@(private="file") -@(require_results) +@(private="file", require_results) rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { start := cast(uintptr)raw_data(block.buffer) end := cast(uintptr)raw_data(block.buffer) + block.offset return start < cast(uintptr)ptr && cast(uintptr)ptr <= end } -@(private="file") -@(require_results) +@(private="file", require_results) rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( parent: ^Rollback_Stack_Block, block: ^Rollback_Stack_Block, @@ -87,8 +85,7 @@ rb_find_ptr :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( return nil, nil, nil, .Invalid_Pointer } -@(private="file") -@(require_results) +@(private="file", require_results) rb_find_last_alloc :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> ( block: ^Rollback_Stack_Block, header: ^Rollback_Stack_Header, @@ -113,8 +110,7 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_ } } -@(private="file") -@(require_results) +@(private="file", require_results) rb_free :: proc(stack: ^Rollback_Stack, ptr: rawptr) -> Allocator_Error { parent, block, header := rb_find_ptr(stack, ptr) or_return if header.is_free { @@ -145,8 +141,7 @@ rb_free_all :: proc(stack: ^Rollback_Stack) { stack.head.offset = 0 } -@(private="file") -@(require_results) +@(private="file", require_results) rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment: int) -> (result: []byte, err: Allocator_Error) { if ptr != nil { if block, _, ok := rb_find_last_alloc(stack, ptr); ok { @@ -172,8 +167,7 @@ rb_resize :: proc(stack: ^Rollback_Stack, ptr: rawptr, old_size, size, alignment return } -@(private="file") -@(require_results) +@(private="file", require_results) rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byte, err: Allocator_Error) { parent: ^Rollback_Stack_Block for block := stack.head; /**/; block = block.next_block { @@ -232,8 +226,7 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt return nil, .Out_Of_Memory } -@(private="file") -@(require_results) +@(private="file", require_results) rb_make_block :: proc(size: int, allocator: Allocator) -> (block: ^Rollback_Stack_Block, err: Allocator_Error) { buffer := runtime.mem_alloc(size_of(Rollback_Stack_Block) + size, align_of(Rollback_Stack_Block), allocator) or_return From a1c5bebac72d14711c317e393d337143df17ca87 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 15:36:22 -0400 Subject: [PATCH 057/270] Fix ANSI redraw eating last log line --- core/testing/reporting.odin | 6 +++++- core/testing/runner.odin | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/core/testing/reporting.odin b/core/testing/reporting.odin index d06681c2d..fba67d67f 100644 --- a/core/testing/reporting.odin +++ b/core/testing/reporting.odin @@ -271,7 +271,11 @@ needs_to_redraw :: proc(report: Report) -> bool { } draw_status_bar :: proc(w: io.Writer, threads_string: string, total_done_count, total_test_count: int) { - if total_done_count != total_test_count { + if total_done_count == total_test_count { + // All tests are done; print a blank line to maintain the same height + // of the progress report. + fmt.wprintln(w) + } else { fmt.wprintfln(w, "%s % 4i/% 4i :: total", threads_string, diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 3a024d090..734b143a6 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -583,8 +583,14 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { finished_in := time.since(start_time) + when !FANCY_OUTPUT { + // One line to space out the results, since we don't have the status + // bar in plain mode. + fmt.wprintln(batch_writer) + } + fmt.wprintf(batch_writer, - "\nFinished %i test%s in %v.", + "Finished %i test%s in %v.", total_done_count, "" if total_done_count == 1 else "s", finished_in) From dcfda195d2c1e215dd506253703fc04e660c9fc6 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 15:36:50 -0400 Subject: [PATCH 058/270] Send terminal control code to `STDOUT` instead `STDERR` might be redirected, and this code signals to the terminal to show the cursor again. Otherwise, the cursor will be invisible. --- core/testing/runner.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 734b143a6..3b4e59e48 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -667,7 +667,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } - fmt.wprint(batch_writer, ansi.CSI + ansi.DECTCEM_SHOW) + fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW) fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer)) From e11f3d252037500197672db990e8884faee230e4 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 15:47:01 -0400 Subject: [PATCH 059/270] Fix missing `-` for define in `tests/core/build.bat` --- tests/core/build.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/build.bat b/tests/core/build.bat index fc5e2dc3f..29603dc68 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -11,7 +11,7 @@ echo --- echo --- echo Running core:container tests echo --- -%PATH_TO_ODIN% test container %COMMON% define:test_progress_width=4 -out:test_core_container.exe || exit /b +%PATH_TO_ODIN% test container %COMMON% -define:test_progress_width=4 -out:test_core_container.exe || exit /b echo --- echo Running core:crypto tests From b7e1ae7073a96099c9a5b18fa6fd44a561823691 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 15:50:16 -0400 Subject: [PATCH 060/270] Change test runner options to `SCREAMING_SNAKE_CASE` This commit also changes the name of `test_select` to `ODIN_TEST_NAMES`, to better conform with the already-existing `-test-name:` option. --- core/testing/runner.odin | 22 +++++++++++----------- tests/core/Makefile | 8 ++++---- tests/core/build.bat | 12 ++++++------ tests/core/image/build.bat | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 3b4e59e48..c79d5537b 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -23,19 +23,19 @@ _ :: pkg_log _ :: strings // Specify how many threads to use when running tests. -TEST_THREADS : int : #config(test_threads, 0) +TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0) // Track the memory used by each test. -TRACKING_MEMORY : bool : #config(test_track_memory, false) +TRACKING_MEMORY : bool : #config(ODIN_TEST_TRACK_MEMORY, false) // Specify how much memory each thread allocator starts with. -PER_THREAD_MEMORY : int : #config(test_thread_memory, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) +PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) // Select a specific set of tests to run by name. -TEST_SELECT : string : #config(test_select, "") +TEST_NAMES : string : #config(ODIN_TEST_NAMES, "") // Show the fancy animated progress report. -FANCY_OUTPUT : bool : #config(test_fancy, true) +FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true) // Copy failed tests to the clipboard when done. -USE_CLIPBOARD : bool : #config(test_clipboard, false) +USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false) // How many test results to show at a time per package. -PROGRESS_WIDTH : int : #config(test_progress_width, 24) +PROGRESS_WIDTH : int : #config(ODIN_TEST_PROGRESS_WIDTH, 24) end_t :: proc(t: ^T) { @@ -122,12 +122,12 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { alloc_error: mem.Allocator_Error - when TEST_SELECT != "" { + when TEST_NAMES != "" { select_internal_tests: [dynamic]Internal_Test defer delete(select_internal_tests) { - index_list := TEST_SELECT + index_list := TEST_NAMES for selector in strings.split_iterator(&index_list, ",") { // Temp allocator is fine since we just need to identify which test it's referring to. split_selector := strings.split(selector, ".", context.temp_allocator) @@ -640,7 +640,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { if total_success_count > 0 { when USE_CLIPBOARD { clipboard_writer := io.to_writer(bytes.buffer_to_stream(&clipboard_buffer)) - fmt.wprint(clipboard_writer, "-define:test_select=") + fmt.wprint(clipboard_writer, "-define:ODIN_TEST_NAMES=") for test_index in sorted_failed_test_reasons { #no_bounds_check it := internal_tests[test_index] fmt.wprintf(clipboard_writer, "%s.%s,", it.pkg, it.name) @@ -655,7 +655,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { "" if total_failure_count == 1 else "s", " has" if total_failure_count == 1 else "s have") } else { - fmt.wprintf(batch_writer, "\nTo run only the failed test%s, use:\n\t-define:test_select=", + fmt.wprintf(batch_writer, "\nTo run only the failed test%s, use:\n\t-define:ODIN_TEST_NAMES=", "" if total_failure_count == 1 else "s") for test_index in sorted_failed_test_reasons { #no_bounds_check it := internal_tests[test_index] diff --git a/tests/core/Makefile b/tests/core/Makefile index 4dede0370..13f8ef41b 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,6 +1,6 @@ ODIN=../../odin PYTHON=$(shell which python3) -COMMON=-no-bounds-check -vet -strict-style -define:test_track_memory=true +COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_TRACK_MEMORY=true COLLECTION=-collection:tests=.. all: all_bsd \ @@ -34,13 +34,13 @@ download_test_assets: $(PYTHON) download_assets.py image_test: - $(ODIN) test image $(COMMON) -define:test_progress_width=12 -out:test_core_image + $(ODIN) test image $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=12 -out:test_core_image compress_test: - $(ODIN) test compress $(COMMON) -define:test_progress_width=3 -out:test_core_compress + $(ODIN) test compress $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=3 -out:test_core_compress container_test: - $(ODIN) test container $(COMMON) -define:test_progress_width=4 -out:test_core_container + $(ODIN) test container $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=4 -out:test_core_container crypto_test: $(ODIN) test crypto $(COMMON) -define:test_progress_width=18 -o:speed -out:test_crypto diff --git a/tests/core/build.bat b/tests/core/build.bat index 29603dc68..b63c0f311 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -1,17 +1,17 @@ @echo off -set COMMON=-no-bounds-check -vet -strict-style -define:test_track_memory=true +set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_TRACK_MEMORY=true set COLLECTION=-collection:tests=.. set PATH_TO_ODIN==..\..\odin python3 download_assets.py echo --- echo Running core:compress tests echo --- -%PATH_TO_ODIN% test compress %COMMON% -define:test_progress_width=3 -out:test_core_compress.exe || exit /b +%PATH_TO_ODIN% test compress %COMMON% -define:ODIN_TEST_PROGRESS_WIDTH=3 -out:test_core_compress.exe || exit /b echo --- echo Running core:container tests echo --- -%PATH_TO_ODIN% test container %COMMON% -define:test_progress_width=4 -out:test_core_container.exe || exit /b +%PATH_TO_ODIN% test container %COMMON% -define:ODIN_TEST_PROGRESS_WIDTH=4 -out:test_core_container.exe || exit /b echo --- echo Running core:crypto tests @@ -25,7 +25,7 @@ rem %PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe | %PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b %PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b %PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b -%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe -define:test_threads=1 -define:test_fancy=false || exit /b +%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe -define:ODIN_TEST_THREADS=1 -define:ODIN_TEST_FANCY=false || exit /b %PATH_TO_ODIN% run encoding/hex %COMMON% -out:test_hex.exe || exit /b %PATH_TO_ODIN% run encoding/base64 %COMMON% -out:test_base64.exe || exit /b @@ -42,7 +42,7 @@ echo --- echo --- echo Running core:image tests echo --- -%PATH_TO_ODIN% test image %COMMON% -define:test_progress_width=12 -out:test_core_image.exe || exit /b +%PATH_TO_ODIN% test image %COMMON% -define:ODIN_TEST_PROGRESS_WIDTH=12 -out:test_core_image.exe || exit /b echo --- echo Running core:math tests @@ -107,4 +107,4 @@ echo --- echo --- echo Running core:time tests echo --- -%PATH_TO_ODIN% run time %COMMON% %COLLECTION% -out:test_core_time.exe || exit /b \ No newline at end of file +%PATH_TO_ODIN% run time %COMMON% %COLLECTION% -out:test_core_time.exe || exit /b diff --git a/tests/core/image/build.bat b/tests/core/image/build.bat index 35d1c64e9..5a07971b8 100644 --- a/tests/core/image/build.bat +++ b/tests/core/image/build.bat @@ -1,2 +1,2 @@ @echo off -odin test . -define:test_track_memory=true -define:test_progress_width=12 -vet -strict-style \ No newline at end of file +odin test . -define:ODIN_TEST_TRACK_MEMORY=true -define:ODIN_TEST_PROGRESS_WIDTH=12 -vet -strict-style From c531427ee5d95187d4da1edb1679b67af58b4cbb Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 May 2024 22:18:05 +0200 Subject: [PATCH 061/270] Update -define for `crypto` --- tests/core/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 13f8ef41b..23d0dcf99 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -43,7 +43,7 @@ container_test: $(ODIN) test container $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=4 -out:test_core_container crypto_test: - $(ODIN) test crypto $(COMMON) -define:test_progress_width=18 -o:speed -out:test_crypto + $(ODIN) test crypto $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=18 -o:speed -out:test_crypto strings_test: $(ODIN) run strings $(COMMON) -out:test_core_strings From bf42e39b1ce77da0bc45f64ff35322e618a59b9c Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 16:19:06 -0400 Subject: [PATCH 062/270] Be specific about `int` size for `Rollback_Stack` asserts This should fix tests failing on 32-bit platforms. --- core/mem/rollback_stack_allocator.odin | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index 75b3cd745..d159897cd 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -251,12 +251,16 @@ rollback_stack_init_buffered :: proc(stack: ^Rollback_Stack, buffer: []byte, loc rollback_stack_init_dynamic :: proc( stack: ^Rollback_Stack, - block_size := ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, + block_size : int = ROLLBACK_STACK_DEFAULT_BLOCK_SIZE, block_allocator := context.allocator, location := #caller_location, ) -> Allocator_Error { assert(block_size >= size_of(Rollback_Stack_Header) + size_of(rawptr), "Rollback Stack Allocator block size is too small.", location) - assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.", location) + when size_of(int) > 4 { + // It's impossible to specify an argument in excess when your integer + // size is insufficient; check only on platforms with big enough ints. + assert(block_size <= ROLLBACK_STACK_MAX_HEAD_BLOCK_SIZE, "Rollback Stack Allocators cannot support head blocks larger than 2 gigabytes.", location) + } block := rb_make_block(block_size, block_allocator) or_return From e1a3c0e21d2683804cdfc7644e55ee6ba7a9f0ea Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 16:39:44 -0400 Subject: [PATCH 063/270] Track memory in the test runner by default --- core/testing/runner.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index c79d5537b..6a9bd6d8f 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -25,7 +25,7 @@ _ :: strings // Specify how many threads to use when running tests. TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0) // Track the memory used by each test. -TRACKING_MEMORY : bool : #config(ODIN_TEST_TRACK_MEMORY, false) +TRACKING_MEMORY : bool : #config(ODIN_TEST_TRACK_MEMORY, true) // Specify how much memory each thread allocator starts with. PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) // Select a specific set of tests to run by name. From 49fa66370f2c96e9fc738ccda67bd3b6d12bad71 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 16:55:04 -0400 Subject: [PATCH 064/270] Report test memory usage only if there's an issue Adds new option `ODIN_TEST_ALWAYS_REPORT_MEMORY`, for when you always want to see the memory usage report. --- core/testing/runner.odin | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 6a9bd6d8f..759b23b0f 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -23,19 +23,21 @@ _ :: pkg_log _ :: strings // Specify how many threads to use when running tests. -TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0) +TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0) // Track the memory used by each test. -TRACKING_MEMORY : bool : #config(ODIN_TEST_TRACK_MEMORY, true) +TRACKING_MEMORY : bool : #config(ODIN_TEST_TRACK_MEMORY, true) +// Always report how much memory is used, even when there are no leaks or bad frees. +ALWAYS_REPORT_MEMORY : bool : #config(ODIN_TEST_ALWAYS_REPORT_MEMORY, false) // Specify how much memory each thread allocator starts with. -PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) +PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) // Select a specific set of tests to run by name. -TEST_NAMES : string : #config(ODIN_TEST_NAMES, "") +TEST_NAMES : string : #config(ODIN_TEST_NAMES, "") // Show the fancy animated progress report. -FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true) +FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true) // Copy failed tests to the clipboard when done. -USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false) +USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false) // How many test results to show at a time per package. -PROGRESS_WIDTH : int : #config(ODIN_TEST_PROGRESS_WIDTH, 24) +PROGRESS_WIDTH : int : #config(ODIN_TEST_PROGRESS_WIDTH, 24) end_t :: proc(t: ^T) { @@ -382,8 +384,14 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } when TRACKING_MEMORY { - pkg_log.info("Memory tracking is enabled. Tests will log their memory usage when complete.") + when ALWAYS_REPORT_MEMORY { + pkg_log.info("Memory tracking is enabled. Tests will log their memory usage when complete.") + } else { + pkg_log.info("Memory tracking is enabled. Tests will log their memory usage if there's an issue.") + } pkg_log.info("< Final Mem/ Total Mem> < Peak Mem> (#Free/Alloc) :: [package.test_name]") + } else when ALWAYS_REPORT_MEMORY { + pkg_log.warn("ODIN_TEST_ALWAYS_REPORT_MEMORY is true, but ODIN_TRACK_MEMORY is false.") } start_time := time.now() @@ -397,10 +405,18 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { when TRACKING_MEMORY { #no_bounds_check tracker := &task_memory_trackers[data.allocator_index] - write_memory_report(batch_writer, tracker, data.it.pkg, data.it.name) + when ALWAYS_REPORT_MEMORY { + should_report := true + } else { + should_report := len(tracker.allocation_map) + len(tracker.bad_free_array) > 0 + } - pkg_log.info(bytes.buffer_to_string(&batch_buffer)) - bytes.buffer_reset(&batch_buffer) + if should_report { + write_memory_report(batch_writer, tracker, data.it.pkg, data.it.name) + + pkg_log.info(bytes.buffer_to_string(&batch_buffer)) + bytes.buffer_reset(&batch_buffer) + } mem.tracking_allocator_reset(tracker) } From 84ad71fdb3e7714d22cf724c8a593f98100962b1 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 17:27:08 -0400 Subject: [PATCH 065/270] Support `ODIN_TEST_PROGRESS_WIDTH=0` This will automatically calculate how wide the progress bars should be based on the package with the greatest number of tests. The progress width is now capped to 100. --- core/testing/reporting.odin | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/core/testing/reporting.odin b/core/testing/reporting.odin index fba67d67f..92e144ccc 100644 --- a/core/testing/reporting.odin +++ b/core/testing/reporting.odin @@ -16,9 +16,11 @@ SGR_RUNNING :: ansi.CSI + ansi.FG_YELLOW + ansi.SGR SGR_SUCCESS :: ansi.CSI + ansi.FG_GREEN + ansi.SGR SGR_FAILED :: ansi.CSI + ansi.FG_RED + ansi.SGR +MAX_PROGRESS_WIDTH :: 100 + // More than enough bytes to cover long package names, long test names, dozens // of ANSI codes, et cetera. -LINE_BUFFER_SIZE :: (PROGRESS_WIDTH * 8 + 256) * runtime.Byte +LINE_BUFFER_SIZE :: (MAX_PROGRESS_WIDTH * 8 + 224) * runtime.Byte PROGRESS_COLUMN_SPACING :: 2 @@ -44,6 +46,7 @@ Report :: struct { pkg_column_len: int, test_column_len: int, + progress_width: int, all_tests: []Internal_Test, all_test_states: []Test_State, @@ -73,6 +76,10 @@ make_report :: proc(internal_tests: []Internal_Test) -> (report: Report, error: }) or_return } + when PROGRESS_WIDTH == 0 { + report.progress_width = max(report.progress_width, index - pkg_start) + } + pkg_start = index report.pkg_column_len = max(report.pkg_column_len, len(cur_pkg)) cur_pkg = it.pkg @@ -80,7 +87,7 @@ make_report :: proc(internal_tests: []Internal_Test) -> (report: Report, error: report.test_column_len = max(report.test_column_len, len(it.name)) } - // Handle the last package. + // Handle the last (or only) package. #no_bounds_check { append(&packages, Package_Run { name = cur_pkg, @@ -89,6 +96,12 @@ make_report :: proc(internal_tests: []Internal_Test) -> (report: Report, error: test_states = report.all_test_states[pkg_start:], }) or_return } + when PROGRESS_WIDTH == 0 { + report.progress_width = max(report.progress_width, len(internal_tests) - pkg_start) + } else { + report.progress_width = PROGRESS_WIDTH + } + report.progress_width = min(report.progress_width, MAX_PROGRESS_WIDTH) report.pkg_column_len = PROGRESS_COLUMN_SPACING + max(report.pkg_column_len, len(cur_pkg)) @@ -123,7 +136,7 @@ destroy_report :: proc(report: ^Report) { delete(report.all_test_states) } -redraw_package :: proc(w: io.Writer, pkg: ^Package_Run) { +redraw_package :: proc(w: io.Writer, report: Report, pkg: ^Package_Run) { if pkg.frame_ready { io.write_string(w, pkg.redraw_string) return @@ -150,8 +163,8 @@ redraw_package :: proc(w: io.Writer, pkg: ^Package_Run) { } } - start := max(0, highest_run_index - (PROGRESS_WIDTH - 1)) - end := min(start + PROGRESS_WIDTH, len(pkg.test_states)) + start := max(0, highest_run_index - (report.progress_width - 1)) + end := min(start + report.progress_width, len(pkg.test_states)) // This variable is to keep track of the last ANSI code emitted, in // order to avoid repeating the same code over in a sequence. @@ -187,7 +200,7 @@ redraw_package :: proc(w: io.Writer, pkg: ^Package_Run) { io.write_byte(line_writer, '|') } - for _ in 0 ..< PROGRESS_WIDTH - (end - start) { + for _ in 0 ..< report.progress_width - (end - start) { io.write_byte(line_writer, ' ') } @@ -255,7 +268,7 @@ redraw_report :: proc(w: io.Writer, report: Report) { // still break... fmt.wprint(w, ansi.CSI + ansi.DECAWM_OFF) for &pkg in report.packages { - redraw_package(w, &pkg) + redraw_package(w, report, &pkg) } fmt.wprint(w, ansi.CSI + ansi.DECAWM_ON) } From 6a1649d8aa000e903353303d54d4514159f7a9b2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 29 May 2024 23:40:15 +0200 Subject: [PATCH 066/270] Update using new defaults for memory + reporting width --- tests/core/Makefile | 99 ++++++++++++++++++++++---------------------- tests/core/build.bat | 12 +++--- 2 files changed, 55 insertions(+), 56 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 23d0dcf99..6556f2916 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,16 +1,16 @@ ODIN=../../odin PYTHON=$(shell which python3) -COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_TRACK_MEMORY=true +COMMON=-no-bounds-check -vet -strict-style COLLECTION=-collection:tests=.. all: all_bsd \ net_test -all_bsd: c_libc_test \ +all_bsd: download_test_assets \ + c_libc_test \ compress_test \ container_test \ crypto_test \ - download_test_assets \ encoding_test \ filepath_test \ fmt_test \ @@ -23,36 +23,26 @@ all_bsd: c_libc_test \ noise_test \ os_exit_test \ reflect_test \ + runtime_test \ slice_test \ strings_test \ thread_test \ - runtime_test \ - time_test \ - fmt_test + time_test download_test_assets: $(PYTHON) download_assets.py -image_test: - $(ODIN) test image $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=12 -out:test_core_image +c_libc_test: + $(ODIN) run c/libc $(COMMON) -out:test_core_libc compress_test: - $(ODIN) test compress $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=3 -out:test_core_compress + $(ODIN) test compress $(COMMON) -out:test_core_compress container_test: - $(ODIN) test container $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=4 -out:test_core_container + $(ODIN) test container $(COMMON) -out:test_core_container crypto_test: - $(ODIN) test crypto $(COMMON) -define:ODIN_TEST_PROGRESS_WIDTH=18 -o:speed -out:test_crypto - -strings_test: - $(ODIN) run strings $(COMMON) -out:test_core_strings - -hash_test: - $(ODIN) run hash $(COMMON) -o:speed -out:test_hash - -noise_test: - $(ODIN) run math/noise $(COMMON) -out:test_noise + $(ODIN) test crypto $(COMMON) -o:speed -out:test_crypto encoding_test: $(ODIN) run encoding/hxa $(COMMON) $(COLLECTION) -out:test_hxa @@ -63,44 +53,53 @@ encoding_test: $(ODIN) run encoding/hex $(COMMON) -out:test_hex $(ODIN) run encoding/base64 $(COMMON) -out:test_base64 -math_test: - $(ODIN) run math $(COMMON) $(COLLECTION) -out:test_core_math - -linalg_glsl_math_test: - $(ODIN) run math/linalg/glsl $(COMMON) $(COLLECTION) -out:test_linalg_glsl_math - filepath_test: $(ODIN) run path/filepath $(COMMON) $(COLLECTION) -out:test_core_filepath -reflect_test: - $(ODIN) run reflect $(COMMON) $(COLLECTION) -out:test_core_reflect - -slice_test: - $(ODIN) run slice $(COMMON) -out:test_core_slice - -os_exit_test: - $(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0 - -i18n_test: - $(ODIN) run text/i18n $(COMMON) -out:test_core_i18n - -match_test: - $(ODIN) run text/match $(COMMON) -out:test_core_match - -c_libc_test: - $(ODIN) run c/libc $(COMMON) -out:test_core_libc - -net_test: - $(ODIN) run net $(COMMON) -out:test_core_net - fmt_test: $(ODIN) run fmt $(COMMON) -out:test_core_fmt -thread_test: - $(ODIN) run thread $(COMMON) -out:test_core_thread +hash_test: + $(ODIN) run hash $(COMMON) -o:speed -out:test_hash + +i18n_test: + $(ODIN) run text/i18n $(COMMON) -out:test_core_i18n + +image_test: + $(ODIN) test image $(COMMON) -out:test_core_image + +linalg_glsl_math_test: + $(ODIN) run math/linalg/glsl $(COMMON) $(COLLECTION) -out:test_linalg_glsl_math + +match_test: + $(ODIN) run text/match $(COMMON) -out:test_core_match + +math_test: + $(ODIN) run math $(COMMON) $(COLLECTION) -out:test_core_math + +net_test: + $(ODIN) run net $(COMMON) -out:test_core_net + +noise_test: + $(ODIN) run math/noise $(COMMON) -out:test_noise + +os_exit_test: + $(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0 + +reflect_test: + $(ODIN) run reflect $(COMMON) $(COLLECTION) -out:test_core_reflect runtime_test: $(ODIN) run runtime $(COMMON) -out:test_core_runtime +slice_test: + $(ODIN) run slice $(COMMON) -out:test_core_slice + +strings_test: + $(ODIN) run strings $(COMMON) -out:test_core_strings + +thread_test: + $(ODIN) run thread $(COMMON) -out:test_core_thread + time_test: - $(ODIN) run time $(COMMON) -out:test_core_time + $(ODIN) run time $(COMMON) -out:test_core_time \ No newline at end of file diff --git a/tests/core/build.bat b/tests/core/build.bat index b63c0f311..60296090e 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -1,22 +1,22 @@ @echo off -set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_TRACK_MEMORY=true +set COMMON=-no-bounds-check -vet -strict-style set COLLECTION=-collection:tests=.. set PATH_TO_ODIN==..\..\odin python3 download_assets.py echo --- echo Running core:compress tests echo --- -%PATH_TO_ODIN% test compress %COMMON% -define:ODIN_TEST_PROGRESS_WIDTH=3 -out:test_core_compress.exe || exit /b +%PATH_TO_ODIN% test compress %COMMON% -out:test_core_compress.exe || exit /b echo --- echo Running core:container tests echo --- -%PATH_TO_ODIN% test container %COMMON% -define:ODIN_TEST_PROGRESS_WIDTH=4 -out:test_core_container.exe || exit /b +%PATH_TO_ODIN% test container %COMMON% -out:test_core_container.exe || exit /b echo --- echo Running core:crypto tests echo --- -%PATH_TO_ODIN% test crypto %COMMON% define:test_progress_width=18 -o:speed -out:test_crypto.exe || exit /b +%PATH_TO_ODIN% test crypto %COMMON% -o:speed -out:test_crypto.exe || exit /b echo --- echo Running core:encoding tests @@ -42,7 +42,7 @@ echo --- echo --- echo Running core:image tests echo --- -%PATH_TO_ODIN% test image %COMMON% -define:ODIN_TEST_PROGRESS_WIDTH=12 -out:test_core_image.exe || exit /b +%PATH_TO_ODIN% test image %COMMON% -out:test_core_image.exe || exit /b echo --- echo Running core:math tests @@ -107,4 +107,4 @@ echo --- echo --- echo Running core:time tests echo --- -%PATH_TO_ODIN% run time %COMMON% %COLLECTION% -out:test_core_time.exe || exit /b +%PATH_TO_ODIN% run time %COMMON% %COLLECTION% -out:test_core_time.exe || exit /b \ No newline at end of file From a27b16721893f78f1788c4f8a7a952d65f7799c3 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 10:42:27 +0200 Subject: [PATCH 067/270] Update `tests\core\encoding\cbor` to use new test runner. It was leaky and required a substantial number of `loc := #caller_location` additions to parts of the core library to make it easier to track down how and where it leaked. The tests now run fine multi-threaded. --- core/bufio/reader.odin | 4 +- core/bytes/buffer.odin | 70 ++-- core/encoding/cbor/cbor.odin | 4 +- core/encoding/cbor/coding.odin | 107 +++--- core/encoding/cbor/marshal.odin | 16 +- core/encoding/cbor/unmarshal.odin | 102 +++--- core/strings/builder.odin | 8 +- tests/core/Makefile | 14 +- tests/core/build.bat | 12 +- tests/core/encoding/base64/base64.odin | 65 ++-- tests/core/encoding/cbor/test_core_cbor.odin | 341 +++++++------------ 11 files changed, 323 insertions(+), 420 deletions(-) diff --git a/core/bufio/reader.odin b/core/bufio/reader.odin index 8ec736a66..a875c732d 100644 --- a/core/bufio/reader.odin +++ b/core/bufio/reader.odin @@ -29,12 +29,12 @@ MIN_READ_BUFFER_SIZE :: 16 @(private) DEFAULT_MAX_CONSECUTIVE_EMPTY_READS :: 128 -reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator) { +reader_init :: proc(b: ^Reader, rd: io.Reader, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator, loc := #caller_location) { size := size size = max(size, MIN_READ_BUFFER_SIZE) reader_reset(b, rd) b.buf_allocator = allocator - b.buf = make([]byte, size, allocator) + b.buf = make([]byte, size, allocator, loc) } reader_init_with_buf :: proc(b: ^Reader, rd: io.Reader, buf: []byte) { diff --git a/core/bytes/buffer.odin b/core/bytes/buffer.odin index cb2ef9c62..a7e9b1c64 100644 --- a/core/bytes/buffer.odin +++ b/core/bytes/buffer.odin @@ -27,19 +27,19 @@ Read_Op :: enum i8 { } -buffer_init :: proc(b: ^Buffer, buf: []byte) { - resize(&b.buf, len(buf)) +buffer_init :: proc(b: ^Buffer, buf: []byte, loc := #caller_location) { + resize(&b.buf, len(buf), loc=loc) copy(b.buf[:], buf) } -buffer_init_string :: proc(b: ^Buffer, s: string) { - resize(&b.buf, len(s)) +buffer_init_string :: proc(b: ^Buffer, s: string, loc := #caller_location) { + resize(&b.buf, len(s), loc=loc) copy(b.buf[:], s) } -buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator) { +buffer_init_allocator :: proc(b: ^Buffer, len, cap: int, allocator := context.allocator, loc := #caller_location) { if b.buf == nil { - b.buf = make([dynamic]byte, len, cap, allocator) + b.buf = make([dynamic]byte, len, cap, allocator, loc) return } @@ -96,28 +96,28 @@ buffer_truncate :: proc(b: ^Buffer, n: int) { } @(private) -_buffer_try_grow :: proc(b: ^Buffer, n: int) -> (int, bool) { +_buffer_try_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> (int, bool) { if l := len(b.buf); n <= cap(b.buf)-l { - resize(&b.buf, l+n) + resize(&b.buf, l+n, loc=loc) return l, true } return 0, false } @(private) -_buffer_grow :: proc(b: ^Buffer, n: int) -> int { +_buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) -> int { m := buffer_length(b) if m == 0 && b.off != 0 { buffer_reset(b) } - if i, ok := _buffer_try_grow(b, n); ok { + if i, ok := _buffer_try_grow(b, n, loc=loc); ok { return i } if b.buf == nil && n <= SMALL_BUFFER_SIZE { // Fixes #2756 by preserving allocator if already set on Buffer via init_buffer_allocator - reserve(&b.buf, SMALL_BUFFER_SIZE) - resize(&b.buf, n) + reserve(&b.buf, SMALL_BUFFER_SIZE, loc=loc) + resize(&b.buf, n, loc=loc) return 0 } @@ -127,31 +127,31 @@ _buffer_grow :: proc(b: ^Buffer, n: int) -> int { } else if c > max(int) - c - n { panic("bytes.Buffer: too large") } else { - resize(&b.buf, 2*c + n) + resize(&b.buf, 2*c + n, loc=loc) copy(b.buf[:], b.buf[b.off:]) } b.off = 0 - resize(&b.buf, m+n) + resize(&b.buf, m+n, loc=loc) return m } -buffer_grow :: proc(b: ^Buffer, n: int) { +buffer_grow :: proc(b: ^Buffer, n: int, loc := #caller_location) { if n < 0 { panic("bytes.buffer_grow: negative count") } - m := _buffer_grow(b, n) - resize(&b.buf, m) + m := _buffer_grow(b, n, loc=loc) + resize(&b.buf, m, loc=loc) } -buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io.Error) { +buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int, loc := #caller_location) -> (n: int, err: io.Error) { b.last_read = .Invalid if offset < 0 { err = .Invalid_Offset return } - _, ok := _buffer_try_grow(b, offset+len(p)) + _, ok := _buffer_try_grow(b, offset+len(p), loc=loc) if !ok { - _ = _buffer_grow(b, offset+len(p)) + _ = _buffer_grow(b, offset+len(p), loc=loc) } if len(b.buf) <= offset { return 0, .Short_Write @@ -160,47 +160,47 @@ buffer_write_at :: proc(b: ^Buffer, p: []byte, offset: int) -> (n: int, err: io. } -buffer_write :: proc(b: ^Buffer, p: []byte) -> (n: int, err: io.Error) { +buffer_write :: proc(b: ^Buffer, p: []byte, loc := #caller_location) -> (n: int, err: io.Error) { b.last_read = .Invalid - m, ok := _buffer_try_grow(b, len(p)) + m, ok := _buffer_try_grow(b, len(p), loc=loc) if !ok { - m = _buffer_grow(b, len(p)) + m = _buffer_grow(b, len(p), loc=loc) } return copy(b.buf[m:], p), nil } -buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int) -> (n: int, err: io.Error) { - return buffer_write(b, ([^]byte)(ptr)[:size]) +buffer_write_ptr :: proc(b: ^Buffer, ptr: rawptr, size: int, loc := #caller_location) -> (n: int, err: io.Error) { + return buffer_write(b, ([^]byte)(ptr)[:size], loc=loc) } -buffer_write_string :: proc(b: ^Buffer, s: string) -> (n: int, err: io.Error) { +buffer_write_string :: proc(b: ^Buffer, s: string, loc := #caller_location) -> (n: int, err: io.Error) { b.last_read = .Invalid - m, ok := _buffer_try_grow(b, len(s)) + m, ok := _buffer_try_grow(b, len(s), loc=loc) if !ok { - m = _buffer_grow(b, len(s)) + m = _buffer_grow(b, len(s), loc=loc) } return copy(b.buf[m:], s), nil } -buffer_write_byte :: proc(b: ^Buffer, c: byte) -> io.Error { +buffer_write_byte :: proc(b: ^Buffer, c: byte, loc := #caller_location) -> io.Error { b.last_read = .Invalid - m, ok := _buffer_try_grow(b, 1) + m, ok := _buffer_try_grow(b, 1, loc=loc) if !ok { - m = _buffer_grow(b, 1) + m = _buffer_grow(b, 1, loc=loc) } b.buf[m] = c return nil } -buffer_write_rune :: proc(b: ^Buffer, r: rune) -> (n: int, err: io.Error) { +buffer_write_rune :: proc(b: ^Buffer, r: rune, loc := #caller_location) -> (n: int, err: io.Error) { if r < utf8.RUNE_SELF { - buffer_write_byte(b, byte(r)) + buffer_write_byte(b, byte(r), loc=loc) return 1, nil } b.last_read = .Invalid - m, ok := _buffer_try_grow(b, utf8.UTF_MAX) + m, ok := _buffer_try_grow(b, utf8.UTF_MAX, loc=loc) if !ok { - m = _buffer_grow(b, utf8.UTF_MAX) + m = _buffer_grow(b, utf8.UTF_MAX, loc=loc) } res: [4]byte res, n = utf8.encode_rune(r) diff --git a/core/encoding/cbor/cbor.odin b/core/encoding/cbor/cbor.odin index d0e406ab1..7897b2a37 100644 --- a/core/encoding/cbor/cbor.odin +++ b/core/encoding/cbor/cbor.odin @@ -320,8 +320,8 @@ to_diagnostic_format :: proc { // Turns the given CBOR value into a human-readable string. // See docs on the proc group `diagnose` for more info. -to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator) -> (string, mem.Allocator_Error) #optional_allocator_error { - b := strings.builder_make(allocator) +to_diagnostic_format_string :: proc(val: Value, padding := 0, allocator := context.allocator, loc := #caller_location) -> (string, mem.Allocator_Error) #optional_allocator_error { + b := strings.builder_make(allocator, loc) w := strings.to_stream(&b) err := to_diagnostic_format_writer(w, val, padding) if err == .EOF { diff --git a/core/encoding/cbor/coding.odin b/core/encoding/cbor/coding.odin index 0d276a7a1..07f0637a6 100644 --- a/core/encoding/cbor/coding.odin +++ b/core/encoding/cbor/coding.odin @@ -95,24 +95,25 @@ decode :: decode_from // Decodes the given string as CBOR. // See docs on the proc group `decode` for more information. -decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) { +decode_from_string :: proc(s: string, flags: Decoder_Flags = {}, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) { r: strings.Reader strings.reader_init(&r, s) - return decode_from_reader(strings.reader_to_stream(&r), flags, allocator) + return decode_from_reader(strings.reader_to_stream(&r), flags, allocator, loc) } // Reads a CBOR value from the given reader. // See docs on the proc group `decode` for more information. -decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator) -> (v: Value, err: Decode_Error) { +decode_from_reader :: proc(r: io.Reader, flags: Decoder_Flags = {}, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) { return decode_from_decoder( Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, allocator=allocator, + loc = loc, ) } // Reads a CBOR value from the given decoder. // See docs on the proc group `decode` for more information. -decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: Value, err: Decode_Error) { +decode_from_decoder :: proc(d: Decoder, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) { context.allocator = allocator d := d @@ -121,13 +122,13 @@ decode_from_decoder :: proc(d: Decoder, allocator := context.allocator) -> (v: V d.max_pre_alloc = DEFAULT_MAX_PRE_ALLOC } - v, err = _decode_from_decoder(d) + v, err = _decode_from_decoder(d, {}, allocator, loc) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. if err == .EOF { err = .Unexpected_EOF } return } -_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value, err: Decode_Error) { +_decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0), allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) { hdr := hdr r := d.reader if hdr == Header(0) { hdr = _decode_header(r) or_return } @@ -161,11 +162,11 @@ _decode_from_decoder :: proc(d: Decoder, hdr: Header = Header(0)) -> (v: Value, switch maj { case .Unsigned: return _decode_tiny_u8(add) case .Negative: return Negative_U8(_decode_tiny_u8(add) or_return), nil - case .Bytes: return _decode_bytes_ptr(d, add) - case .Text: return _decode_text_ptr(d, add) - case .Array: return _decode_array_ptr(d, add) - case .Map: return _decode_map_ptr(d, add) - case .Tag: return _decode_tag_ptr(d, add) + case .Bytes: return _decode_bytes_ptr(d, add, .Bytes, allocator, loc) + case .Text: return _decode_text_ptr(d, add, allocator, loc) + case .Array: return _decode_array_ptr(d, add, allocator, loc) + case .Map: return _decode_map_ptr(d, add, allocator, loc) + case .Tag: return _decode_tag_ptr(d, add, allocator, loc) case .Other: return _decode_tiny_simple(add) case: return nil, .Bad_Major } @@ -203,27 +204,27 @@ encode :: encode_into // Encodes the CBOR value into binary CBOR allocated on the given allocator. // See the docs on the proc group `encode_into` for more info. -encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (data: []byte, err: Encode_Error) { - b := strings.builder_make(allocator) or_return +encode_into_bytes :: proc(v: Value, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (data: []byte, err: Encode_Error) { + b := strings.builder_make(allocator, loc) or_return encode_into_builder(&b, v, flags, temp_allocator) or_return return b.buf[:], nil } // Encodes the CBOR value into binary CBOR written to the given builder. // See the docs on the proc group `encode_into` for more info. -encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error { - return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator) +encode_into_builder :: proc(b: ^strings.Builder, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Encode_Error { + return encode_into_writer(strings.to_stream(b), v, flags, temp_allocator, loc=loc) } // Encodes the CBOR value into binary CBOR written to the given writer. // See the docs on the proc group `encode_into` for more info. -encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Encode_Error { - return encode_into_encoder(Encoder{flags, w, temp_allocator}, v) +encode_into_writer :: proc(w: io.Writer, v: Value, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Encode_Error { + return encode_into_encoder(Encoder{flags, w, temp_allocator}, v, loc=loc) } // Encodes the CBOR value into binary CBOR written to the given encoder. // See the docs on the proc group `encode_into` for more info. -encode_into_encoder :: proc(e: Encoder, v: Value) -> Encode_Error { +encode_into_encoder :: proc(e: Encoder, v: Value, loc := #caller_location) -> Encode_Error { e := e if e.temp_allocator.procedure == nil { @@ -366,21 +367,21 @@ _encode_u64_exact :: proc(w: io.Writer, v: u64, major: Major = .Unsigned) -> (er return } -_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes) -> (v: ^Bytes, err: Decode_Error) { - v = new(Bytes) or_return - defer if err != nil { free(v) } +_decode_bytes_ptr :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator, loc := #caller_location) -> (v: ^Bytes, err: Decode_Error) { + v = new(Bytes, allocator, loc) or_return + defer if err != nil { free(v, allocator, loc) } - v^ = _decode_bytes(d, add, type) or_return + v^ = _decode_bytes(d, add, type, allocator, loc) or_return return } -_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator) -> (v: Bytes, err: Decode_Error) { +_decode_bytes :: proc(d: Decoder, add: Add, type: Major = .Bytes, allocator := context.allocator, loc := #caller_location) -> (v: Bytes, err: Decode_Error) { context.allocator = allocator add := add n, scap := _decode_len_str(d, add) or_return - buf := strings.builder_make(0, scap) or_return + buf := strings.builder_make(0, scap, allocator, loc) or_return defer if err != nil { strings.builder_destroy(&buf) } buf_stream := strings.to_stream(&buf) @@ -426,40 +427,40 @@ _encode_bytes :: proc(e: Encoder, val: Bytes, major: Major = .Bytes) -> (err: En return } -_decode_text_ptr :: proc(d: Decoder, add: Add) -> (v: ^Text, err: Decode_Error) { - v = new(Text) or_return +_decode_text_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Text, err: Decode_Error) { + v = new(Text, allocator, loc) or_return defer if err != nil { free(v) } - v^ = _decode_text(d, add) or_return + v^ = _decode_text(d, add, allocator, loc) or_return return } -_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator) -> (v: Text, err: Decode_Error) { - return (Text)(_decode_bytes(d, add, .Text, allocator) or_return), nil +_decode_text :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Text, err: Decode_Error) { + return (Text)(_decode_bytes(d, add, .Text, allocator, loc) or_return), nil } _encode_text :: proc(e: Encoder, val: Text) -> Encode_Error { return _encode_bytes(e, transmute([]byte)val, .Text) } -_decode_array_ptr :: proc(d: Decoder, add: Add) -> (v: ^Array, err: Decode_Error) { - v = new(Array) or_return +_decode_array_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Array, err: Decode_Error) { + v = new(Array, allocator, loc) or_return defer if err != nil { free(v) } - v^ = _decode_array(d, add) or_return + v^ = _decode_array(d, add, allocator, loc) or_return return } -_decode_array :: proc(d: Decoder, add: Add) -> (v: Array, err: Decode_Error) { +_decode_array :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Array, err: Decode_Error) { n, scap := _decode_len_container(d, add) or_return - array := make([dynamic]Value, 0, scap) or_return + array := make([dynamic]Value, 0, scap, allocator, loc) or_return defer if err != nil { - for entry in array { destroy(entry) } - delete(array) + for entry in array { destroy(entry, allocator) } + delete(array, loc) } for i := 0; n == -1 || i < n; i += 1 { - val, verr := _decode_from_decoder(d) + val, verr := _decode_from_decoder(d, {}, allocator, loc) if n == -1 && verr == .Break { break } else if verr != nil { @@ -485,39 +486,39 @@ _encode_array :: proc(e: Encoder, arr: Array) -> Encode_Error { return nil } -_decode_map_ptr :: proc(d: Decoder, add: Add) -> (v: ^Map, err: Decode_Error) { - v = new(Map) or_return +_decode_map_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: ^Map, err: Decode_Error) { + v = new(Map, allocator, loc) or_return defer if err != nil { free(v) } - v^ = _decode_map(d, add) or_return + v^ = _decode_map(d, add, allocator, loc) or_return return } -_decode_map :: proc(d: Decoder, add: Add) -> (v: Map, err: Decode_Error) { +_decode_map :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Map, err: Decode_Error) { n, scap := _decode_len_container(d, add) or_return - items := make([dynamic]Map_Entry, 0, scap) or_return + items := make([dynamic]Map_Entry, 0, scap, allocator, loc) or_return defer if err != nil { for entry in items { destroy(entry.key) destroy(entry.value) } - delete(items) + delete(items, loc) } for i := 0; n == -1 || i < n; i += 1 { - key, kerr := _decode_from_decoder(d) + key, kerr := _decode_from_decoder(d, {}, allocator, loc) if n == -1 && kerr == .Break { break } else if kerr != nil { return nil, kerr } - value := _decode_from_decoder(d) or_return + value := _decode_from_decoder(d, {}, allocator, loc) or_return append(&items, Map_Entry{ key = key, value = value, - }) or_return + }, loc) or_return } if .Shrink_Excess in d.flags { shrink(&items) } @@ -578,20 +579,20 @@ _encode_map :: proc(e: Encoder, m: Map) -> (err: Encode_Error) { return nil } -_decode_tag_ptr :: proc(d: Decoder, add: Add) -> (v: Value, err: Decode_Error) { - tag := _decode_tag(d, add) or_return +_decode_tag_ptr :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Value, err: Decode_Error) { + tag := _decode_tag(d, add, allocator, loc) or_return if t, ok := tag.?; ok { defer if err != nil { destroy(t.value) } - tp := new(Tag) or_return + tp := new(Tag, allocator, loc) or_return tp^ = t return tp, nil } // no error, no tag, this was the self described CBOR tag, skip it. - return _decode_from_decoder(d) + return _decode_from_decoder(d, {}, allocator, loc) } -_decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error) { +_decode_tag :: proc(d: Decoder, add: Add, allocator := context.allocator, loc := #caller_location) -> (v: Maybe(Tag), err: Decode_Error) { num := _decode_uint_as_u64(d.reader, add) or_return // CBOR can be wrapped in a tag that decoders can use to see/check if the binary data is CBOR. @@ -602,7 +603,7 @@ _decode_tag :: proc(d: Decoder, add: Add) -> (v: Maybe(Tag), err: Decode_Error) t := Tag{ number = num, - value = _decode_from_decoder(d) or_return, + value = _decode_from_decoder(d, {}, allocator, loc) or_return, } if nested, ok := t.value.(^Tag); ok { @@ -883,4 +884,4 @@ _encode_deterministic_f64 :: proc(w: io.Writer, v: f64) -> io.Error { } return _encode_f64_exact(w, v) -} +} \ No newline at end of file diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index 37c9dd180..775eafd9c 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -45,8 +45,8 @@ marshal :: marshal_into // Marshals the given value into a CBOR byte stream (allocated using the given allocator). // See docs on the `marshal_into` proc group for more info. -marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (bytes: []byte, err: Marshal_Error) { - b, alloc_err := strings.builder_make(allocator) +marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (bytes: []byte, err: Marshal_Error) { + b, alloc_err := strings.builder_make(allocator, loc=loc) // The builder as a stream also returns .EOF if it ran out of memory so this is consistent. if alloc_err != nil { return nil, .EOF @@ -54,7 +54,7 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a defer if err != nil { strings.builder_destroy(&b) } - if err = marshal_into_builder(&b, v, flags, temp_allocator); err != nil { + if err = marshal_into_builder(&b, v, flags, temp_allocator, loc=loc); err != nil { return } @@ -63,20 +63,20 @@ marshal_into_bytes :: proc(v: any, flags := ENCODE_SMALL, allocator := context.a // Marshals the given value into a CBOR byte stream written to the given builder. // See docs on the `marshal_into` proc group for more info. -marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error { - return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator) +marshal_into_builder :: proc(b: ^strings.Builder, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error { + return marshal_into_writer(strings.to_writer(b), v, flags, temp_allocator, loc=loc) } // Marshals the given value into a CBOR byte stream written to the given writer. // See docs on the `marshal_into` proc group for more info. -marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator) -> Marshal_Error { +marshal_into_writer :: proc(w: io.Writer, v: any, flags := ENCODE_SMALL, temp_allocator := context.temp_allocator, loc := #caller_location) -> Marshal_Error { encoder := Encoder{flags, w, temp_allocator} - return marshal_into_encoder(encoder, v) + return marshal_into_encoder(encoder, v, loc=loc) } // Marshals the given value into a CBOR byte stream written to the given encoder. // See docs on the `marshal_into` proc group for more info. -marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) { +marshal_into_encoder :: proc(e: Encoder, v: any, loc := #caller_location) -> (err: Marshal_Error) { e := e if e.temp_allocator.procedure == nil { diff --git a/core/encoding/cbor/unmarshal.odin b/core/encoding/cbor/unmarshal.odin index a1524d9f4..c31ba1d92 100644 --- a/core/encoding/cbor/unmarshal.odin +++ b/core/encoding/cbor/unmarshal.odin @@ -31,8 +31,8 @@ unmarshal :: proc { unmarshal_from_string, } -unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) { - err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator) +unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { + err = unmarshal_from_decoder(Decoder{ DEFAULT_MAX_PRE_ALLOC, flags, r }, ptr, allocator, temp_allocator, loc) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. if err == .EOF { err = .Unexpected_EOF } @@ -40,21 +40,21 @@ unmarshal_from_reader :: proc(r: io.Reader, ptr: ^$T, flags := Decoder_Flags{}, } // Unmarshals from a string, see docs on the proc group `Unmarshal` for more info. -unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) { +unmarshal_from_string :: proc(s: string, ptr: ^$T, flags := Decoder_Flags{}, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { sr: strings.Reader r := strings.to_reader(&sr, s) - err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator) + err = unmarshal_from_reader(r, ptr, flags, allocator, temp_allocator, loc) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. if err == .EOF { err = .Unexpected_EOF } return } -unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator) -> (err: Unmarshal_Error) { +unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { d := d - err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator) + err = _unmarshal_any_ptr(d, ptr, nil, allocator, temp_allocator, loc) // Normal EOF does not exist here, we try to read the exact amount that is said to be provided. if err == .EOF { err = .Unexpected_EOF } @@ -62,7 +62,7 @@ unmarshal_from_decoder :: proc(d: Decoder, ptr: ^$T, allocator := context.alloca } -_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator) -> Unmarshal_Error { +_unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> Unmarshal_Error { context.allocator = allocator context.temp_allocator = temp_allocator v := v @@ -78,10 +78,10 @@ _unmarshal_any_ptr :: proc(d: Decoder, v: any, hdr: Maybe(Header) = nil, allocat } data := any{(^rawptr)(v.data)^, ti.variant.(reflect.Type_Info_Pointer).elem.id} - return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return)) + return _unmarshal_value(d, data, hdr.? or_else (_decode_header(d.reader) or_return), allocator, temp_allocator, loc) } -_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Error) { +_unmarshal_value :: proc(d: Decoder, v: any, hdr: Header, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { v := v ti := reflect.type_info_base(type_info_of(v.id)) r := d.reader @@ -104,7 +104,7 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err // Allow generic unmarshal by doing it into a `Value`. switch &dst in v { case Value: - dst = err_conv(_decode_from_decoder(d, hdr)) or_return + dst = err_conv(_decode_from_decoder(d, hdr, allocator, loc)) or_return return } @@ -308,7 +308,7 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err if impl, ok := _tag_implementations_nr[nr]; ok { return impl->unmarshal(d, nr, v) } else if nr == TAG_OBJECT_TYPE { - return _unmarshal_union(d, v, ti, hdr) + return _unmarshal_union(d, v, ti, hdr, loc=loc) } else { // Discard the tag info and unmarshal as its value. return _unmarshal_value(d, v, _decode_header(r) or_return) @@ -316,19 +316,19 @@ _unmarshal_value :: proc(d: Decoder, v: any, hdr: Header) -> (err: Unmarshal_Err return _unsupported(v, hdr, add) - case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add) - case .Text: return _unmarshal_string(d, v, ti, hdr, add) - case .Array: return _unmarshal_array(d, v, ti, hdr, add) - case .Map: return _unmarshal_map(d, v, ti, hdr, add) + case .Bytes: return _unmarshal_bytes(d, v, ti, hdr, add, allocator=allocator, loc=loc) + case .Text: return _unmarshal_string(d, v, ti, hdr, add, allocator=allocator, loc=loc) + case .Array: return _unmarshal_array(d, v, ti, hdr, add, allocator=allocator, loc=loc) + case .Map: return _unmarshal_map(d, v, ti, hdr, add, allocator=allocator, loc=loc) case: return .Bad_Major } } -_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { +_unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) { #partial switch t in ti.variant { case reflect.Type_Info_String: - bytes := err_conv(_decode_bytes(d, add)) or_return + bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return if t.is_cstring { raw := (^cstring)(v.data) @@ -347,7 +347,7 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if elem_base.id != byte { return _unsupported(v, hdr) } - bytes := err_conv(_decode_bytes(d, add)) or_return + bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return raw := (^mem.Raw_Slice)(v.data) raw^ = transmute(mem.Raw_Slice)bytes return @@ -357,12 +357,12 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if elem_base.id != byte { return _unsupported(v, hdr) } - bytes := err_conv(_decode_bytes(d, add)) or_return + bytes := err_conv(_decode_bytes(d, add, allocator=allocator, loc=loc)) or_return raw := (^mem.Raw_Dynamic_Array)(v.data) raw.data = raw_data(bytes) raw.len = len(bytes) raw.cap = len(bytes) - raw.allocator = context.allocator + raw.allocator = allocator return case reflect.Type_Info_Array: @@ -385,10 +385,10 @@ _unmarshal_bytes :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return _unsupported(v, hdr) } -_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { +_unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, temp_allocator := context.temp_allocator, loc := #caller_location) -> (err: Unmarshal_Error) { #partial switch t in ti.variant { case reflect.Type_Info_String: - text := err_conv(_decode_text(d, add)) or_return + text := err_conv(_decode_text(d, add, allocator, loc)) or_return if t.is_cstring { raw := (^cstring)(v.data) @@ -403,8 +403,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade // Enum by its variant name. case reflect.Type_Info_Enum: - text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return - defer delete(text, context.temp_allocator) + text := err_conv(_decode_text(d, add, allocator=temp_allocator, loc=loc)) or_return + defer delete(text, temp_allocator, loc) for name, i in t.names { if name == text { @@ -414,8 +414,8 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade } case reflect.Type_Info_Rune: - text := err_conv(_decode_text(d, add, allocator=context.temp_allocator)) or_return - defer delete(text, context.temp_allocator) + text := err_conv(_decode_text(d, add, allocator=temp_allocator, loc=loc)) or_return + defer delete(text, temp_allocator, loc) r := (^rune)(v.data) dr, n := utf8.decode_rune(text) @@ -430,13 +430,15 @@ _unmarshal_string :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Heade return _unsupported(v, hdr) } -_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { +_unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) { assign_array :: proc( d: Decoder, da: ^mem.Raw_Dynamic_Array, elemt: ^reflect.Type_Info, length: int, growable := true, + allocator := context.allocator, + loc := #caller_location, ) -> (out_of_space: bool, err: Unmarshal_Error) { for idx: uintptr = 0; length == -1 || idx < uintptr(length); idx += 1 { elem_ptr := rawptr(uintptr(da.data) + idx*uintptr(elemt.size)) @@ -450,13 +452,13 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if !growable { return true, .Out_Of_Memory } cap := 2 * da.cap - ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap) + ok := runtime.__dynamic_array_reserve(da, elemt.size, elemt.align, cap, loc) // NOTE: Might be lying here, but it is at least an allocator error. if !ok { return false, .Out_Of_Memory } } - err = _unmarshal_value(d, elem, hdr) + err = _unmarshal_value(d, elem, hdr, allocator=allocator, loc=loc) if length == -1 && err == .Break { break } if err != nil { return } @@ -469,10 +471,10 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header // Allow generically storing the values array. switch &dst in v { case ^Array: - dst = err_conv(_decode_array_ptr(d, add)) or_return + dst = err_conv(_decode_array_ptr(d, add, allocator=allocator, loc=loc)) or_return return case Array: - dst = err_conv(_decode_array(d, add)) or_return + dst = err_conv(_decode_array(d, add, allocator=allocator, loc=loc)) or_return return } @@ -480,8 +482,8 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header case reflect.Type_Info_Slice: length, scap := err_conv(_decode_len_container(d, add)) or_return - data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return - defer if err != nil { mem.free_bytes(data) } + data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, allocator=allocator, loc=loc) or_return + defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) } da := mem.Raw_Dynamic_Array{raw_data(data), 0, length, context.allocator } @@ -489,7 +491,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if .Shrink_Excess in d.flags { // Ignoring an error here, but this is not critical to succeed. - _ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len) + _ = runtime.__dynamic_array_shrink(&da, t.elem.size, t.elem.align, da.len, loc=loc) } raw := (^mem.Raw_Slice)(v.data) @@ -500,8 +502,8 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header case reflect.Type_Info_Dynamic_Array: length, scap := err_conv(_decode_len_container(d, add)) or_return - data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align) or_return - defer if err != nil { mem.free_bytes(data) } + data := mem.alloc_bytes_non_zeroed(t.elem.size * scap, t.elem.align, loc=loc) or_return + defer if err != nil { mem.free_bytes(data, allocator=allocator, loc=loc) } raw := (^mem.Raw_Dynamic_Array)(v.data) raw.data = raw_data(data) @@ -513,7 +515,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if .Shrink_Excess in d.flags { // Ignoring an error here, but this is not critical to succeed. - _ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len) + _ = runtime.__dynamic_array_shrink(raw, t.elem.size, t.elem.align, raw.len, loc=loc) } return @@ -525,7 +527,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return _unsupported(v, hdr) } - da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator } + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator } out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return if out_of_space { return _unsupported(v, hdr) } @@ -539,7 +541,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return _unsupported(v, hdr) } - da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, context.allocator } + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator } out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return if out_of_space { return _unsupported(v, hdr) } @@ -553,7 +555,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return _unsupported(v, hdr) } - da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, context.allocator } + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 2, allocator } info: ^runtime.Type_Info switch ti.id { @@ -575,7 +577,7 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header return _unsupported(v, hdr) } - da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, context.allocator } + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, 4, allocator } info: ^runtime.Type_Info switch ti.id { @@ -593,17 +595,17 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header } } -_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add) -> (err: Unmarshal_Error) { +_unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, add: Add, allocator := context.allocator, loc := #caller_location) -> (err: Unmarshal_Error) { r := d.reader - decode_key :: proc(d: Decoder, v: any, allocator := context.allocator) -> (k: string, err: Unmarshal_Error) { + decode_key :: proc(d: Decoder, v: any, allocator := context.allocator, loc := #caller_location) -> (k: string, err: Unmarshal_Error) { entry_hdr := _decode_header(d.reader) or_return entry_maj, entry_add := _header_split(entry_hdr) #partial switch entry_maj { case .Text: - k = err_conv(_decode_text(d, entry_add, allocator)) or_return + k = err_conv(_decode_text(d, entry_add, allocator=allocator, loc=loc)) or_return return case .Bytes: - bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator)) or_return + bytes := err_conv(_decode_bytes(d, entry_add, allocator=allocator, loc=loc)) or_return k = string(bytes) return case: @@ -615,10 +617,10 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, // Allow generically storing the map array. switch &dst in v { case ^Map: - dst = err_conv(_decode_map_ptr(d, add)) or_return + dst = err_conv(_decode_map_ptr(d, add, allocator=allocator, loc=loc)) or_return return case Map: - dst = err_conv(_decode_map(d, add)) or_return + dst = err_conv(_decode_map(d, add, allocator=allocator, loc=loc)) or_return return } @@ -754,7 +756,7 @@ _unmarshal_map :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, // Unmarshal into a union, based on the `TAG_OBJECT_TYPE` tag of the spec, it denotes a tag which // contains an array of exactly two elements, the first is a textual representation of the following // CBOR value's type. -_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header) -> (err: Unmarshal_Error) { +_unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header, loc := #caller_location) -> (err: Unmarshal_Error) { r := d.reader #partial switch t in ti.variant { case reflect.Type_Info_Union: @@ -792,7 +794,7 @@ _unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header case reflect.Type_Info_Named: if vti.name == target_name { reflect.set_union_variant_raw_tag(v, tag) - return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return) + return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return, loc=loc) } case: @@ -804,7 +806,7 @@ _unmarshal_union :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if variant_name == target_name { reflect.set_union_variant_raw_tag(v, tag) - return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return) + return _unmarshal_value(d, any{v.data, variant.id}, _decode_header(r) or_return, loc=loc) } } } diff --git a/core/strings/builder.odin b/core/strings/builder.odin index 72eb815f9..11885b689 100644 --- a/core/strings/builder.odin +++ b/core/strings/builder.odin @@ -350,9 +350,9 @@ Output: ab */ -write_byte :: proc(b: ^Builder, x: byte) -> (n: int) { +write_byte :: proc(b: ^Builder, x: byte, loc := #caller_location) -> (n: int) { n0 := len(b.buf) - append(&b.buf, x) + append(&b.buf, x, loc) n1 := len(b.buf) return n1-n0 } @@ -380,9 +380,9 @@ NOTE: The backing dynamic array may be fixed in capacity or fail to resize, `n` Returns: - n: The number of bytes appended */ -write_bytes :: proc(b: ^Builder, x: []byte) -> (n: int) { +write_bytes :: proc(b: ^Builder, x: []byte, loc := #caller_location) -> (n: int) { n0 := len(b.buf) - append(&b.buf, ..x) + append(&b.buf, ..x, loc=loc) n1 := len(b.buf) return n1-n0 } diff --git a/tests/core/Makefile b/tests/core/Makefile index 6556f2916..0a1055120 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -45,13 +45,13 @@ crypto_test: $(ODIN) test crypto $(COMMON) -o:speed -out:test_crypto encoding_test: - $(ODIN) run encoding/hxa $(COMMON) $(COLLECTION) -out:test_hxa - $(ODIN) run encoding/json $(COMMON) -out:test_json - $(ODIN) run encoding/varint $(COMMON) -out:test_varint - $(ODIN) run encoding/xml $(COMMON) -out:test_xml - $(ODIN) run encoding/cbor $(COMMON) -out:test_cbor - $(ODIN) run encoding/hex $(COMMON) -out:test_hex - $(ODIN) run encoding/base64 $(COMMON) -out:test_base64 + $(ODIN) test encoding/base64 $(COMMON) -out:test_base64 + $(ODIN) test encoding/cbor $(COMMON) -out:test_cbor + $(ODIN) run encoding/hex $(COMMON) -out:test_hex + $(ODIN) run encoding/hxa $(COMMON) $(COLLECTION) -out:test_hxa + $(ODIN) run encoding/json $(COMMON) -out:test_json + $(ODIN) run encoding/varint $(COMMON) -out:test_varint + $(ODIN) run encoding/xml $(COMMON) -out:test_xml filepath_test: $(ODIN) run path/filepath $(COMMON) $(COLLECTION) -out:test_core_filepath diff --git a/tests/core/build.bat b/tests/core/build.bat index 60296090e..a26ccf176 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -21,13 +21,13 @@ echo --- echo --- echo Running core:encoding tests echo --- +%PATH_TO_ODIN% test encoding/base64 %COMMON% -out:test_base64.exe || exit /b +%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe || exit /b +%PATH_TO_ODIN% test encoding/hex %COMMON% -out:test_hex.exe || exit /b rem %PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe || exit /b -%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b -%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b -%PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b -%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe -define:ODIN_TEST_THREADS=1 -define:ODIN_TEST_FANCY=false || exit /b -%PATH_TO_ODIN% run encoding/hex %COMMON% -out:test_hex.exe || exit /b -%PATH_TO_ODIN% run encoding/base64 %COMMON% -out:test_base64.exe || exit /b +%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b +%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b +%PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b echo --- echo Running core:fmt tests diff --git a/tests/core/encoding/base64/base64.odin b/tests/core/encoding/base64/base64.odin index e48eea020..6679c8ce2 100644 --- a/tests/core/encoding/base64/base64.odin +++ b/tests/core/encoding/base64/base64.odin @@ -1,61 +1,38 @@ package test_encoding_base64 import "base:intrinsics" - import "core:encoding/base64" -import "core:fmt" -import "core:os" -import "core:reflect" import "core:testing" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect_value :: testing.expect_value - -} else { - expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { - TEST_count += 1 - ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) - if !ok { - TEST_fail += 1 - fmt.printf("[%v] expected %v, got %v\n", loc, expected, value) - } - return ok - } +Test :: struct { + vector: string, + base64: string, } -main :: proc() { - t := testing.T{} - - test_encoding(&t) - test_decoding(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } +tests :: []Test{ + {"", ""}, + {"f", "Zg=="}, + {"fo", "Zm8="}, + {"foo", "Zm9v"}, + {"foob", "Zm9vYg=="}, + {"fooba", "Zm9vYmE="}, + {"foobar", "Zm9vYmFy"}, } @(test) test_encoding :: proc(t: ^testing.T) { - expect_value(t, base64.encode(transmute([]byte)string("")), "") - expect_value(t, base64.encode(transmute([]byte)string("f")), "Zg==") - expect_value(t, base64.encode(transmute([]byte)string("fo")), "Zm8=") - expect_value(t, base64.encode(transmute([]byte)string("foo")), "Zm9v") - expect_value(t, base64.encode(transmute([]byte)string("foob")), "Zm9vYg==") - expect_value(t, base64.encode(transmute([]byte)string("fooba")), "Zm9vYmE=") - expect_value(t, base64.encode(transmute([]byte)string("foobar")), "Zm9vYmFy") + for test in tests { + v := base64.encode(transmute([]byte)test.vector) + defer delete(v) + testing.expect_value(t, v, test.base64) + } } @(test) test_decoding :: proc(t: ^testing.T) { - expect_value(t, string(base64.decode("")), "") - expect_value(t, string(base64.decode("Zg==")), "f") - expect_value(t, string(base64.decode("Zm8=")), "fo") - expect_value(t, string(base64.decode("Zm9v")), "foo") - expect_value(t, string(base64.decode("Zm9vYg==")), "foob") - expect_value(t, string(base64.decode("Zm9vYmE=")), "fooba") - expect_value(t, string(base64.decode("Zm9vYmFy")), "foobar") + for test in tests { + v := string(base64.decode(test.base64)) + defer delete(v) + testing.expect_value(t, v, test.vector) + } } diff --git a/tests/core/encoding/cbor/test_core_cbor.odin b/tests/core/encoding/cbor/test_core_cbor.odin index 72244e1d3..d069ef05b 100644 --- a/tests/core/encoding/cbor/test_core_cbor.odin +++ b/tests/core/encoding/cbor/test_core_cbor.odin @@ -1,105 +1,15 @@ package test_encoding_cbor import "base:intrinsics" - import "core:bytes" import "core:encoding/cbor" import "core:fmt" import "core:io" import "core:math/big" -import "core:mem" -import "core:os" import "core:reflect" import "core:testing" import "core:time" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - expect_value :: testing.expect_value - errorf :: testing.errorf - log :: testing.log - -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - - expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { - TEST_count += 1 - ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) - if !ok { - TEST_fail += 1 - fmt.printf("[%v] expected %v, got %v\n", loc, expected, value) - } - return ok - } - - errorf :: proc(t: ^testing.T, fmts: string, args: ..any, loc := #caller_location) { - TEST_fail += 1 - fmt.printf("[%v] ERROR: ", loc) - fmt.printf(fmts, ..args) - fmt.println() - } - - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - test_marshalling(&t) - - test_marshalling_maybe(&t) - test_marshalling_nil_maybe(&t) - - test_marshalling_union(&t) - - test_lying_length_array(&t) - - test_decode_unsigned(&t) - test_encode_unsigned(&t) - - test_decode_negative(&t) - test_encode_negative(&t) - - test_decode_simples(&t) - test_encode_simples(&t) - - test_decode_floats(&t) - test_encode_floats(&t) - - test_decode_bytes(&t) - test_encode_bytes(&t) - - test_decode_strings(&t) - test_encode_strings(&t) - - test_decode_lists(&t) - test_encode_lists(&t) - - test_decode_maps(&t) - test_encode_maps(&t) - - test_decode_tags(&t) - test_encode_tags(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - Foo :: struct { str: string, cstr: cstring, @@ -143,14 +53,6 @@ FooBars :: bit_set[FooBar; u16] @(test) test_marshalling :: proc(t: ^testing.T) { - tracker: mem.Tracking_Allocator - mem.tracking_allocator_init(&tracker, context.allocator) - context.allocator = mem.tracking_allocator(&tracker) - context.temp_allocator = context.allocator - defer mem.tracking_allocator_destroy(&tracker) - - ev :: expect_value - { nice := "16 is a nice number" now := time.Time{_nsec = 1701117968 * 1e9} @@ -205,18 +107,18 @@ test_marshalling :: proc(t: ^testing.T) { } data, err := cbor.marshal(f, cbor.ENCODE_FULLY_DETERMINISTIC) - ev(t, err, nil) + testing.expect_value(t, err, nil) defer delete(data) decoded, derr := cbor.decode(string(data)) - ev(t, derr, nil) + testing.expect_value(t, derr, nil) defer cbor.destroy(decoded) diagnosis, eerr := cbor.to_diagnostic_format(decoded) - ev(t, eerr, nil) + testing.expect_value(t, eerr, nil) defer delete(diagnosis) - ev(t, diagnosis, `{ + testing.expect_value(t, diagnosis, `{ "base64": 34("MTYgaXMgYSBuaWNlIG51bWJlcg=="), "biggest": 2(h'f951a9fd3c158afdff08ab8e0'), "biggie": 18446744073709551615, @@ -285,7 +187,7 @@ test_marshalling :: proc(t: ^testing.T) { backf: Foo uerr := cbor.unmarshal(string(data), &backf) - ev(t, uerr, nil) + testing.expect_value(t, uerr, nil) defer { delete(backf.str) delete(backf.cstr) @@ -304,104 +206,102 @@ test_marshalling :: proc(t: ^testing.T) { big.destroy(&backf.smallest) } - ev(t, backf.str, f.str) - ev(t, backf.cstr, f.cstr) + testing.expect_value(t, backf.str, f.str) + testing.expect_value(t, backf.cstr, f.cstr) #partial switch v in backf.value { case ^cbor.Map: for entry, i in v { fm := f.value.(^cbor.Map) - ev(t, entry.key, fm[i].key) + testing.expect_value(t, entry.key, fm[i].key) if str, is_str := entry.value.(^cbor.Text); is_str { - ev(t, str^, fm[i].value.(^cbor.Text)^) + testing.expect_value(t, str^, fm[i].value.(^cbor.Text)^) } else { - ev(t, entry.value, fm[i].value) + testing.expect_value(t, entry.value, fm[i].value) } } - case: errorf(t, "wrong type %v", v) + case: testing.expectf(t, false, "wrong type %v", v) } - ev(t, backf.neg, f.neg) - ev(t, backf.iamint, f.iamint) - ev(t, backf.base64, f.base64) - ev(t, backf.renamed, f.renamed) - ev(t, backf.now, f.now) - ev(t, backf.nowie, f.nowie) - for e, i in f.child.dyn { ev(t, backf.child.dyn[i], e) } - for key, value in f.child.mappy { ev(t, backf.child.mappy[key], value) } - ev(t, backf.child.my_integers, f.child.my_integers) - ev(t, len(backf.my_bytes), 0) - ev(t, len(backf.my_bytes), len(f.my_bytes)) - ev(t, backf.ennie, f.ennie) - ev(t, backf.ennieb, f.ennieb) - ev(t, backf.quat, f.quat) - ev(t, backf.comp, f.comp) - ev(t, backf.important, f.important) - ev(t, backf.no, nil) - ev(t, backf.nos, nil) - ev(t, backf.yes, f.yes) - ev(t, backf.biggie, f.biggie) - ev(t, backf.smallie, f.smallie) - ev(t, backf.onetwenty, f.onetwenty) - ev(t, backf.small_onetwenty, f.small_onetwenty) - ev(t, backf.ignore_this, nil) + testing.expect_value(t, backf.neg, f.neg) + testing.expect_value(t, backf.iamint, f.iamint) + testing.expect_value(t, backf.base64, f.base64) + testing.expect_value(t, backf.renamed, f.renamed) + testing.expect_value(t, backf.now, f.now) + testing.expect_value(t, backf.nowie, f.nowie) + for e, i in f.child.dyn { testing.expect_value(t, backf.child.dyn[i], e) } + for key, value in f.child.mappy { testing.expect_value(t, backf.child.mappy[key], value) } + testing.expect_value(t, backf.child.my_integers, f.child.my_integers) + testing.expect_value(t, len(backf.my_bytes), 0) + testing.expect_value(t, len(backf.my_bytes), len(f.my_bytes)) + testing.expect_value(t, backf.ennie, f.ennie) + testing.expect_value(t, backf.ennieb, f.ennieb) + testing.expect_value(t, backf.quat, f.quat) + testing.expect_value(t, backf.comp, f.comp) + testing.expect_value(t, backf.important, f.important) + testing.expect_value(t, backf.no, nil) + testing.expect_value(t, backf.nos, nil) + testing.expect_value(t, backf.yes, f.yes) + testing.expect_value(t, backf.biggie, f.biggie) + testing.expect_value(t, backf.smallie, f.smallie) + testing.expect_value(t, backf.onetwenty, f.onetwenty) + testing.expect_value(t, backf.small_onetwenty, f.small_onetwenty) + testing.expect_value(t, backf.ignore_this, nil) s_equals, s_err := big.equals(&backf.smallest, &f.smallest) - ev(t, s_err, nil) + testing.expect_value(t, s_err, nil) if !s_equals { - errorf(t, "smallest: %v does not equal %v", big.itoa(&backf.smallest), big.itoa(&f.smallest)) + testing.expectf(t, false, "smallest: %v does not equal %v", big.itoa(&backf.smallest), big.itoa(&f.smallest)) } b_equals, b_err := big.equals(&backf.biggest, &f.biggest) - ev(t, b_err, nil) + testing.expect_value(t, b_err, nil) if !b_equals { - errorf(t, "biggest: %v does not equal %v", big.itoa(&backf.biggest), big.itoa(&f.biggest)) + testing.expectf(t, false, "biggest: %v does not equal %v", big.itoa(&backf.biggest), big.itoa(&f.biggest)) } } - - for _, leak in tracker.allocation_map { - errorf(t, "%v leaked %m\n", leak.location, leak.size) - } - - for bad_free in tracker.bad_free_array { - errorf(t, "%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) - } } @(test) test_marshalling_maybe :: proc(t: ^testing.T) { maybe_test: Maybe(int) = 1 data, err := cbor.marshal(maybe_test) - expect_value(t, err, nil) + defer delete(data) + testing.expect_value(t, err, nil) val, derr := cbor.decode(string(data)) - expect_value(t, derr, nil) + testing.expect_value(t, derr, nil) - expect_value(t, cbor.to_diagnostic_format(val), "1") + diag := cbor.to_diagnostic_format(val) + testing.expect_value(t, diag, "1") + delete(diag) maybe_dest: Maybe(int) uerr := cbor.unmarshal(string(data), &maybe_dest) - expect_value(t, uerr, nil) - expect_value(t, maybe_dest, 1) + testing.expect_value(t, uerr, nil) + testing.expect_value(t, maybe_dest, 1) } @(test) test_marshalling_nil_maybe :: proc(t: ^testing.T) { maybe_test: Maybe(int) data, err := cbor.marshal(maybe_test) - expect_value(t, err, nil) + defer delete(data) + testing.expect_value(t, err, nil) val, derr := cbor.decode(string(data)) - expect_value(t, derr, nil) + testing.expect_value(t, derr, nil) - expect_value(t, cbor.to_diagnostic_format(val), "nil") + diag := cbor.to_diagnostic_format(val) + testing.expect_value(t, diag, "nil") + delete(diag) maybe_dest: Maybe(int) uerr := cbor.unmarshal(string(data), &maybe_dest) - expect_value(t, uerr, nil) - expect_value(t, maybe_dest, nil) + testing.expect_value(t, uerr, nil) + testing.expect_value(t, maybe_dest, nil) } @(test) @@ -427,17 +327,24 @@ test_marshalling_union :: proc(t: ^testing.T) { { test: My_Union = My_Distinct("Hello, World!") data, err := cbor.marshal(test) - expect_value(t, err, nil) + defer delete(data) + testing.expect_value(t, err, nil) val, derr := cbor.decode(string(data)) - expect_value(t, derr, nil) + defer cbor.destroy(val) + testing.expect_value(t, derr, nil) - expect_value(t, cbor.to_diagnostic_format(val, -1), `1010(["My_Distinct", "Hello, World!"])`) + diag := cbor.to_diagnostic_format(val, -1) + defer delete(diag) + testing.expect_value(t, diag, `1010(["My_Distinct", "Hello, World!"])`) dest: My_Union uerr := cbor.unmarshal(string(data), &dest) - expect_value(t, uerr, nil) - expect_value(t, dest, My_Distinct("Hello, World!")) + testing.expect_value(t, uerr, nil) + testing.expect_value(t, dest, My_Distinct("Hello, World!")) + if str, ok := dest.(My_Distinct); ok { + delete(string(str)) + } } My_Union_No_Nil :: union #no_nil { @@ -450,17 +357,21 @@ test_marshalling_union :: proc(t: ^testing.T) { { test: My_Union_No_Nil = My_Struct{.Two} data, err := cbor.marshal(test) - expect_value(t, err, nil) + defer delete(data) + testing.expect_value(t, err, nil) val, derr := cbor.decode(string(data)) - expect_value(t, derr, nil) + defer cbor.destroy(val) + testing.expect_value(t, derr, nil) - expect_value(t, cbor.to_diagnostic_format(val, -1), `1010(["My_Struct", {"my_enum": 1}])`) + diag := cbor.to_diagnostic_format(val, -1) + defer delete(diag) + testing.expect_value(t, diag, `1010(["My_Struct", {"my_enum": 1}])`) dest: My_Union_No_Nil uerr := cbor.unmarshal(string(data), &dest) - expect_value(t, uerr, nil) - expect_value(t, dest, My_Struct{.Two}) + testing.expect_value(t, uerr, nil) + testing.expect_value(t, dest, My_Struct{.Two}) } } @@ -469,7 +380,7 @@ test_lying_length_array :: proc(t: ^testing.T) { // Input says this is an array of length max(u64), this should not allocate that amount. input := []byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42} _, err := cbor.decode(string(input)) - expect_value(t, err, io.Error.Unexpected_EOF) // .Out_Of_Memory would be bad. + testing.expect_value(t, err, io.Error.Unexpected_EOF) // .Out_Of_Memory would be bad. } @(test) @@ -691,65 +602,73 @@ test_encode_lists :: proc(t: ^testing.T) { expect_streamed_encoding(t, "\x9f\xff", &cbor.Array{}) { - bytes.buffer_reset(&buf) + buf: bytes.Buffer + bytes.buffer_init_allocator(&buf, 0, 0) + defer bytes.buffer_destroy(&buf) + stream := bytes.buffer_to_stream(&buf) + encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} err: cbor.Encode_Error err = cbor.encode_stream_begin(stream, .Array) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) { err = cbor.encode_stream_array_item(encoder, u8(1)) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) err = cbor.encode_stream_array_item(encoder, &cbor.Array{u8(2), u8(3)}) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) err = cbor.encode_stream_begin(stream, .Array) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) { err = cbor.encode_stream_array_item(encoder, u8(4)) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) err = cbor.encode_stream_array_item(encoder, u8(5)) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) } err = cbor.encode_stream_end(stream) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) } err = cbor.encode_stream_end(stream) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) - expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x9f\x01\x82\x02\x03\x9f\x04\x05\xff\xff"))) + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x9f\x01\x82\x02\x03\x9f\x04\x05\xff\xff"))) } { - bytes.buffer_reset(&buf) + buf: bytes.Buffer + bytes.buffer_init_allocator(&buf, 0, 0) + defer bytes.buffer_destroy(&buf) + stream := bytes.buffer_to_stream(&buf) + encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} err: cbor.Encode_Error err = cbor._encode_u8(stream, 2, .Array) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) a := "a" err = cbor.encode(encoder, &a) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) { err = cbor.encode_stream_begin(stream, .Map) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) b := "b" c := "c" err = cbor.encode_stream_map_entry(encoder, &b, &c) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) err = cbor.encode_stream_end(stream) - expect_value(t, err, nil) + testing.expect_value(t, err, nil) } - expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x82\x61\x61\xbf\x61\x62\x61\x63\xff"))) + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)string("\x82\x61\x61\xbf\x61\x62\x61\x63\xff"))) } } @@ -807,30 +726,30 @@ expect_decoding :: proc(t: ^testing.T, encoded: string, decoded: string, type: t res, err := cbor.decode(encoded) defer cbor.destroy(res) - expect_value(t, reflect.union_variant_typeid(res), type, loc) - expect_value(t, err, nil, loc) + testing.expect_value(t, reflect.union_variant_typeid(res), type, loc) + testing.expect_value(t, err, nil, loc) str := cbor.to_diagnostic_format(res, padding=-1) defer delete(str) - expect_value(t, str, decoded, loc) + testing.expect_value(t, str, decoded, loc) } expect_tag :: proc(t: ^testing.T, encoded: string, nr: cbor.Tag_Number, value_decoded: string, loc := #caller_location) { res, err := cbor.decode(encoded) defer cbor.destroy(res) - expect_value(t, err, nil, loc) + testing.expect_value(t, err, nil, loc) if tag, is_tag := res.(^cbor.Tag); is_tag { - expect_value(t, tag.number, nr, loc) + testing.expect_value(t, tag.number, nr, loc) str := cbor.to_diagnostic_format(tag, padding=-1) defer delete(str) - expect_value(t, str, value_decoded, loc) + testing.expect_value(t, str, value_decoded, loc) } else { - errorf(t, "Value %#v is not a tag", res, loc) + testing.expectf(t, false, "Value %#v is not a tag", res, loc) } } @@ -838,35 +757,39 @@ expect_float :: proc(t: ^testing.T, encoded: string, expected: $T, loc := #calle res, err := cbor.decode(encoded) defer cbor.destroy(res) - expect_value(t, reflect.union_variant_typeid(res), typeid_of(T), loc) - expect_value(t, err, nil, loc) + testing.expect_value(t, reflect.union_variant_typeid(res), typeid_of(T), loc) + testing.expect_value(t, err, nil, loc) #partial switch r in res { case f16: - when T == f16 { expect_value(t, res, expected, loc) } else { unreachable() } + when T == f16 { testing.expect_value(t, res, expected, loc) } else { unreachable() } case f32: - when T == f32 { expect_value(t, res, expected, loc) } else { unreachable() } + when T == f32 { testing.expect_value(t, res, expected, loc) } else { unreachable() } case f64: - when T == f64 { expect_value(t, res, expected, loc) } else { unreachable() } + when T == f64 { testing.expect_value(t, res, expected, loc) } else { unreachable() } case: unreachable() } } -buf: bytes.Buffer -stream := bytes.buffer_to_stream(&buf) -encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} - expect_encoding :: proc(t: ^testing.T, val: cbor.Value, encoded: string, loc := #caller_location) { - bytes.buffer_reset(&buf) + buf: bytes.Buffer + bytes.buffer_init_allocator(&buf, 0, 0) + defer bytes.buffer_destroy(&buf) + stream := bytes.buffer_to_stream(&buf) + encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} - err := cbor.encode(encoder, val) - expect_value(t, err, nil, loc) - expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) + err := cbor.encode(encoder, val, loc) + testing.expect_value(t, err, nil, loc) + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) } expect_streamed_encoding :: proc(t: ^testing.T, encoded: string, values: ..cbor.Value, loc := #caller_location) { - bytes.buffer_reset(&buf) + buf: bytes.Buffer + bytes.buffer_init_allocator(&buf, 0, 0) + defer bytes.buffer_destroy(&buf) + stream := bytes.buffer_to_stream(&buf) + encoder := cbor.Encoder{cbor.ENCODE_FULLY_DETERMINISTIC, stream, {}} for value, i in values { err: cbor.Encode_Error @@ -891,15 +814,15 @@ expect_streamed_encoding :: proc(t: ^testing.T, encoded: string, values: ..cbor. if err2 != nil { break } } case: - errorf(t, "%v does not support streamed encoding", reflect.union_variant_typeid(value)) + testing.expectf(t, false, "%v does not support streamed encoding", reflect.union_variant_typeid(value)) } - expect_value(t, err, nil, loc) - expect_value(t, err2, nil, loc) + testing.expect_value(t, err, nil, loc) + testing.expect_value(t, err2, nil, loc) } err := cbor.encode_stream_end(stream) - expect_value(t, err, nil, loc) + testing.expect_value(t, err, nil, loc) - expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) + testing.expect_value(t, fmt.tprint(bytes.buffer_to_bytes(&buf)), fmt.tprint(transmute([]byte)encoded), loc) } From 1f6a6f2cd367aec2e506ae8d5fafcb34d4b2a0e3 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 18:04:59 -0400 Subject: [PATCH 068/270] Support deterministic random seeding of tests Add a new option `ODIN_TEST_RANDOM_SEED` which is picked from the cycle counter at startup, if it's not specified by the user. This number is sent to every test in the `T` struct and reset every test (just in case). --- core/testing/runner.odin | 14 ++++++++++++++ core/testing/testing.odin | 10 ++++++++++ 2 files changed, 24 insertions(+) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 759b23b0f..faba54286 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -9,6 +9,7 @@ import "core:encoding/base64" import "core:fmt" import "core:io" import pkg_log "core:log" +import "core:math/rand" import "core:mem" import "core:os" import "core:slice" @@ -38,6 +39,9 @@ FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true) USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false) // How many test results to show at a time per package. PROGRESS_WIDTH : int : #config(ODIN_TEST_PROGRESS_WIDTH, 24) +// This is the random seed that will be sent to each test. +// If it is unspecified, it will be set to the system cycle counter at startup. +SHARED_RANDOM_SEED : u64 : #config(ODIN_TEST_RANDOM_SEED, 0) end_t :: proc(t: ^T) { @@ -333,6 +337,12 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { defer bytes.buffer_destroy(&clipboard_buffer) } + when SHARED_RANDOM_SEED == 0 { + shared_random_seed := cast(u64)intrinsics.read_cycle_counter() + } else { + shared_random_seed := SHARED_RANDOM_SEED + } + // -- Setup initial tasks. // NOTE(Feoramund): This is the allocator that will be used by threads to @@ -356,6 +366,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { defer run_index += 1 data.it = it + data.t.seed = shared_random_seed #no_bounds_check data.t.channel = chan.as_send(task_channels[task_index].channel) data.t._log_allocator = shared_log_allocator data.allocator_index = task_index @@ -383,6 +394,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count) } + pkg_log.infof("The random seed sent to every test is: %v", shared_random_seed) + when TRACKING_MEMORY { when ALWAYS_REPORT_MEMORY { pkg_log.info("Memory tracking is enabled. Tests will log their memory usage when complete.") @@ -428,6 +441,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { defer run_index += 1 data.it = it + data.t.seed = shared_random_seed data.t.error_count = 0 thread.pool_add_task(&pool, task.allocator, run_test_task, data, run_index) diff --git a/core/testing/testing.odin b/core/testing/testing.odin index 30109304d..92b4d391d 100644 --- a/core/testing/testing.odin +++ b/core/testing/testing.odin @@ -29,6 +29,16 @@ Internal_Cleanup :: struct { T :: struct { error_count: int, + // If your test needs to perform random operations, it's advised to use + // this value to seed a local random number generator rather than relying + // on the non-thread-safe global one. + // + // This way, your results will be deterministic. + // + // This value is chosen at startup of the test runner, logged, and may be + // specified by the user. It is the same for all tests of a single run. + seed: u64, + channel: Update_Channel_Sender, cleanups: [dynamic]Internal_Cleanup, From b74b956fdaccbd930851e705ecc3e7d0f5d8a957 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 29 May 2024 18:09:41 -0400 Subject: [PATCH 069/270] Remove unneeded import --- core/testing/runner.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index faba54286..47ae5d528 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -9,7 +9,6 @@ import "core:encoding/base64" import "core:fmt" import "core:io" import pkg_log "core:log" -import "core:math/rand" import "core:mem" import "core:os" import "core:slice" From 40b20fb47353e61681ef815c43423d76b99e61d9 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 11:32:41 +0200 Subject: [PATCH 070/270] Port `tests\core\c\libc` --- tests/core/Makefile | 2 +- tests/core/build.bat | 5 +++ tests/core/c/libc/test_core_libc.odin | 36 ------------------- .../c/libc/test_core_libc_complex_pow.odin | 15 ++++---- 4 files changed, 13 insertions(+), 45 deletions(-) delete mode 100644 tests/core/c/libc/test_core_libc.odin diff --git a/tests/core/Makefile b/tests/core/Makefile index 0a1055120..4073da3f1 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -33,7 +33,7 @@ download_test_assets: $(PYTHON) download_assets.py c_libc_test: - $(ODIN) run c/libc $(COMMON) -out:test_core_libc + $(ODIN) test c/libc $(COMMON) -out:test_core_libc compress_test: $(ODIN) test compress $(COMMON) -out:test_core_compress diff --git a/tests/core/build.bat b/tests/core/build.bat index a26ccf176..69a7d432a 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -3,6 +3,11 @@ set COMMON=-no-bounds-check -vet -strict-style set COLLECTION=-collection:tests=.. set PATH_TO_ODIN==..\..\odin python3 download_assets.py +echo --- +echo Running core:c/libc tests +echo --- +%PATH_TO_ODIN% test c\libc %COMMON% -out:test_libc.exe || exit /b + echo --- echo Running core:compress tests echo --- diff --git a/tests/core/c/libc/test_core_libc.odin b/tests/core/c/libc/test_core_libc.odin deleted file mode 100644 index 9b5014dee..000000000 --- a/tests/core/c/libc/test_core_libc.odin +++ /dev/null @@ -1,36 +0,0 @@ -package test_core_libc - -import "core:fmt" -import "core:os" -import "core:testing" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_libc_complex(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} diff --git a/tests/core/c/libc/test_core_libc_complex_pow.odin b/tests/core/c/libc/test_core_libc_complex_pow.odin index 90928794c..cd50c8f6a 100644 --- a/tests/core/c/libc/test_core_libc_complex_pow.odin +++ b/tests/core/c/libc/test_core_libc_complex_pow.odin @@ -1,8 +1,8 @@ package test_core_libc import "core:testing" -import "core:fmt" import "core:c/libc" +import "core:log" reldiff :: proc(lhs, rhs: $T) -> f64 { if lhs == rhs { @@ -14,7 +14,7 @@ reldiff :: proc(lhs, rhs: $T) -> f64 { return out } -isclose :: proc(lhs, rhs: $T, rtol:f64 = 1e-12, atol:f64 = 1e-12) -> bool { +isclose :: proc(t: ^testing.T, lhs, rhs: $T, rtol:f64 = 1e-12, atol:f64 = 1e-12) -> bool { adiff := f64(abs(lhs - rhs)) if adiff < atol { return true @@ -23,7 +23,7 @@ isclose :: proc(lhs, rhs: $T, rtol:f64 = 1e-12, atol:f64 = 1e-12) -> bool { if rdiff < rtol { return true } - fmt.printf("not close -- lhs:%v rhs:%v -- adiff:%e rdiff:%e\n",lhs, rhs, adiff, rdiff) + log.infof("not close -- lhs:%v rhs:%v -- adiff:%e rdiff:%e\n",lhs, rhs, adiff, rdiff) return false } @@ -44,7 +44,6 @@ test_libc_complex :: proc(t: ^testing.T) { test_libc_pow_binding(t, libc.complex_float, f32, libc_powf, 1e-12, 1e-5) } -@test test_libc_pow_binding :: proc(t: ^testing.T, $LIBC_COMPLEX:typeid, $F:typeid, pow: proc(LIBC_COMPLEX, LIBC_COMPLEX) -> LIBC_COMPLEX, rtol: f64, atol: f64) { // Tests that c/libc/pow(f) functions have two arguments and that the function works as expected for simple inputs @@ -56,8 +55,8 @@ test_libc_pow_binding :: proc(t: ^testing.T, $LIBC_COMPLEX:typeid, $F:typeid, po for n in -4..=4 { complex_power := LIBC_COMPLEX(complex(F(n), F(0.))) result := pow(complex_base, complex_power) - expect(t, isclose(expected_real, F(real(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol)) - expect(t, isclose(expected_imag, F(imag(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol)) + testing.expectf(t, isclose(t, expected_real, F(real(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol) + testing.expectf(t, isclose(t, expected_imag, F(imag(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol) expected_real *= 2 } } @@ -83,8 +82,8 @@ test_libc_pow_binding :: proc(t: ^testing.T, $LIBC_COMPLEX:typeid, $F:typeid, po expected_real = 0. expected_imag = -value } - expect(t, isclose(expected_real, F(real(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol)) - expect(t, isclose(expected_imag, F(imag(result)), rtol, atol), fmt.tprintf("ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol)) + testing.expectf(t, isclose(t, expected_real, F(real(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, re(%v)) is greater than specified rtol:%e", F{}, n, expected_real, result, rtol) + testing.expectf(t, isclose(t, expected_imag, F(imag(result)), rtol, atol), "ftype:%T, n:%v reldiff(%v, im(%v)) is greater than specified rtol:%e", F{}, n, expected_imag, result, rtol) value *= 2 } } From 3404dea8ac741f98e3de4a341f64ef37d06550d9 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 11:42:34 +0200 Subject: [PATCH 071/270] Port `tests\encoding\hex` --- core/encoding/hex/hex.odin | 11 ++- tests/core/Makefile | 2 +- tests/core/encoding/hex/test_core_hex.odin | 94 ++++++++++------------ 3 files changed, 50 insertions(+), 57 deletions(-) diff --git a/core/encoding/hex/hex.odin b/core/encoding/hex/hex.odin index dbffe216b..c2cd89c5b 100644 --- a/core/encoding/hex/hex.odin +++ b/core/encoding/hex/hex.odin @@ -2,8 +2,8 @@ package encoding_hex import "core:strings" -encode :: proc(src: []byte, allocator := context.allocator) -> []byte #no_bounds_check { - dst := make([]byte, len(src) * 2, allocator) +encode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> []byte #no_bounds_check { + dst := make([]byte, len(src) * 2, allocator, loc) for i, j := 0, 0; i < len(src); i += 1 { v := src[i] dst[j] = HEXTABLE[v>>4] @@ -15,12 +15,12 @@ encode :: proc(src: []byte, allocator := context.allocator) -> []byte #no_bounds } -decode :: proc(src: []byte, allocator := context.allocator) -> (dst: []byte, ok: bool) #no_bounds_check { +decode :: proc(src: []byte, allocator := context.allocator, loc := #caller_location) -> (dst: []byte, ok: bool) #no_bounds_check { if len(src) % 2 == 1 { return } - dst = make([]byte, len(src) / 2, allocator) + dst = make([]byte, len(src) / 2, allocator, loc) for i, j := 0, 1; j < len(src); j += 2 { p := src[j-1] q := src[j] @@ -69,5 +69,4 @@ hex_digit :: proc(char: byte) -> (u8, bool) { case 'A' ..= 'F': return char - 'A' + 10, true case: return 0, false } -} - +} \ No newline at end of file diff --git a/tests/core/Makefile b/tests/core/Makefile index 4073da3f1..93d141e60 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -47,7 +47,7 @@ crypto_test: encoding_test: $(ODIN) test encoding/base64 $(COMMON) -out:test_base64 $(ODIN) test encoding/cbor $(COMMON) -out:test_cbor - $(ODIN) run encoding/hex $(COMMON) -out:test_hex + $(ODIN) test encoding/hex $(COMMON) -out:test_hex $(ODIN) run encoding/hxa $(COMMON) $(COLLECTION) -out:test_hxa $(ODIN) run encoding/json $(COMMON) -out:test_json $(ODIN) run encoding/varint $(COMMON) -out:test_varint diff --git a/tests/core/encoding/hex/test_core_hex.odin b/tests/core/encoding/hex/test_core_hex.odin index d928cd28e..6a00c9705 100644 --- a/tests/core/encoding/hex/test_core_hex.odin +++ b/tests/core/encoding/hex/test_core_hex.odin @@ -2,42 +2,6 @@ package test_core_hex import "core:encoding/hex" import "core:testing" -import "core:fmt" -import "core:os" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - hex_encode(&t) - hex_decode(&t) - hex_decode_sequence(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} CASES :: [][2]string{ {"11", "3131"}, @@ -49,10 +13,14 @@ CASES :: [][2]string{ hex_encode :: proc(t: ^testing.T) { for test in CASES { encoded := string(hex.encode(transmute([]byte)test[0])) - expect( + defer delete(encoded) + testing.expectf( t, encoded == test[1], - fmt.tprintf("encode: %q -> %q (should be: %q)", test[0], encoded, test[1]), + "encode: %q -> %q (should be: %q)", + test[0], + encoded, + test[1], ) } } @@ -61,11 +29,20 @@ hex_encode :: proc(t: ^testing.T) { hex_decode :: proc(t: ^testing.T) { for test in CASES { decoded, ok := hex.decode(transmute([]byte)test[1]) - expect(t, ok, fmt.tprintf("decode: %q not ok", test[1])) - expect( + defer delete(decoded) + testing.expectf( + t, + ok, + "decode: %q not ok", + test[1], + ) + testing.expectf( t, string(decoded) == test[0], - fmt.tprintf("decode: %q -> %q (should be: %q)", test[1], string(decoded), test[0]), + "decode: %q -> %q (should be: %q)", + test[1], + string(decoded), + test[0], ) } } @@ -73,20 +50,37 @@ hex_decode :: proc(t: ^testing.T) { @(test) hex_decode_sequence :: proc(t: ^testing.T) { b, ok := hex.decode_sequence("0x23") - expect(t, ok, "decode_sequence: 0x23 not ok") - expect(t, b == '#', fmt.tprintf("decode_sequence: 0x23 -> %c (should be: %c)", b, '#')) + testing.expect(t, ok, "decode_sequence: 0x23 not ok") + testing.expectf( + t, + b == '#', + "decode_sequence: 0x23 -> %c (should be: %c)", + b, + '#', + ) b, ok = hex.decode_sequence("0X3F") - expect(t, ok, "decode_sequence: 0X3F not ok") - expect(t, b == '?', fmt.tprintf("decode_sequence: 0X3F -> %c (should be: %c)", b, '?')) + testing.expect(t, ok, "decode_sequence: 0X3F not ok") + testing.expectf( + t, + b == '?', + "decode_sequence: 0X3F -> %c (should be: %c)", + b, + '?', + ) b, ok = hex.decode_sequence("2a") - expect(t, ok, "decode_sequence: 2a not ok") - expect(t, b == '*', fmt.tprintf("decode_sequence: 2a -> %c (should be: %c)", b, '*')) + testing.expect(t, ok, "decode_sequence: 2a not ok") + testing.expectf(t, + b == '*', + "decode_sequence: 2a -> %c (should be: %c)", + b, + '*', + ) _, ok = hex.decode_sequence("1") - expect(t, !ok, "decode_sequence: 1 should be too short") + testing.expect(t, !ok, "decode_sequence: 1 should be too short") _, ok = hex.decode_sequence("123") - expect(t, !ok, "decode_sequence: 123 should be too long") -} + testing.expect(t, !ok, "decode_sequence: 123 should be too long") +} \ No newline at end of file From 1b32e27aa47c59a71b1cf4fefd40ce6e5ffc5bfb Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 13:10:38 +0200 Subject: [PATCH 072/270] Port `tests\core\encoding\hxa` And fix a few leaks in `core:encoding/hxa` while at it. --- core/encoding/hxa/hxa.odin | 31 ++-- core/encoding/hxa/read.odin | 42 ++--- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/encoding/hxa/test_core_hxa.odin | 202 +++++++++------------ 5 files changed, 125 insertions(+), 154 deletions(-) diff --git a/core/encoding/hxa/hxa.odin b/core/encoding/hxa/hxa.odin index 9b24ede9c..9d0c58196 100644 --- a/core/encoding/hxa/hxa.odin +++ b/core/encoding/hxa/hxa.odin @@ -160,34 +160,35 @@ CONVENTION_SOFT_TRANSFORM :: "transform" /* destroy procedures */ -meta_destroy :: proc(meta: Meta, allocator := context.allocator) { +meta_destroy :: proc(meta: Meta, allocator := context.allocator, loc := #caller_location) { if nested, ok := meta.value.([]Meta); ok { for m in nested { - meta_destroy(m) + meta_destroy(m, loc=loc) } - delete(nested, allocator) + delete(nested, allocator, loc=loc) } } -nodes_destroy :: proc(nodes: []Node, allocator := context.allocator) { +nodes_destroy :: proc(nodes: []Node, allocator := context.allocator, loc := #caller_location) { for node in nodes { for meta in node.meta_data { - meta_destroy(meta) + meta_destroy(meta, loc=loc) } - delete(node.meta_data, allocator) + delete(node.meta_data, allocator, loc=loc) switch n in node.content { case Node_Geometry: - delete(n.corner_stack, allocator) - delete(n.edge_stack, allocator) - delete(n.face_stack, allocator) + delete(n.corner_stack, allocator, loc=loc) + delete(n.vertex_stack, allocator, loc=loc) + delete(n.edge_stack, allocator, loc=loc) + delete(n.face_stack, allocator, loc=loc) case Node_Image: - delete(n.image_stack, allocator) + delete(n.image_stack, allocator, loc=loc) } } - delete(nodes, allocator) + delete(nodes, allocator, loc=loc) } -file_destroy :: proc(file: File) { - nodes_destroy(file.nodes, file.allocator) - delete(file.backing, file.allocator) -} +file_destroy :: proc(file: File, loc := #caller_location) { + nodes_destroy(file.nodes, file.allocator, loc=loc) + delete(file.backing, file.allocator, loc=loc) +} \ No newline at end of file diff --git a/core/encoding/hxa/read.odin b/core/encoding/hxa/read.odin index f37dc3193..5c8503229 100644 --- a/core/encoding/hxa/read.odin +++ b/core/encoding/hxa/read.odin @@ -11,24 +11,21 @@ Read_Error :: enum { Unable_To_Read_File, } -read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) { +read_from_file :: proc(filename: string, print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) { context.allocator = allocator - data, ok := os.read_entire_file(filename) + data, ok := os.read_entire_file(filename, allocator, loc) if !ok { err = .Unable_To_Read_File + delete(data, allocator, loc) return } - defer if !ok { - delete(data) - } else { - file.backing = data - } - file, err = read(data, filename, print_error, allocator) + file, err = read(data, filename, print_error, allocator, loc) + file.backing = data return } -read :: proc(data: []byte, filename := "", print_error := false, allocator := context.allocator) -> (file: File, err: Read_Error) { +read :: proc(data: []byte, filename := "", print_error := false, allocator := context.allocator, loc := #caller_location) -> (file: File, err: Read_Error) { Reader :: struct { filename: string, data: []byte, @@ -79,8 +76,8 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato return string(data[:len]), nil } - read_meta :: proc(r: ^Reader, capacity: u32le) -> (meta_data: []Meta, err: Read_Error) { - meta_data = make([]Meta, int(capacity)) + read_meta :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (meta_data: []Meta, err: Read_Error) { + meta_data = make([]Meta, int(capacity), allocator=allocator) count := 0 defer meta_data = meta_data[:count] for &m in meta_data { @@ -111,10 +108,10 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato return } - read_layer_stack :: proc(r: ^Reader, capacity: u32le) -> (layers: Layer_Stack, err: Read_Error) { + read_layer_stack :: proc(r: ^Reader, capacity: u32le, allocator := context.allocator, loc := #caller_location) -> (layers: Layer_Stack, err: Read_Error) { stack_count := read_value(r, u32le) or_return layer_count := 0 - layers = make(Layer_Stack, stack_count) + layers = make(Layer_Stack, stack_count, allocator=allocator, loc=loc) defer layers = layers[:layer_count] for &layer in layers { layer.name = read_name(r) or_return @@ -170,7 +167,8 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato node_count := 0 file.header = header^ - file.nodes = make([]Node, header.internal_node_count) + file.nodes = make([]Node, header.internal_node_count, allocator=allocator, loc=loc) + file.allocator = allocator defer if err != nil { nodes_destroy(file.nodes) file.nodes = nil @@ -198,15 +196,15 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato case .Geometry: g: Node_Geometry - g.vertex_count = read_value(r, u32le) or_return - g.vertex_stack = read_layer_stack(r, g.vertex_count) or_return - g.edge_corner_count = read_value(r, u32le) or_return - g.corner_stack = read_layer_stack(r, g.edge_corner_count) or_return + g.vertex_count = read_value(r, u32le) or_return + g.vertex_stack = read_layer_stack(r, g.vertex_count, loc=loc) or_return + g.edge_corner_count = read_value(r, u32le) or_return + g.corner_stack = read_layer_stack(r, g.edge_corner_count, loc=loc) or_return if header.version > 2 { - g.edge_stack = read_layer_stack(r, g.edge_corner_count) or_return + g.edge_stack = read_layer_stack(r, g.edge_corner_count, loc=loc) or_return } - g.face_count = read_value(r, u32le) or_return - g.face_stack = read_layer_stack(r, g.face_count) or_return + g.face_count = read_value(r, u32le) or_return + g.face_stack = read_layer_stack(r, g.face_count, loc=loc) or_return node.content = g @@ -233,4 +231,4 @@ read :: proc(data: []byte, filename := "", print_error := false, allocato } return -} +} \ No newline at end of file diff --git a/tests/core/Makefile b/tests/core/Makefile index 93d141e60..46b9e352e 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -48,7 +48,7 @@ encoding_test: $(ODIN) test encoding/base64 $(COMMON) -out:test_base64 $(ODIN) test encoding/cbor $(COMMON) -out:test_cbor $(ODIN) test encoding/hex $(COMMON) -out:test_hex - $(ODIN) run encoding/hxa $(COMMON) $(COLLECTION) -out:test_hxa + $(ODIN) test encoding/hxa $(COMMON) -out:test_hxa $(ODIN) run encoding/json $(COMMON) -out:test_json $(ODIN) run encoding/varint $(COMMON) -out:test_varint $(ODIN) run encoding/xml $(COMMON) -out:test_xml diff --git a/tests/core/build.bat b/tests/core/build.bat index 69a7d432a..094a12f14 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -29,7 +29,7 @@ echo --- %PATH_TO_ODIN% test encoding/base64 %COMMON% -out:test_base64.exe || exit /b %PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe || exit /b %PATH_TO_ODIN% test encoding/hex %COMMON% -out:test_hex.exe || exit /b -rem %PATH_TO_ODIN% run encoding/hxa %COMMON% %COLLECTION% -out:test_hxa.exe || exit /b +%PATH_TO_ODIN% test encoding/hxa %COMMON% -out:test_hxa.exe || exit /b %PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b %PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b %PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b diff --git a/tests/core/encoding/hxa/test_core_hxa.odin b/tests/core/encoding/hxa/test_core_hxa.odin index 40c3c2e23..31d40c8b3 100644 --- a/tests/core/encoding/hxa/test_core_hxa.odin +++ b/tests/core/encoding/hxa/test_core_hxa.odin @@ -6,127 +6,99 @@ package test_core_hxa import "core:encoding/hxa" import "core:fmt" import "core:testing" -import tc "tests:common" -TEAPOT_PATH :: "core/assets/HXA/teapot.hxa" +TEAPOT_PATH :: ODIN_ROOT + "tests/core/assets/HXA/teapot.hxa" -main :: proc() { - t := testing.T{} - - test_read(&t) - test_write(&t) - - tc.report(&t) -} +import "core:os" @test test_read :: proc(t: ^testing.T) { - filename := tc.get_data_path(t, TEAPOT_PATH) - defer delete(filename) + data, _ := os.read_entire_file(TEAPOT_PATH) + // file, err := hxa.read_from_file(TEAPOT_PATH) + file, err := hxa.read(data) + file.backing = data + file.allocator = context.allocator + hxa.file_destroy(file) + // fmt.printfln("%#v", file) - file, err := hxa.read_from_file(filename) e :: hxa.Read_Error.None - tc.expect(t, err == e, fmt.tprintf("%v: read_from_file(%v) -> %v != %v", #procedure, filename, err, e)) - defer hxa.file_destroy(file) + testing.expectf(t, err == e, "read_from_file(%v) -> %v != %v", TEAPOT_PATH, err, e) /* Header */ - tc.expect(t, file.magic_number == 0x417848, fmt.tprintf("%v: file.magic_number %v != %v", - #procedure, file.magic_number, 0x417848)) - tc.expect(t, file.version == 1, fmt.tprintf("%v: file.version %v != %v", - #procedure, file.version, 1)) - tc.expect(t, file.internal_node_count == 1, fmt.tprintf("%v: file.internal_node_count %v != %v", - #procedure, file.internal_node_count, 1)) + testing.expectf(t, file.magic_number == 0x417848, "file.magic_number %v != %v", file.magic_number, 0x417848) + testing.expectf(t, file.version == 1, "file.version %v != %v", file.version, 1) + testing.expectf(t, file.internal_node_count == 1, "file.internal_node_count %v != %v", file.internal_node_count, 1) /* Nodes (only one) */ - tc.expect(t, len(file.nodes) == 1, fmt.tprintf("%v: len(file.nodes) %v != %v", #procedure, len(file.nodes), 1)) + testing.expectf(t, len(file.nodes) == 1, "len(file.nodes) %v != %v", len(file.nodes), 1) m := &file.nodes[0].meta_data - tc.expect(t, len(m^) == 38, fmt.tprintf("%v: len(m^) %v != %v", #procedure, len(m^), 38)) + testing.expectf(t, len(m^) == 38, "len(m^) %v != %v", len(m^), 38) { e :: "Texture resolution" - tc.expect(t, m[0].name == e, fmt.tprintf("%v: m[0].name %v != %v", #procedure, m[0].name, e)) + testing.expectf(t, m[0].name == e, "m[0].name %v != %v", m[0].name, e) m_v, m_v_ok := m[0].value.([]i64le) - tc.expect(t, m_v_ok, fmt.tprintf("%v: m_v_ok %v != %v", #procedure, m_v_ok, true)) - tc.expect(t, len(m_v) == 1, fmt.tprintf("%v: len(m_v) %v != %v", #procedure, len(m_v), 1)) - tc.expect(t, m_v[0] == 1024, fmt.tprintf("%v: m_v[0] %v != %v", #procedure, len(m_v), 1024)) + testing.expectf(t, m_v_ok, "m_v_ok %v != %v", m_v_ok, true) + testing.expectf(t, len(m_v) == 1, "len(m_v) %v != %v", len(m_v), 1) + testing.expectf(t, m_v[0] == 1024, "m_v[0] %v != %v", len(m_v), 1024) } { e :: "Validate" - tc.expect(t, m[37].name == e, fmt.tprintf("%v: m[37].name %v != %v", #procedure, m[37].name, e)) + testing.expectf(t, m[37].name == e, "m[37].name %v != %v", m[37].name, e) m_v, m_v_ok := m[37].value.([]i64le) - tc.expect(t, m_v_ok, fmt.tprintf("%v: m_v_ok %v != %v", #procedure, m_v_ok, true)) - tc.expect(t, len(m_v) == 1, fmt.tprintf("%v: len(m_v) %v != %v", #procedure, len(m_v), 1)) - tc.expect(t, m_v[0] == -2054847231, fmt.tprintf("%v: m_v[0] %v != %v", #procedure, len(m_v), -2054847231)) + testing.expectf(t, m_v_ok, "m_v_ok %v != %v", m_v_ok, true) + testing.expectf(t, len(m_v) == 1, "len(m_v) %v != %v", len(m_v), 1) + testing.expectf(t, m_v[0] == -2054847231, "m_v[0] %v != %v", len(m_v), -2054847231) } /* Node content */ v, v_ok := file.nodes[0].content.(hxa.Node_Geometry) - tc.expect(t, v_ok, fmt.tprintf("%v: v_ok %v != %v", #procedure, v_ok, true)) + testing.expectf(t, v_ok, "v_ok %v != %v", v_ok, true) - tc.expect(t, v.vertex_count == 530, fmt.tprintf("%v: v.vertex_count %v != %v", #procedure, v.vertex_count, 530)) - tc.expect(t, v.edge_corner_count == 2026, fmt.tprintf("%v: v.edge_corner_count %v != %v", - #procedure, v.edge_corner_count, 2026)) - tc.expect(t, v.face_count == 517, fmt.tprintf("%v: v.face_count %v != %v", #procedure, v.face_count, 517)) + testing.expectf(t, v.vertex_count == 530, "v.vertex_count %v != %v", v.vertex_count, 530) + testing.expectf(t, v.edge_corner_count == 2026, "v.edge_corner_count %v != %v", v.edge_corner_count, 2026) + testing.expectf(t, v.face_count == 517, "v.face_count %v != %v", v.face_count, 517) /* Vertex stack */ - tc.expect(t, len(v.vertex_stack) == 1, fmt.tprintf("%v: len(v.vertex_stack) %v != %v", - #procedure, len(v.vertex_stack), 1)) + testing.expectf(t, len(v.vertex_stack) == 1, "len(v.vertex_stack) %v != %v", len(v.vertex_stack), 1) { e := "vertex" - tc.expect(t, v.vertex_stack[0].name == e, fmt.tprintf("%v: v.vertex_stack[0].name %v != %v", - #procedure, v.vertex_stack[0].name, e)) + testing.expectf(t, v.vertex_stack[0].name == e, "v.vertex_stack[0].name %v != %v", v.vertex_stack[0].name, e) } - tc.expect(t, v.vertex_stack[0].components == 3, fmt.tprintf("%v: v.vertex_stack[0].components %v != %v", - #procedure, v.vertex_stack[0].components, 3)) + testing.expectf(t, v.vertex_stack[0].components == 3, "v.vertex_stack[0].components %v != %v", v.vertex_stack[0].components, 3) /* Vertex stack data */ vs_d, vs_d_ok := v.vertex_stack[0].data.([]f64le) - tc.expect(t, vs_d_ok, fmt.tprintf("%v: vs_d_ok %v != %v", #procedure, vs_d_ok, true)) - tc.expect(t, len(vs_d) == 1590, fmt.tprintf("%v: len(vs_d) %v != %v", #procedure, len(vs_d), 1590)) - - tc.expect(t, vs_d[0] == 4.06266, fmt.tprintf("%v: vs_d[0] %v (%h) != %v (%h)", - #procedure, vs_d[0], vs_d[0], 4.06266, 4.06266)) - tc.expect(t, vs_d[1] == 2.83457, fmt.tprintf("%v: vs_d[1] %v (%h) != %v (%h)", - #procedure, vs_d[1], vs_d[1], 2.83457, 2.83457)) - tc.expect(t, vs_d[2] == 0hbfbc5da6a4441787, fmt.tprintf("%v: vs_d[2] %v (%h) != %v (%h)", - #procedure, vs_d[2], vs_d[2], - 0hbfbc5da6a4441787, 0hbfbc5da6a4441787)) - tc.expect(t, vs_d[3] == 0h4010074fb549f948, fmt.tprintf("%v: vs_d[3] %v (%h) != %v (%h)", - #procedure, vs_d[3], vs_d[3], - 0h4010074fb549f948, 0h4010074fb549f948)) - tc.expect(t, vs_d[1587] == 0h400befa82e87d2c7, fmt.tprintf("%v: vs_d[1587] %v (%h) != %v (%h)", - #procedure, vs_d[1587], vs_d[1587], - 0h400befa82e87d2c7, 0h400befa82e87d2c7)) - tc.expect(t, vs_d[1588] == 2.83457, fmt.tprintf("%v: vs_d[1588] %v (%h) != %v (%h)", - #procedure, vs_d[1588], vs_d[1588], 2.83457, 2.83457)) - tc.expect(t, vs_d[1589] == -1.56121, fmt.tprintf("%v: vs_d[1589] %v (%h) != %v (%h)", - #procedure, vs_d[1589], vs_d[1589], -1.56121, -1.56121)) + testing.expectf(t, vs_d_ok, "vs_d_ok %v != %v", vs_d_ok, true) + testing.expectf(t, len(vs_d) == 1590, "len(vs_d) %v != %v", len(vs_d), 1590) + testing.expectf(t, vs_d[0] == 4.06266, "vs_d[0] %v (%h) != %v (%h)", vs_d[0], vs_d[0], 4.06266, 4.06266) + testing.expectf(t, vs_d[1] == 2.83457, "vs_d[1] %v (%h) != %v (%h)", vs_d[1], vs_d[1], 2.83457, 2.83457) + testing.expectf(t, vs_d[2] == 0hbfbc5da6a4441787, "vs_d[2] %v (%h) != %v (%h)", vs_d[2], vs_d[2], 0hbfbc5da6a4441787, 0hbfbc5da6a4441787) + testing.expectf(t, vs_d[3] == 0h4010074fb549f948, "vs_d[3] %v (%h) != %v (%h)", vs_d[3], vs_d[3], 0h4010074fb549f948, 0h4010074fb549f948) + testing.expectf(t, vs_d[1587] == 0h400befa82e87d2c7, "vs_d[1587] %v (%h) != %v (%h)", vs_d[1587], vs_d[1587], 0h400befa82e87d2c7, 0h400befa82e87d2c7) + testing.expectf(t, vs_d[1588] == 2.83457, "vs_d[1588] %v (%h) != %v (%h)", vs_d[1588], vs_d[1588], 2.83457, 2.83457) + testing.expectf(t, vs_d[1589] == -1.56121, "vs_d[1589] %v (%h) != %v (%h)", vs_d[1589], vs_d[1589], -1.56121, -1.56121) /* Corner stack */ - tc.expect(t, len(v.corner_stack) == 1, - fmt.tprintf("%v: len(v.corner_stack) %v != %v", #procedure, len(v.corner_stack), 1)) + testing.expectf(t, len(v.corner_stack) == 1, "len(v.corner_stack) %v != %v", len(v.corner_stack), 1) { e := "reference" - tc.expect(t, v.corner_stack[0].name == e, fmt.tprintf("%v: v.corner_stack[0].name %v != %v", - #procedure, v.corner_stack[0].name, e)) + testing.expectf(t, v.corner_stack[0].name == e, "v.corner_stack[0].name %v != %v", v.corner_stack[0].name, e) } - tc.expect(t, v.corner_stack[0].components == 1, fmt.tprintf("%v: v.corner_stack[0].components %v != %v", - #procedure, v.corner_stack[0].components, 1)) + testing.expectf(t, v.corner_stack[0].components == 1, "v.corner_stack[0].components %v != %v", v.corner_stack[0].components, 1) /* Corner stack data */ cs_d, cs_d_ok := v.corner_stack[0].data.([]i32le) - tc.expect(t, cs_d_ok, fmt.tprintf("%v: cs_d_ok %v != %v", #procedure, cs_d_ok, true)) - tc.expect(t, len(cs_d) == 2026, fmt.tprintf("%v: len(cs_d) %v != %v", #procedure, len(cs_d), 2026)) - tc.expect(t, cs_d[0] == 6, fmt.tprintf("%v: cs_d[0] %v != %v", #procedure, cs_d[0], 6)) - tc.expect(t, cs_d[2025] == -32, fmt.tprintf("%v: cs_d[2025] %v != %v", #procedure, cs_d[2025], -32)) + testing.expectf(t, cs_d_ok, "cs_d_ok %v != %v", cs_d_ok, true) + testing.expectf(t, len(cs_d) == 2026, "len(cs_d) %v != %v", len(cs_d), 2026) + testing.expectf(t, cs_d[0] == 6, "cs_d[0] %v != %v", cs_d[0], 6) + testing.expectf(t, cs_d[2025] == -32, "cs_d[2025] %v != %v", cs_d[2025], -32) /* Edge and face stacks (empty) */ - tc.expect(t, len(v.edge_stack) == 0, fmt.tprintf("%v: len(v.edge_stack) %v != %v", - #procedure, len(v.edge_stack), 0)) - tc.expect(t, len(v.face_stack) == 0, fmt.tprintf("%v: len(v.face_stack) %v != %v", - #procedure, len(v.face_stack), 0)) + testing.expectf(t, len(v.edge_stack) == 0, "len(v.edge_stack) %v != %v", len(v.edge_stack), 0) + testing.expectf(t, len(v.face_stack) == 0, "len(v.face_stack) %v != %v", len(v.face_stack), 0) } @test @@ -154,72 +126,72 @@ test_write :: proc(t: ^testing.T) { n, write_err := hxa.write(buf, w_file) write_e :: hxa.Write_Error.None - tc.expect(t, write_err == write_e, fmt.tprintf("%v: write_err %v != %v", #procedure, write_err, write_e)) - tc.expect(t, n == required_size, fmt.tprintf("%v: n %v != %v", #procedure, n, required_size)) + testing.expectf(t, write_err == write_e, fmt.tprintf("write_err %v != %v", write_err, write_e)) + testing.expectf(t, n == required_size, fmt.tprintf("n %v != %v", n, required_size)) file, read_err := hxa.read(buf) read_e :: hxa.Read_Error.None - tc.expect(t, read_err == read_e, fmt.tprintf("%v: read_err %v != %v", #procedure, read_err, read_e)) + testing.expectf(t, read_err == read_e, fmt.tprintf("read_err %v != %v", read_err, read_e)) defer hxa.file_destroy(file) - tc.expect(t, file.magic_number == 0x417848, fmt.tprintf("%v: file.magic_number %v != %v", - #procedure, file.magic_number, 0x417848)) - tc.expect(t, file.version == 3, fmt.tprintf("%v: file.version %v != %v", #procedure, file.version, 3)) - tc.expect(t, file.internal_node_count == 1, fmt.tprintf("%v: file.internal_node_count %v != %v", - #procedure, file.internal_node_count, 1)) + testing.expectf(t, file.magic_number == 0x417848, fmt.tprintf("file.magic_number %v != %v", + file.magic_number, 0x417848)) + testing.expectf(t, file.version == 3, fmt.tprintf("file.version %v != %v", file.version, 3)) + testing.expectf(t, file.internal_node_count == 1, fmt.tprintf("file.internal_node_count %v != %v", + file.internal_node_count, 1)) - tc.expect(t, len(file.nodes) == len(w_file.nodes), fmt.tprintf("%v: len(file.nodes) %v != %v", - #procedure, len(file.nodes), len(w_file.nodes))) + testing.expectf(t, len(file.nodes) == len(w_file.nodes), fmt.tprintf("len(file.nodes) %v != %v", + len(file.nodes), len(w_file.nodes))) m := &file.nodes[0].meta_data w_m := &w_file.nodes[0].meta_data - tc.expect(t, len(m^) == len(w_m^), fmt.tprintf("%v: len(m^) %v != %v", #procedure, len(m^), len(w_m^))) - tc.expect(t, m[0].name == w_m[0].name, fmt.tprintf("%v: m[0].name %v != %v", #procedure, m[0].name, w_m[0].name)) + testing.expectf(t, len(m^) == len(w_m^), fmt.tprintf("len(m^) %v != %v", len(m^), len(w_m^))) + testing.expectf(t, m[0].name == w_m[0].name, fmt.tprintf("m[0].name %v != %v", m[0].name, w_m[0].name)) m_v, m_v_ok := m[0].value.([]f64le) - tc.expect(t, m_v_ok, fmt.tprintf("%v: m_v_ok %v != %v", #procedure, m_v_ok, true)) - tc.expect(t, len(m_v) == len(n1_m1_value), fmt.tprintf("%v: %v != len(m_v) %v", - #procedure, len(m_v), len(n1_m1_value))) + testing.expectf(t, m_v_ok, fmt.tprintf("m_v_ok %v != %v", m_v_ok, true)) + testing.expectf(t, len(m_v) == len(n1_m1_value), fmt.tprintf("%v != len(m_v) %v", + len(m_v), len(n1_m1_value))) for i := 0; i < len(m_v); i += 1 { - tc.expect(t, m_v[i] == n1_m1_value[i], fmt.tprintf("%v: m_v[%d] %v != %v", - #procedure, i, m_v[i], n1_m1_value[i])) + testing.expectf(t, m_v[i] == n1_m1_value[i], fmt.tprintf("m_v[%d] %v != %v", + i, m_v[i], n1_m1_value[i])) } v, v_ok := file.nodes[0].content.(hxa.Node_Image) - tc.expect(t, v_ok, fmt.tprintf("%v: v_ok %v != %v", #procedure, v_ok, true)) - tc.expect(t, v.type == n1_content.type, fmt.tprintf("%v: v.type %v != %v", #procedure, v.type, n1_content.type)) - tc.expect(t, len(v.resolution) == 3, fmt.tprintf("%v: len(v.resolution) %v != %v", - #procedure, len(v.resolution), 3)) - tc.expect(t, len(v.image_stack) == len(n1_content.image_stack), fmt.tprintf("%v: len(v.image_stack) %v != %v", - #procedure, len(v.image_stack), len(n1_content.image_stack))) + testing.expectf(t, v_ok, fmt.tprintf("v_ok %v != %v", v_ok, true)) + testing.expectf(t, v.type == n1_content.type, fmt.tprintf("v.type %v != %v", v.type, n1_content.type)) + testing.expectf(t, len(v.resolution) == 3, fmt.tprintf("len(v.resolution) %v != %v", + len(v.resolution), 3)) + testing.expectf(t, len(v.image_stack) == len(n1_content.image_stack), fmt.tprintf("len(v.image_stack) %v != %v", + len(v.image_stack), len(n1_content.image_stack))) for i := 0; i < len(v.image_stack); i += 1 { - tc.expect(t, v.image_stack[i].name == n1_content.image_stack[i].name, - fmt.tprintf("%v: v.image_stack[%d].name %v != %v", - #procedure, i, v.image_stack[i].name, n1_content.image_stack[i].name)) - tc.expect(t, v.image_stack[i].components == n1_content.image_stack[i].components, - fmt.tprintf("%v: v.image_stack[%d].components %v != %v", - #procedure, i, v.image_stack[i].components, n1_content.image_stack[i].components)) + testing.expectf(t, v.image_stack[i].name == n1_content.image_stack[i].name, + fmt.tprintf("v.image_stack[%d].name %v != %v", + i, v.image_stack[i].name, n1_content.image_stack[i].name)) + testing.expectf(t, v.image_stack[i].components == n1_content.image_stack[i].components, + fmt.tprintf("v.image_stack[%d].components %v != %v", + i, v.image_stack[i].components, n1_content.image_stack[i].components)) switch n1_t in n1_content.image_stack[i].data { case []u8: - tc.expect(t, false, fmt.tprintf("%v: n1_content.image_stack[i].data []u8", #procedure)) + testing.expectf(t, false, fmt.tprintf("n1_content.image_stack[i].data []u8", #procedure)) case []i32le: - tc.expect(t, false, fmt.tprintf("%v: n1_content.image_stack[i].data []i32le", #procedure)) + testing.expectf(t, false, fmt.tprintf("n1_content.image_stack[i].data []i32le", #procedure)) case []f32le: l, l_ok := v.image_stack[i].data.([]f32le) - tc.expect(t, l_ok, fmt.tprintf("%v: l_ok %v != %v", #procedure, l_ok, true)) - tc.expect(t, len(l) == len(n1_t), fmt.tprintf("%v: len(l) %v != %v", #procedure, len(l), len(n1_t))) + testing.expectf(t, l_ok, fmt.tprintf("l_ok %v != %v", l_ok, true)) + testing.expectf(t, len(l) == len(n1_t), fmt.tprintf("len(l) %v != %v", len(l), len(n1_t))) for j := 0; j < len(l); j += 1 { - tc.expect(t, l[j] == n1_t[j], fmt.tprintf("%v: l[%d] %v (%h) != %v (%h)", - #procedure, j, l[j], l[j], n1_t[j], n1_t[j])) + testing.expectf(t, l[j] == n1_t[j], fmt.tprintf("l[%d] %v (%h) != %v (%h)", + j, l[j], l[j], n1_t[j], n1_t[j])) } case []f64le: l, l_ok := v.image_stack[i].data.([]f64le) - tc.expect(t, l_ok, fmt.tprintf("%v: l_ok %v != %v", #procedure, l_ok, true)) - tc.expect(t, len(l) == len(n1_t), fmt.tprintf("%v: len(l) %v != %v", #procedure, len(l), len(n1_t))) + testing.expectf(t, l_ok, fmt.tprintf("l_ok %v != %v", l_ok, true)) + testing.expectf(t, len(l) == len(n1_t), fmt.tprintf("len(l) %v != %v", len(l), len(n1_t))) for j := 0; j < len(l); j += 1 { - tc.expect(t, l[j] == n1_t[j], fmt.tprintf("%v: l[%d] %v != %v", #procedure, j, l[j], n1_t[j])) + testing.expectf(t, l[j] == n1_t[j], fmt.tprintf("l[%d] %v != %v", j, l[j], n1_t[j])) } } } -} +} \ No newline at end of file From 601df0e8f77bb9da13557e9f54abbd99b973c4f2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 14:36:36 +0200 Subject: [PATCH 073/270] Port `tests\core\encoding\json` --- core/encoding/json/marshal.odin | 4 +- core/encoding/json/parser.odin | 65 ++++++++------- core/encoding/json/types.odin | 14 ++-- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/encoding/json/test_core_json.odin | 88 +++++--------------- 6 files changed, 67 insertions(+), 108 deletions(-) diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index 4f5b50ec5..2933adf9a 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -62,8 +62,8 @@ Marshal_Options :: struct { mjson_skipped_first_braces_end: bool, } -marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator) -> (data: []byte, err: Marshal_Error) { - b := strings.builder_make(allocator) +marshal :: proc(v: any, opt: Marshal_Options = {}, allocator := context.allocator, loc := #caller_location) -> (data: []byte, err: Marshal_Error) { + b := strings.builder_make(allocator, loc) defer if err != nil { strings.builder_destroy(&b) } diff --git a/core/encoding/json/parser.odin b/core/encoding/json/parser.odin index 3973725dc..38f71edf6 100644 --- a/core/encoding/json/parser.odin +++ b/core/encoding/json/parser.odin @@ -28,27 +28,27 @@ make_parser_from_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, par } -parse :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> (Value, Error) { - return parse_string(string(data), spec, parse_integers, allocator) +parse :: proc(data: []byte, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) { + return parse_string(string(data), spec, parse_integers, allocator, loc) } -parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator) -> (Value, Error) { +parse_string :: proc(data: string, spec := DEFAULT_SPECIFICATION, parse_integers := false, allocator := context.allocator, loc := #caller_location) -> (Value, Error) { context.allocator = allocator p := make_parser_from_string(data, spec, parse_integers, allocator) switch p.spec { case .JSON: - return parse_object(&p) + return parse_object(&p, loc) case .JSON5: - return parse_value(&p) + return parse_value(&p, loc) case .SJSON: #partial switch p.curr_token.kind { case .Ident, .String: - return parse_object_body(&p, .EOF) + return parse_object_body(&p, .EOF, loc) } - return parse_value(&p) + return parse_value(&p, loc) } - return parse_object(&p) + return parse_object(&p, loc) } token_end_pos :: proc(tok: Token) -> Pos { @@ -106,7 +106,7 @@ parse_comma :: proc(p: ^Parser) -> (do_break: bool) { return false } -parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) { +parse_value :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) { err = .None token := p.curr_token #partial switch token.kind { @@ -142,13 +142,13 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) { case .String: advance_token(p) - return unquote_string(token, p.spec, p.allocator) + return unquote_string(token, p.spec, p.allocator, loc) case .Open_Brace: - return parse_object(p) + return parse_object(p, loc) case .Open_Bracket: - return parse_array(p) + return parse_array(p, loc) case: if p.spec != .JSON { @@ -176,7 +176,7 @@ parse_value :: proc(p: ^Parser) -> (value: Value, err: Error) { return } -parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) { +parse_array :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) { err = .None expect_token(p, .Open_Bracket) or_return @@ -184,14 +184,14 @@ parse_array :: proc(p: ^Parser) -> (value: Value, err: Error) { array.allocator = p.allocator defer if err != nil { for elem in array { - destroy_value(elem) + destroy_value(elem, loc=loc) } - delete(array) + delete(array, loc) } for p.curr_token.kind != .Close_Bracket { - elem := parse_value(p) or_return - append(&array, elem) + elem := parse_value(p, loc) or_return + append(&array, elem, loc) if parse_comma(p) { break @@ -228,38 +228,39 @@ clone_string :: proc(s: string, allocator: mem.Allocator, loc := #caller_locatio return } -parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator) -> (key: string, err: Error) { +parse_object_key :: proc(p: ^Parser, key_allocator: mem.Allocator, loc := #caller_location) -> (key: string, err: Error) { tok := p.curr_token if p.spec != .JSON { if allow_token(p, .Ident) { - return clone_string(tok.text, key_allocator) + return clone_string(tok.text, key_allocator, loc) } } if tok_err := expect_token(p, .String); tok_err != nil { err = .Expected_String_For_Object_Key return } - return unquote_string(tok, p.spec, key_allocator) + return unquote_string(tok, p.spec, key_allocator, loc) } -parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, err: Error) { - obj.allocator = p.allocator +parse_object_body :: proc(p: ^Parser, end_token: Token_Kind, loc := #caller_location) -> (obj: Object, err: Error) { + obj = make(Object, allocator=p.allocator, loc=loc) + defer if err != nil { for key, elem in obj { - delete(key, p.allocator) - destroy_value(elem) + delete(key, p.allocator, loc) + destroy_value(elem, loc=loc) } - delete(obj) + delete(obj, loc) } for p.curr_token.kind != end_token { - key := parse_object_key(p, p.allocator) or_return + key := parse_object_key(p, p.allocator, loc) or_return parse_colon(p) or_return - elem := parse_value(p) or_return + elem := parse_value(p, loc) or_return if key in obj { err = .Duplicate_Object_Key - delete(key, p.allocator) + delete(key, p.allocator, loc) return } @@ -267,7 +268,7 @@ parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, er // inserting empty key/values into the object and for those we do not // want to allocate anything if key != "" { - reserve_error := reserve(&obj, len(obj) + 1) + reserve_error := reserve(&obj, len(obj) + 1, loc) if reserve_error == mem.Allocator_Error.Out_Of_Memory { return nil, .Out_Of_Memory } @@ -281,9 +282,9 @@ parse_object_body :: proc(p: ^Parser, end_token: Token_Kind) -> (obj: Object, er return obj, .None } -parse_object :: proc(p: ^Parser) -> (value: Value, err: Error) { +parse_object :: proc(p: ^Parser, loc := #caller_location) -> (value: Value, err: Error) { expect_token(p, .Open_Brace) or_return - obj := parse_object_body(p, .Close_Brace) or_return + obj := parse_object_body(p, .Close_Brace, loc) or_return expect_token(p, .Close_Brace) or_return return obj, .None } @@ -480,4 +481,4 @@ unquote_string :: proc(token: Token, spec: Specification, allocator := context.a } return string(b[:w]), nil -} +} \ No newline at end of file diff --git a/core/encoding/json/types.odin b/core/encoding/json/types.odin index 73e183615..41eb21377 100644 --- a/core/encoding/json/types.odin +++ b/core/encoding/json/types.odin @@ -89,22 +89,22 @@ Error :: enum { -destroy_value :: proc(value: Value, allocator := context.allocator) { +destroy_value :: proc(value: Value, allocator := context.allocator, loc := #caller_location) { context.allocator = allocator #partial switch v in value { case Object: for key, elem in v { - delete(key) - destroy_value(elem) + delete(key, loc=loc) + destroy_value(elem, loc=loc) } - delete(v) + delete(v, loc=loc) case Array: for elem in v { - destroy_value(elem) + destroy_value(elem, loc=loc) } - delete(v) + delete(v, loc=loc) case String: - delete(v) + delete(v, loc=loc) } } diff --git a/tests/core/Makefile b/tests/core/Makefile index 46b9e352e..98027ab39 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -49,7 +49,7 @@ encoding_test: $(ODIN) test encoding/cbor $(COMMON) -out:test_cbor $(ODIN) test encoding/hex $(COMMON) -out:test_hex $(ODIN) test encoding/hxa $(COMMON) -out:test_hxa - $(ODIN) run encoding/json $(COMMON) -out:test_json + $(ODIN) test encoding/json $(COMMON) -out:test_json $(ODIN) run encoding/varint $(COMMON) -out:test_varint $(ODIN) run encoding/xml $(COMMON) -out:test_xml diff --git a/tests/core/build.bat b/tests/core/build.bat index 094a12f14..4bc5bb938 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -30,7 +30,7 @@ echo --- %PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe || exit /b %PATH_TO_ODIN% test encoding/hex %COMMON% -out:test_hex.exe || exit /b %PATH_TO_ODIN% test encoding/hxa %COMMON% -out:test_hxa.exe || exit /b -%PATH_TO_ODIN% run encoding/json %COMMON% -out:test_json.exe || exit /b +%PATH_TO_ODIN% test encoding/json %COMMON% -out:test_json.exe || exit /b %PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b %PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b diff --git a/tests/core/encoding/json/test_core_json.odin b/tests/core/encoding/json/test_core_json.odin index 813d11b2c..92c050952 100644 --- a/tests/core/encoding/json/test_core_json.odin +++ b/tests/core/encoding/json/test_core_json.odin @@ -2,46 +2,8 @@ package test_core_json import "core:encoding/json" import "core:testing" -import "core:fmt" -import "core:os" import "core:mem/virtual" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - parse_json(&t) - marshal_json(&t) - unmarshal_json(&t) - surrogate(&t) - utf8_string_of_multibyte_characters(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - @test parse_json :: proc(t: ^testing.T) { @@ -72,10 +34,9 @@ parse_json :: proc(t: ^testing.T) { } ` - _, err := json.parse(transmute([]u8)json_data) - - msg := fmt.tprintf("Expected `json.parse` to return nil, got %v", err) - expect(t, err == nil, msg) + val, err := json.parse(transmute([]u8)json_data) + json.destroy_value(val) + testing.expectf(t, err == nil, "Expected `json.parse` to return nil, got %v", err) } @test @@ -83,7 +44,7 @@ out_of_memory_in_parse_json :: proc(t: ^testing.T) { arena: virtual.Arena arena_buffer: [256]byte arena_init_error := virtual.arena_init_buffer(&arena, arena_buffer[:]) - testing.expect(t, arena_init_error == nil, fmt.tprintf("Expected arena initialization to not return error, got: %v\n", arena_init_error)) + testing.expectf(t, arena_init_error == nil, "Expected arena initialization to not return error, got: %v\n", arena_init_error) context.allocator = virtual.arena_allocator(&arena) @@ -114,11 +75,11 @@ out_of_memory_in_parse_json :: proc(t: ^testing.T) { } ` - _, err := json.parse(transmute([]u8)json_data) + val, err := json.parse(transmute([]u8)json_data) + json.destroy_value(val) expected_error := json.Error.Out_Of_Memory - msg := fmt.tprintf("Expected `json.parse` to fail with %v, got %v", expected_error, err) - expect(t, err == json.Error.Out_Of_Memory, msg) + testing.expectf(t, err == json.Error.Out_Of_Memory, "Expected `json.parse` to fail with %v, got %v", expected_error, err) } @test @@ -134,9 +95,9 @@ marshal_json :: proc(t: ^testing.T) { b = 5, } - _, err := json.marshal(my_struct) - msg := fmt.tprintf("Expected `json.marshal` to return nil, got %v", err) - expect(t, err == nil, msg) + data, err := json.marshal(my_struct) + defer delete(data) + testing.expectf(t, err == nil, "Expected `json.marshal` to return nil, got %v", err) } PRODUCTS := ` @@ -378,17 +339,12 @@ unmarshal_json :: proc(t: ^testing.T) { err := json.unmarshal(transmute([]u8)PRODUCTS, &g, json.DEFAULT_SPECIFICATION) defer cleanup(g) - msg := fmt.tprintf("Expected `json.unmarshal` to return nil, got %v", err) - expect(t, err == nil, msg) - - msg = fmt.tprintf("Expected %v products to have been unmarshaled, got %v", len(original_data.products), len(g.products)) - expect(t, len(g.products) == len(original_data.products), msg) - - msg = fmt.tprintf("Expected cash to have been unmarshaled as %v, got %v", original_data.cash, g.cash) - expect(t, original_data.cash == g.cash, msg) + testing.expectf(t, err == nil, "Expected `json.unmarshal` to return nil, got %v", err) + testing.expectf(t, len(g.products) == len(original_data.products), "Expected %v products to have been unmarshaled, got %v", len(original_data.products), len(g.products)) + testing.expectf(t, original_data.cash == g.cash, "Expected cash to have been unmarshaled as %v, got %v", original_data.cash, g.cash) for p, i in g.products { - expect(t, p == original_data.products[i], "Producted unmarshaled improperly") + testing.expect(t, p == original_data.products[i], "Producted unmarshaled improperly") } } @@ -397,17 +353,19 @@ surrogate :: proc(t: ^testing.T) { input := `+ + * 😃 - /` out, err := json.marshal(input) - expect(t, err == nil, fmt.tprintf("Expected `json.marshal(%q)` to return a nil error, got %v", input, err)) + defer delete(out) + testing.expectf(t, err == nil, "Expected `json.marshal(%q)` to return a nil error, got %v", input, err) back: string uerr := json.unmarshal(out, &back) - expect(t, uerr == nil, fmt.tprintf("Expected `json.unmarshal(%q)` to return a nil error, got %v", string(out), uerr)) - expect(t, back == input, fmt.tprintf("Expected `json.unmarshal(%q)` to return %q, got %v", string(out), input, uerr)) + defer delete(back) + testing.expectf(t, uerr == nil, "Expected `json.unmarshal(%q)` to return a nil error, got %v", string(out), uerr) + testing.expectf(t, back == input, "Expected `json.unmarshal(%q)` to return %q, got %v", string(out), input, uerr) } @test utf8_string_of_multibyte_characters :: proc(t: ^testing.T) { - _, err := json.parse_string(`"🐛✅"`) - msg := fmt.tprintf("Expected `json.parse` to return nil, got %v", err) - expect(t, err == nil, msg) -} + val, err := json.parse_string(`"🐛✅"`) + defer json.destroy_value(val) + testing.expectf(t, err == nil, "Expected `json.parse` to return nil, got %v", err) +} \ No newline at end of file From 6641a6f6c9357c8cb977622ff2da2937af69cfed Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 14:57:43 +0200 Subject: [PATCH 074/270] Port `tests\core\encoding\varint` --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- .../encoding/varint/test_core_varint.odin | 84 ++++++------------- 3 files changed, 26 insertions(+), 62 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 98027ab39..4dc12969d 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -50,7 +50,7 @@ encoding_test: $(ODIN) test encoding/hex $(COMMON) -out:test_hex $(ODIN) test encoding/hxa $(COMMON) -out:test_hxa $(ODIN) test encoding/json $(COMMON) -out:test_json - $(ODIN) run encoding/varint $(COMMON) -out:test_varint + $(ODIN) test encoding/varint $(COMMON) -out:test_varint $(ODIN) run encoding/xml $(COMMON) -out:test_xml filepath_test: diff --git a/tests/core/build.bat b/tests/core/build.bat index 4bc5bb938..983546ddb 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -31,7 +31,7 @@ echo --- %PATH_TO_ODIN% test encoding/hex %COMMON% -out:test_hex.exe || exit /b %PATH_TO_ODIN% test encoding/hxa %COMMON% -out:test_hxa.exe || exit /b %PATH_TO_ODIN% test encoding/json %COMMON% -out:test_json.exe || exit /b -%PATH_TO_ODIN% run encoding/varint %COMMON% -out:test_varint.exe || exit /b +%PATH_TO_ODIN% test encoding/varint %COMMON% -out:test_varint.exe || exit /b %PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b echo --- diff --git a/tests/core/encoding/varint/test_core_varint.odin b/tests/core/encoding/varint/test_core_varint.odin index ee1798aa7..5058f3022 100644 --- a/tests/core/encoding/varint/test_core_varint.odin +++ b/tests/core/encoding/varint/test_core_varint.odin @@ -2,110 +2,74 @@ package test_core_varint import "core:encoding/varint" import "core:testing" -import "core:fmt" -import "core:os" import "core:slice" import "core:math/rand" -TEST_count := 0 -TEST_fail := 0 - -RANDOM_TESTS :: 100 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - test_leb128(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} +NUM_RANDOM_TESTS_PER_BYTE_SIZE :: 10_000 @(test) -test_leb128 :: proc(t: ^testing.T) { +test_uleb :: proc(t: ^testing.T) { buf: [varint.LEB128_MAX_BYTES]u8 for vector in ULEB_Vectors { val, size, err := varint.decode_uleb128(vector.encoded) - msg := fmt.tprintf("Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size) - expect(t, size == vector.size && val == vector.value, msg) - - msg = fmt.tprintf("Expected decoder to return error %v, got %v for vector %v", vector.error, err, vector) - expect(t, err == vector.error, msg) + testing.expectf(t, size == vector.size && val == vector.value, "Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size) + testing.expectf(t, err == vector.error, "Expected decoder to return error %v, got %v for vector %v", vector.error, err, vector) if err == .None { // Try to roundtrip size, err = varint.encode_uleb128(buf[:], vector.value) - msg = fmt.tprintf("Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size]) - expect(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), msg) + testing.expectf(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), "Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size]) } } +} + +@(test) +test_ileb :: proc(t: ^testing.T) { + buf: [varint.LEB128_MAX_BYTES]u8 for vector in ILEB_Vectors { val, size, err := varint.decode_ileb128(vector.encoded) - msg := fmt.tprintf("Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size) - expect(t, size == vector.size && val == vector.value, msg) - - msg = fmt.tprintf("Expected decoder to return error %v, got %v", vector.error, err) - expect(t, err == vector.error, msg) + testing.expectf(t, size == vector.size && val == vector.value, "Expected %02x to decode to %v consuming %v bytes, got %v and %v", vector.encoded, vector.value, vector.size, val, size) + testing.expectf(t, err == vector.error, "Expected decoder to return error %v, got %v", vector.error, err) if err == .None { // Try to roundtrip size, err = varint.encode_ileb128(buf[:], vector.value) - msg = fmt.tprintf("Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size]) - expect(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), msg) + testing.expectf(t, size == vector.size && slice.simple_equal(vector.encoded, buf[:size]), "Expected %v to encode to %02x, got %02x", vector.value, vector.encoded, buf[:size]) } } +} + +@(test) +test_random :: proc(t: ^testing.T) { + buf: [varint.LEB128_MAX_BYTES]u8 for num_bytes in 1..=uint(16) { - for _ in 0..=RANDOM_TESTS { + for _ in 0..=NUM_RANDOM_TESTS_PER_BYTE_SIZE { unsigned, signed := get_random(num_bytes) - { encode_size, encode_err := varint.encode_uleb128(buf[:], unsigned) - msg := fmt.tprintf("%v failed to encode as an unsigned LEB128 value, got %v", unsigned, encode_err) - expect(t, encode_err == .None, msg) + testing.expectf(t, encode_err == .None, "%v failed to encode as an unsigned LEB128 value, got %v", unsigned, encode_err) decoded, decode_size, decode_err := varint.decode_uleb128(buf[:]) - msg = fmt.tprintf("Expected %02x to decode as %v, got %v", buf[:encode_size], unsigned, decoded) - expect(t, decode_err == .None && decode_size == encode_size && decoded == unsigned, msg) + testing.expectf(t, decode_err == .None && decode_size == encode_size && decoded == unsigned, "Expected %02x to decode as %v, got %v", buf[:encode_size], unsigned, decoded) } { encode_size, encode_err := varint.encode_ileb128(buf[:], signed) - msg := fmt.tprintf("%v failed to encode as a signed LEB128 value, got %v", signed, encode_err) - expect(t, encode_err == .None, msg) + testing.expectf(t, encode_err == .None, "%v failed to encode as a signed LEB128 value, got %v", signed, encode_err) decoded, decode_size, decode_err := varint.decode_ileb128(buf[:]) - msg = fmt.tprintf("Expected %02x to decode as %v, got %v, err: %v", buf[:encode_size], signed, decoded, decode_err) - expect(t, decode_err == .None && decode_size == encode_size && decoded == signed, msg) + testing.expectf(t, decode_err == .None && decode_size == encode_size && decoded == signed, "Expected %02x to decode as %v, got %v, err: %v", buf[:encode_size], signed, decoded, decode_err) } } } } +@(private) get_random :: proc(byte_count: uint) -> (u: u128, i: i128) { assert(byte_count >= 0 && byte_count <= size_of(u128)) From 9d0f4833bfde5c1ae443c4826f268b565022a817 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 15:45:15 +0200 Subject: [PATCH 075/270] Port `tests\core\encoding\xml` Made them run in parallel as well. --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/encoding/xml/test_core_xml.odin | 226 ++++++++------------- 3 files changed, 89 insertions(+), 141 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 4dc12969d..357a22edb 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -51,7 +51,7 @@ encoding_test: $(ODIN) test encoding/hxa $(COMMON) -out:test_hxa $(ODIN) test encoding/json $(COMMON) -out:test_json $(ODIN) test encoding/varint $(COMMON) -out:test_varint - $(ODIN) run encoding/xml $(COMMON) -out:test_xml + $(ODIN) test encoding/xml $(COMMON) -out:test_xml filepath_test: $(ODIN) run path/filepath $(COMMON) $(COLLECTION) -out:test_core_filepath diff --git a/tests/core/build.bat b/tests/core/build.bat index 983546ddb..d35fee5b3 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -32,7 +32,7 @@ echo --- %PATH_TO_ODIN% test encoding/hxa %COMMON% -out:test_hxa.exe || exit /b %PATH_TO_ODIN% test encoding/json %COMMON% -out:test_json.exe || exit /b %PATH_TO_ODIN% test encoding/varint %COMMON% -out:test_varint.exe || exit /b -%PATH_TO_ODIN% run encoding/xml %COMMON% -out:test_xml.exe || exit /b +%PATH_TO_ODIN% test encoding/xml %COMMON% -out:test_xml.exe || exit /b echo --- echo Running core:fmt tests diff --git a/tests/core/encoding/xml/test_core_xml.odin b/tests/core/encoding/xml/test_core_xml.odin index c62033491..22852d1f3 100644 --- a/tests/core/encoding/xml/test_core_xml.odin +++ b/tests/core/encoding/xml/test_core_xml.odin @@ -2,10 +2,10 @@ package test_core_xml import "core:encoding/xml" import "core:testing" -import "core:mem" import "core:strings" import "core:io" import "core:fmt" +import "core:log" import "core:hash" Silent :: proc(pos: xml.Pos, format: string, args: ..any) {} @@ -14,9 +14,6 @@ OPTIONS :: xml.Options{ flags = { .Ignore_Unsupported, .Intern_Comments, }, expected_doctype = "", } -TEST_count := 0 -TEST_fail := 0 - TEST :: struct { filename: string, options: xml.Options, @@ -24,22 +21,14 @@ TEST :: struct { crc32: u32, } -/* - Relative to ODIN_ROOT -*/ -TEST_FILE_PATH_PREFIX :: "tests/core/assets" +TEST_SUITE_PATH :: ODIN_ROOT + "tests/core/assets/" -TESTS :: []TEST{ - /* - First we test that certain files parse without error. - */ - - { - /* - Tests UTF-8 idents and values. - Test namespaced ident. - Tests that nested partial CDATA start doesn't trip up parser. - */ +@(test) +xml_test_utf8_normal :: proc(t: ^testing.T) { + run_test(t, { + // Tests UTF-8 idents and values. + // Test namespaced ident. + // Tests that nested partial CDATA start doesn't trip up parser. filename = "XML/utf8.xml", options = { flags = { @@ -48,13 +37,14 @@ TESTS :: []TEST{ expected_doctype = "恥ずべきフクロウ", }, crc32 = 0xe9b62f03, - }, + }) +} - { - /* - Same as above. - Unbox CDATA in data tag. - */ +@(test) +xml_test_utf8_unbox_cdata :: proc(t: ^testing.T) { + run_test(t, { + // Same as above. + // Unbox CDATA in data tag. filename = "XML/utf8.xml", options = { flags = { @@ -63,13 +53,14 @@ TESTS :: []TEST{ expected_doctype = "恥ずべきフクロウ", }, crc32 = 0x9c2643ed, - }, + }) +} - { - /* - Simple Qt TS translation file. - `core:i18n` requires it to be parsed properly. - */ +@(test) +xml_test_nl_qt_ts :: proc(t: ^testing.T) { + run_test(t, { + // Simple Qt TS translation file. + // `core:i18n` requires it to be parsed properly. filename = "I18N/nl_NL-qt-ts.ts", options = { flags = { @@ -78,13 +69,14 @@ TESTS :: []TEST{ expected_doctype = "TS", }, crc32 = 0x859b7443, - }, + }) +} - { - /* - Simple XLiff 1.2 file. - `core:i18n` requires it to be parsed properly. - */ +@(test) +xml_test_xliff_1_2 :: proc(t: ^testing.T) { + run_test(t, { + // Simple XLiff 1.2 file. + // `core:i18n` requires it to be parsed properly. filename = "I18N/nl_NL-xliff-1.2.xliff", options = { flags = { @@ -93,13 +85,14 @@ TESTS :: []TEST{ expected_doctype = "xliff", }, crc32 = 0x3deaf329, - }, + }) +} - { - /* - Simple XLiff 2.0 file. - `core:i18n` requires it to be parsed properly. - */ +@(test) +xml_test_xliff_2_0 :: proc(t: ^testing.T) { + run_test(t, { + // Simple XLiff 2.0 file. + // `core:i18n` requires it to be parsed properly. filename = "I18N/nl_NL-xliff-2.0.xliff", options = { flags = { @@ -108,9 +101,12 @@ TESTS :: []TEST{ expected_doctype = "xliff", }, crc32 = 0x0c55e287, - }, + }) +} - { +@(test) +xml_test_entities :: proc(t: ^testing.T) { + run_test(t, { filename = "XML/entities.html", options = { flags = { @@ -119,9 +115,12 @@ TESTS :: []TEST{ expected_doctype = "html", }, crc32 = 0x05373317, - }, + }) +} - { +@(test) +xml_test_entities_unbox :: proc(t: ^testing.T) { + run_test(t, { filename = "XML/entities.html", options = { flags = { @@ -130,9 +129,12 @@ TESTS :: []TEST{ expected_doctype = "html", }, crc32 = 0x3b6d4a90, - }, + }) +} - { +@(test) +xml_test_entities_unbox_decode :: proc(t: ^testing.T) { + run_test(t, { filename = "XML/entities.html", options = { flags = { @@ -141,12 +143,12 @@ TESTS :: []TEST{ expected_doctype = "html", }, crc32 = 0x5be2ffdc, - }, + }) +} - /* - Then we test that certain errors are returned as expected. - */ - { +@(test) +xml_test_invalid_doctype :: proc(t: ^testing.T) { + run_test(t, { filename = "XML/utf8.xml", options = { flags = { @@ -156,12 +158,12 @@ TESTS :: []TEST{ }, err = .Invalid_DocType, crc32 = 0x49b83d0a, - }, + }) +} - /* - Parse the 9.08 MiB unicode.xml for good measure. - */ - { +@(test) +xml_test_unicode :: proc(t: ^testing.T) { + run_test(t, { filename = "XML/unicode.xml", options = { flags = { @@ -171,39 +173,37 @@ TESTS :: []TEST{ }, err = .None, crc32 = 0x0b6100ab, - }, + }) } -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] LOG:\n\t%v\n", loc, v) +@(private) +run_test :: proc(t: ^testing.T, test: TEST) { + path := strings.concatenate({TEST_SUITE_PATH, test.filename}) + defer delete(path) + + doc, err := xml.load_from_file(path, test.options, Silent) + defer xml.destroy(doc) + + tree_string := doc_to_string(doc) + tree_bytes := transmute([]u8)tree_string + defer delete(tree_bytes) + + crc32 := hash.crc32(tree_bytes) + + failed := err != test.err + testing.expectf(t, err == test.err, "%v: Expected return value %v, got %v", test.filename, test.err, err) + + failed |= crc32 != test.crc32 + testing.expectf(t, crc32 == test.crc32, "%v: Expected CRC 0x%08x, got 0x%08x, with options %v", test.filename, test.crc32, crc32, test.options) + + if failed { + // Don't fully print big trees. + tree_string = tree_string[:min(2_048, len(tree_string))] + log.error(tree_string) } } -test_file_path :: proc(filename: string) -> (path: string) { - - path = fmt.tprintf("%v%v/%v", ODIN_ROOT, TEST_FILE_PATH_PREFIX, filename) - temp := transmute([]u8)path - - for r, i in path { - if r == '\\' { - temp[i] = '/' - } - } - return path -} - +@(private) doc_to_string :: proc(doc: ^xml.Document) -> (result: string) { /* Effectively a clone of the debug printer in the xml package. @@ -284,56 +284,4 @@ doc_to_string :: proc(doc: ^xml.Document) -> (result: string) { print(strings.to_writer(&buf), doc) return strings.clone(strings.to_string(buf)) -} - -@test -run_tests :: proc(t: ^testing.T) { - for test in TESTS { - path := test_file_path(test.filename) - log(t, fmt.tprintf("Trying to parse %v", path)) - - doc, err := xml.load_from_file(path, test.options, Silent) - defer xml.destroy(doc) - - tree_string := doc_to_string(doc) - tree_bytes := transmute([]u8)tree_string - defer delete(tree_bytes) - - crc32 := hash.crc32(tree_bytes) - - failed := err != test.err - err_msg := fmt.tprintf("Expected return value %v, got %v", test.err, err) - expect(t, err == test.err, err_msg) - - failed |= crc32 != test.crc32 - err_msg = fmt.tprintf("Expected CRC 0x%08x, got 0x%08x, with options %v", test.crc32, crc32, test.options) - expect(t, crc32 == test.crc32, err_msg) - - if failed { - /* - Don't fully print big trees. - */ - tree_string = tree_string[:min(2_048, len(tree_string))] - fmt.println(tree_string) - } - } -} - -main :: proc() { - t := testing.T{} - - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - run_tests(&t) - - if len(track.allocation_map) > 0 { - for _, v in track.allocation_map { - err_msg := fmt.tprintf("%v Leaked %v bytes.", v.location, v.size) - expect(&t, false, err_msg) - } - } - - fmt.printf("\n%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) } \ No newline at end of file From d334b8c72a6c95e22af2113a4dc71c04224af4ee Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 16:04:24 +0200 Subject: [PATCH 076/270] Port `tests\core\path\filepath` --- tests/core/Makefile | 2 +- tests/core/build.bat | 10 +-- .../path/filepath/test_core_filepath.odin | 73 +++++++++---------- 3 files changed, 41 insertions(+), 44 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 357a22edb..df3924e4d 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -54,7 +54,7 @@ encoding_test: $(ODIN) test encoding/xml $(COMMON) -out:test_xml filepath_test: - $(ODIN) run path/filepath $(COMMON) $(COLLECTION) -out:test_core_filepath + $(ODIN) test path/filepath $(COMMON) -out:test_core_filepath fmt_test: $(ODIN) run fmt $(COMMON) -out:test_core_fmt diff --git a/tests/core/build.bat b/tests/core/build.bat index d35fee5b3..214f15b45 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -34,6 +34,11 @@ echo --- %PATH_TO_ODIN% test encoding/varint %COMMON% -out:test_varint.exe || exit /b %PATH_TO_ODIN% test encoding/xml %COMMON% -out:test_xml.exe || exit /b +echo --- +echo Running core:path/filepath tests +echo --- +%PATH_TO_ODIN% test path/filepath %COMMON% -out:test_core_filepath.exe || exit /b + echo --- echo Running core:fmt tests echo --- @@ -74,11 +79,6 @@ echo Running core:odin tests echo --- %PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe || exit /b -echo --- -echo Running core:path/filepath tests -echo --- -%PATH_TO_ODIN% run path/filepath %COMMON% %COLLECTION% -out:test_core_filepath.exe || exit /b - echo --- echo Running core:reflect tests echo --- diff --git a/tests/core/path/filepath/test_core_filepath.odin b/tests/core/path/filepath/test_core_filepath.odin index 4c70e5f28..94b9329bb 100644 --- a/tests/core/path/filepath/test_core_filepath.odin +++ b/tests/core/path/filepath/test_core_filepath.odin @@ -1,26 +1,19 @@ // Tests "path.odin" in "core:path/filepath". -// Must be run with `-collection:tests=` flag, e.g. -// ./odin run tests/core/path/filepath/test_core_filepath.odin -collection:tests=tests package test_core_filepath import "core:fmt" import "core:path/filepath" import "core:testing" -import tc "tests:common" - -main :: proc() { - t := testing.T{} +@(test) +test_split_list :: proc(t: ^testing.T) { when ODIN_OS == .Windows { - test_split_list_windows(&t) + test_split_list_windows(t) } else { - test_split_list_unix(&t) + test_split_list_unix(t) } - - tc.report(&t) } -@test test_split_list_windows :: proc(t: ^testing.T) { Datum :: struct { i: int, @@ -41,12 +34,12 @@ test_split_list_windows :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i)) r := filepath.split_list(d.v) - defer delete(r) - tc.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d", + defer delete_split(r) + testing.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d", i, #procedure, d.v, len(r), len(d.e))) if len(r) == len(d.e) { for _, j in r { - tc.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v", + testing.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v", i, #procedure, d.v, r[j], j, d.e[j])) } } @@ -55,47 +48,43 @@ test_split_list_windows :: proc(t: ^testing.T) { { v := "" r := filepath.split_list(v) - tc.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r)) + defer delete_split(r) + testing.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r)) } { v := "a" r := filepath.split_list(v) - defer delete(r) - tc.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r))) + defer delete_split(r) + testing.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r))) if len(r) == 1 { - tc.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0])) + testing.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0])) } } } -@test test_split_list_unix :: proc(t: ^testing.T) { Datum :: struct { - i: int, v: string, e: [3]string, } @static data := []Datum{ - { 0, "/opt/butler:/home/fancykillerpanda/Projects/Odin/Odin:/usr/local/sbin", + { "/opt/butler:/home/fancykillerpanda/Projects/Odin/Odin:/usr/local/sbin", [3]string{"/opt/butler", "/home/fancykillerpanda/Projects/Odin/Odin", "/usr/local/sbin"} }, // Issue #1537 - { 1, "a::b", [3]string{"a", "", "b"} }, - { 2, "a:b:", [3]string{"a", "b", ""} }, - { 3, ":a:b", [3]string{"", "a", "b"} }, - { 4, "::", [3]string{"", "", ""} }, - { 5, "\"a:b\"c:d:\"f\"", [3]string{"a:bc", "d", "f"} }, - { 6, "\"a:b:c\":d\":e\":f", [3]string{"a:b:c", "d:e", "f"} }, + { "a::b", [3]string{"a", "", "b"} }, + { "a:b:", [3]string{"a", "b", ""} }, + { ":a:b", [3]string{"", "a", "b"} }, + { "::", [3]string{"", "", ""} }, + { "\"a:b\"c:d:\"f\"", [3]string{"a:bc", "d", "f"} }, + { "\"a:b:c\":d\":e\":f", [3]string{"a:b:c", "d:e", "f"} }, } - for d, i in data { - assert(i == d.i, fmt.tprintf("wrong data index: i %d != d.i %d\n", i, d.i)) + for d in data { r := filepath.split_list(d.v) - defer delete(r) - tc.expect(t, len(r) == len(d.e), fmt.tprintf("i:%d %s(%s) len(r) %d != len(d.e) %d", - i, #procedure, d.v, len(r), len(d.e))) + defer delete_split(r) + testing.expectf(t, len(r) == len(d.e), "%s len(r) %d != len(d.e) %d", d.v, len(r), len(d.e)) if len(r) == len(d.e) { for _, j in r { - tc.expect(t, r[j] == d.e[j], fmt.tprintf("i:%d %s(%v) -> %v[%d] != %v", - i, #procedure, d.v, r[j], j, d.e[j])) + testing.expectf(t, r[j] == d.e[j], "%v -> %v[%d] != %v", d.v, r[j], j, d.e[j]) } } } @@ -103,15 +92,23 @@ test_split_list_unix :: proc(t: ^testing.T) { { v := "" r := filepath.split_list(v) - tc.expect(t, r == nil, fmt.tprintf("%s(%s) -> %v != nil", #procedure, v, r)) + testing.expectf(t, r == nil, "'%s' -> '%v' != nil", v, r) } { v := "a" r := filepath.split_list(v) - defer delete(r) - tc.expect(t, len(r) == 1, fmt.tprintf("%s(%s) len(r) %d != 1", #procedure, v, len(r))) + defer delete_split(r) + testing.expectf(t, len(r) == 1, "'%s' len(r) %d != 1", v, len(r)) if len(r) == 1 { - tc.expect(t, r[0] == "a", fmt.tprintf("%s(%v) -> %v[0] != a", #procedure, v, r[0])) + testing.expectf(t, r[0] == "a", "'%v' -> %v[0] != a", v, r[0]) } } } + +@(private) +delete_split :: proc(s: []string) { + for part in s { + delete(part) + } + delete(s) +} \ No newline at end of file From 6f7c5a7577c6c03a6894728009daa7681e60a0a1 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 16:52:29 +0200 Subject: [PATCH 077/270] Port `tests\core\fmt` --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/fmt/test_core_fmt.odin | 61 ++++++++++--------------------- 3 files changed, 22 insertions(+), 43 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index df3924e4d..bc574b004 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -57,7 +57,7 @@ filepath_test: $(ODIN) test path/filepath $(COMMON) -out:test_core_filepath fmt_test: - $(ODIN) run fmt $(COMMON) -out:test_core_fmt + $(ODIN) test fmt $(COMMON) -out:test_core_fmt hash_test: $(ODIN) run hash $(COMMON) -o:speed -out:test_hash diff --git a/tests/core/build.bat b/tests/core/build.bat index 214f15b45..aa307672b 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -42,7 +42,7 @@ echo --- echo --- echo Running core:fmt tests echo --- -%PATH_TO_ODIN% run fmt %COMMON% %COLLECTION% -out:test_core_fmt.exe || exit /b +%PATH_TO_ODIN% test fmt %COMMON% -out:test_core_fmt.exe || exit /b echo --- echo Running core:hash tests diff --git a/tests/core/fmt/test_core_fmt.odin b/tests/core/fmt/test_core_fmt.odin index 82d009ac6..3e5839ae7 100644 --- a/tests/core/fmt/test_core_fmt.odin +++ b/tests/core/fmt/test_core_fmt.odin @@ -1,47 +1,8 @@ package test_core_fmt import "core:fmt" -import "core:os" -import "core:testing" import "core:mem" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_fmt_memory(&t) - test_fmt_doc_examples(&t) - test_fmt_options(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - -check :: proc(t: ^testing.T, exp: string, format: string, args: ..any, loc := #caller_location) { - got := fmt.tprintf(format, ..args) - expect(t, got == exp, fmt.tprintf("(%q, %v): %q != %q", format, args, got, exp), loc) -} +import "core:testing" @(test) test_fmt_memory :: proc(t: ^testing.T) { @@ -75,7 +36,7 @@ test_fmt_doc_examples :: proc(t: ^testing.T) { } @(test) -test_fmt_options :: proc(t: ^testing.T) { +test_fmt_escaping_prefixes :: proc(t: ^testing.T) { // Escaping check(t, "% { } 0 { } } {", "%% {{ }} {} {{ }} }} {{", 0 ) @@ -86,7 +47,10 @@ test_fmt_options :: proc(t: ^testing.T) { check(t, "+3", "%+i", 3 ) check(t, "0b11", "%#b", 3 ) check(t, "0xA", "%#X", 10 ) +} +@(test) +test_fmt_indexing :: proc(t: ^testing.T) { // Specific index formatting check(t, "1 2 3", "%i %i %i", 1, 2, 3) check(t, "1 2 3", "%[0]i %[1]i %[2]i", 1, 2, 3) @@ -95,7 +59,10 @@ test_fmt_options :: proc(t: ^testing.T) { check(t, "1 2 3", "%i %[1]i %i", 1, 2, 3) check(t, "1 3 2", "%i %[2]i %i", 1, 2, 3) check(t, "1 1 1", "%[0]i %[0]i %[0]i", 1) +} +@(test) +test_fmt_width_precision :: proc(t: ^testing.T) { // Width check(t, "3.140", "%f", 3.14) check(t, "3.140", "%4f", 3.14) @@ -133,7 +100,10 @@ test_fmt_options :: proc(t: ^testing.T) { check(t, "3.140", "%*[1].*[2][0]f", 3.14, 5, 3) check(t, "3.140", "%*[2].*[1]f", 3.14, 3, 5) check(t, "3.140", "%5.*[1]f", 3.14, 3) +} +@(test) +test_fmt_arg_errors :: proc(t: ^testing.T) { // Error checking check(t, "%!(MISSING ARGUMENT)%!(NO VERB)", "%" ) @@ -156,7 +126,10 @@ test_fmt_options :: proc(t: ^testing.T) { check(t, "%!(BAD ARGUMENT NUMBER)%!(NO VERB)%!(EXTRA 0)", "%[1]", 0) check(t, "3.1%!(EXTRA 3.14)", "%.1f", 3.14, 3.14) +} +@(test) +test_fmt_python_syntax :: proc(t: ^testing.T) { // Python-like syntax check(t, "1 2 3", "{} {} {}", 1, 2, 3) check(t, "3 2 1", "{2} {1} {0}", 1, 2, 3) @@ -181,3 +154,9 @@ test_fmt_options :: proc(t: ^testing.T) { check(t, "%!(MISSING CLOSE BRACE)%!(EXTRA 1)", "{", 1) check(t, "%!(MISSING CLOSE BRACE)%!(EXTRA 1)", "{0", 1 ) } + +@(private) +check :: proc(t: ^testing.T, exp: string, format: string, args: ..any) { + got := fmt.tprintf(format, ..args) + testing.expectf(t, got == exp, "(%q, %v): %q != %q", format, args, got, exp) +} \ No newline at end of file From 39fd73fe171c06f9eb58e107d5d5242114b43539 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 18:05:09 +0200 Subject: [PATCH 078/270] Port `testing\core\hash` --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/hash/test_core_hash.odin | 604 +++++++++++------------ tests/core/hash/test_vectors_xxhash.odin | 6 +- 4 files changed, 302 insertions(+), 312 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index bc574b004..873bd24af 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -60,7 +60,7 @@ fmt_test: $(ODIN) test fmt $(COMMON) -out:test_core_fmt hash_test: - $(ODIN) run hash $(COMMON) -o:speed -out:test_hash + $(ODIN) test hash $(COMMON) -o:speed -out:test_hash i18n_test: $(ODIN) run text/i18n $(COMMON) -out:test_core_i18n diff --git a/tests/core/build.bat b/tests/core/build.bat index aa307672b..4748c3071 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -47,7 +47,7 @@ echo --- echo --- echo Running core:hash tests echo --- -%PATH_TO_ODIN% run hash %COMMON% -o:size -out:test_core_hash.exe || exit /b +%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:test_core_hash.exe || exit /b echo --- echo Running core:image tests diff --git a/tests/core/hash/test_core_hash.odin b/tests/core/hash/test_core_hash.odin index 932d2f34c..a6294de55 100644 --- a/tests/core/hash/test_core_hash.odin +++ b/tests/core/hash/test_core_hash.odin @@ -5,47 +5,314 @@ import "core:hash" import "core:time" import "core:testing" import "core:fmt" -import "core:os" +import "core:log" import "core:math/rand" import "base:intrinsics" -TEST_count := 0 -TEST_fail := 0 +@test +test_xxhash_zero_fixed :: proc(t: ^testing.T) { + many_zeroes := make([]u8, 16 * 1024 * 1024) + defer delete(many_zeroes) -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return + // All at once. + for i, v in ZERO_VECTORS { + b := many_zeroes[:i] + + xxh32 := xxhash.XXH32(b) + xxh64 := xxhash.XXH64(b) + xxh3_64 := xxhash.XXH3_64(b) + xxh3_128 := xxhash.XXH3_128(b) + + testing.expectf(t, xxh32 == v.xxh_32, "[ XXH32(%03d) ] Expected: %08x, got: %08x", i, v.xxh_32, xxh32) + testing.expectf(t, xxh64 == v.xxh_64, "[ XXH64(%03d) ] Expected: %16x, got: %16x", i, v.xxh_64, xxh64) + testing.expectf(t, xxh3_64 == v.xxh3_64, "[XXH3_64(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64) + testing.expectf(t, xxh3_128 == v.xxh3_128, "[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128) + } +} + +@(test) +test_xxhash_zero_streamed_random_updates :: proc(t: ^testing.T) { + many_zeroes := make([]u8, 16 * 1024 * 1024) + defer delete(many_zeroes) + + // Streamed + for i, v in ZERO_VECTORS { + b := many_zeroes[:i] + + xxh_32_state, xxh_32_err := xxhash.XXH32_create_state() + defer xxhash.XXH32_destroy_state(xxh_32_state) + testing.expect(t, xxh_32_err == nil, "Problem initializing XXH_32 state") + + xxh_64_state, xxh_64_err := xxhash.XXH64_create_state() + defer xxhash.XXH64_destroy_state(xxh_64_state) + testing.expect(t, xxh_64_err == nil, "Problem initializing XXH_64 state") + + xxh3_64_state, xxh3_64_err := xxhash.XXH3_create_state() + defer xxhash.XXH3_destroy_state(xxh3_64_state) + testing.expect(t, xxh3_64_err == nil, "Problem initializing XXH3_64 state") + + xxh3_128_state, xxh3_128_err := xxhash.XXH3_create_state() + defer xxhash.XXH3_destroy_state(xxh3_128_state) + testing.expect(t, xxh3_128_err == nil, "Problem initializing XXH3_128 state") + + // XXH3_128_update + random_seed := rand.create(t.seed) + for len(b) > 0 { + update_size := min(len(b), rand.int_max(8192, &random_seed)) + if update_size > 4096 { + update_size %= 73 + } + xxhash.XXH32_update (xxh_32_state, b[:update_size]) + xxhash.XXH64_update (xxh_64_state, b[:update_size]) + + xxhash.XXH3_64_update (xxh3_64_state, b[:update_size]) + xxhash.XXH3_128_update(xxh3_128_state, b[:update_size]) + + b = b[update_size:] + } + + // Now finalize + xxh32 := xxhash.XXH32_digest(xxh_32_state) + xxh64 := xxhash.XXH64_digest(xxh_64_state) + + xxh3_64 := xxhash.XXH3_64_digest(xxh3_64_state) + xxh3_128 := xxhash.XXH3_128_digest(xxh3_128_state) + + xxh32_error := fmt.tprintf("[ XXH32(%03d) ] Expected: %08x, got: %08x", i, v.xxh_32, xxh32) + xxh64_error := fmt.tprintf("[ XXH64(%03d) ] Expected: %16x, got: %16x", i, v.xxh_64, xxh64) + xxh3_64_error := fmt.tprintf("[XXH3_64(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64) + xxh3_128_error := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128) + + testing.expect(t, xxh32 == v.xxh_32, xxh32_error) + testing.expect(t, xxh64 == v.xxh_64, xxh64_error) + testing.expect(t, xxh3_64 == v.xxh3_64, xxh3_64_error) + testing.expect(t, xxh3_128 == v.xxh3_128, xxh3_128_error) + } +} + +@test +test_xxhash_seeded :: proc(t: ^testing.T) { + buf := make([]u8, 256) + defer delete(buf) + + for seed, table in XXHASH_TEST_VECTOR_SEEDED { + for v, i in table { + b := buf[:i] + + xxh32 := xxhash.XXH32(b, u32(seed)) + xxh64 := xxhash.XXH64(b, seed) + xxh3_64 := xxhash.XXH3_64(b, seed) + xxh3_128 := xxhash.XXH3_128(b, seed) + + testing.expectf(t, xxh32 == v.xxh_32, "[ XXH32(%03d) ] Expected: %08x, got: %08x", i, v.xxh_32, xxh32) + testing.expectf(t, xxh64 == v.xxh_64, "[ XXH64(%03d) ] Expected: %16x, got: %16x", i, v.xxh_64, xxh64) + testing.expectf(t, xxh3_64 == v.xxh3_64, "[XXH3_64(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64) + testing.expectf(t, xxh3_128 == v.xxh3_128, "[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128) + + if len(b) > xxhash.XXH3_MIDSIZE_MAX { + xxh3_state, _ := xxhash.XXH3_create_state() + xxhash.XXH3_64_reset_with_seed(xxh3_state, seed) + xxhash.XXH3_64_update(xxh3_state, b) + xxh3_64_streamed := xxhash.XXH3_64_digest(xxh3_state) + xxhash.XXH3_destroy_state(xxh3_state) + testing.expectf(t, xxh3_64_streamed == v.xxh3_64, "[XXH3_64s(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64_streamed) + + xxh3_state2, _ := xxhash.XXH3_create_state() + xxhash.XXH3_128_reset_with_seed(xxh3_state2, seed) + xxhash.XXH3_128_update(xxh3_state2, b) + xxh3_128_streamed := xxhash.XXH3_128_digest(xxh3_state2) + xxhash.XXH3_destroy_state(xxh3_state2) + testing.expectf(t, xxh3_128_streamed == v.xxh3_128, "[XXH3_128s(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128_streamed) + } } } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) +} + +@test +test_xxhash_secret :: proc(t: ^testing.T) { + buf := make([]u8, 256) + defer delete(buf) + + for secret, table in XXHASH_TEST_VECTOR_SECRET { + secret_bytes := transmute([]u8)secret + for v, i in table { + b := buf[:i] + + xxh3_128 := xxhash.XXH3_128(b, secret_bytes) + testing.expectf(t, xxh3_128 == v.xxh3_128_secret, "[XXH3_128(%03d)] Expected: %32x, got: %32x", i, v.xxh3_128_secret, xxh3_128) + } } } -main :: proc() { - t := testing.T{} - test_benchmark_runner(&t) - test_crc64_vectors(&t) - test_xxhash_vectors(&t) - test_xxhash_large(&t) +@test +test_crc64_vectors :: proc(t: ^testing.T) { + vectors := map[string][4]u64 { + "123456789" = { + 0x6c40df5f0b497347, // ECMA-182, + 0x995dc9bbdf1939fa, // XZ + 0x46a5a9388a5beffe, // ISO 3306 + 0xb90956c775a41001, // ISO 3306, input and output inverted + }, + "This is a test of the emergency broadcast system." = { + 0x344fe1d09c983d13, // ECMA-182 + 0x27db187fc15bbc72, // XZ + 0x187184d744afc49e, // ISO 3306 + 0xe7fcf1006b503b61, // ISO 3306, input and output inverted + }, + } + defer delete(vectors) - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) + for vector, expected in vectors { + b := transmute([]u8)vector + ecma := hash.crc64_ecma_182(b) + xz := hash.crc64_xz(b) + iso := hash.crc64_iso_3306(b) + iso2 := hash.crc64_iso_3306_inverse(b) + + testing.expectf(t, ecma == expected[0], "[ CRC-64 ECMA ] Expected: %016x, got: %016x", expected[0], ecma) + testing.expectf(t, xz == expected[1], "[ CRC-64 XZ ] Expected: %016x, got: %016x", expected[1], xz) + testing.expectf(t, iso == expected[2], "[ CRC-64 ISO 3306] Expected: %016x, got: %016x", expected[2], iso) + testing.expectf(t, iso2 == expected[3], "[~CRC-64 ISO 3306] Expected: %016x, got: %016x", expected[3], iso2) } } -/* - Benchmarks -*/ +@(test) +test_benchmark_xxh32 :: proc(t: ^testing.T) { + name := "XXH32 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh32, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x85f6413c) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +@(test) +test_benchmark_xxh32_1MB :: proc(t: ^testing.T) { + name := "XXH32 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh32, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x9430f97f) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +@(test) +test_benchmark_xxh64 :: proc(t: ^testing.T) { + name := "XXH64 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x17bb1103c92c502f) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +@(test) +test_benchmark_xxh64_1MB :: proc(t: ^testing.T) { + name := "XXH64 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x87d2a1b6e1163ef1) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +@(test) +test_benchmark_xxh3_64 :: proc(t: ^testing.T) { + name := "XXH3_64 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh3_64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x801fedc74ccd608c) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +@(test) +test_benchmark_xxh3_64_1MB :: proc(t: ^testing.T) { + name := "XXH3_64 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh3_64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x918780b90550bf34) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +@(test) +test_benchmark_xxh3_128 :: proc(t: ^testing.T) { + name := "XXH3_128 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh3_128, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x6ba30a4e9dffe1ff801fedc74ccd608c) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +@(test) +test_benchmark_xxh3_128_1MB :: proc(t: ^testing.T) { + name := "XXH3_128 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh3_128, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0xb6ef17a3448492b6918780b90550bf34) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(name, options) +} + +// Benchmarks setup_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { assert(options != nil) @@ -113,289 +380,14 @@ benchmark_xxh3_128 :: proc(options: ^time.Benchmark_Options, allocator := contex return nil } -benchmark_print :: proc(name: string, options: ^time.Benchmark_Options) { - fmt.printf("\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", +benchmark_print :: proc(name: string, options: ^time.Benchmark_Options, loc := #caller_location) { + log.infof("\n\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s", name, options.rounds, options.processed, time.duration_nanoseconds(options.duration), options.rounds_per_second, options.megabytes_per_second, + location=loc, ) -} - -@test -test_benchmark_runner :: proc(t: ^testing.T) { - fmt.println("Starting benchmarks:") - - name := "XXH32 100 zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 100, - setup = setup_xxhash, - bench = benchmark_xxh32, - teardown = teardown_xxhash, - } - - err := time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x85f6413c, name) - benchmark_print(name, options) - - name = "XXH32 1 MiB zero bytes" - options.bytes = 1_048_576 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x9430f97f, name) - benchmark_print(name, options) - - name = "XXH64 100 zero bytes" - options.bytes = 100 - options.bench = benchmark_xxh64 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x17bb1103c92c502f, name) - benchmark_print(name, options) - - name = "XXH64 1 MiB zero bytes" - options.bytes = 1_048_576 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x87d2a1b6e1163ef1, name) - benchmark_print(name, options) - - name = "XXH3_64 100 zero bytes" - options.bytes = 100 - options.bench = benchmark_xxh3_64 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x801fedc74ccd608c, name) - benchmark_print(name, options) - - name = "XXH3_64 1 MiB zero bytes" - options.bytes = 1_048_576 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x918780b90550bf34, name) - benchmark_print(name, options) - - name = "XXH3_128 100 zero bytes" - options.bytes = 100 - options.bench = benchmark_xxh3_128 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0x6ba30a4e9dffe1ff801fedc74ccd608c, name) - benchmark_print(name, options) - - name = "XXH3_128 1 MiB zero bytes" - options.bytes = 1_048_576 - err = time.benchmark(options, context.allocator) - expect(t, err == nil, name) - expect(t, options.hash == 0xb6ef17a3448492b6918780b90550bf34, name) - benchmark_print(name, options) -} - -@test -test_xxhash_large :: proc(t: ^testing.T) { - many_zeroes := make([]u8, 16 * 1024 * 1024) - defer delete(many_zeroes) - - // All at once. - for i, v in ZERO_VECTORS { - b := many_zeroes[:i] - - fmt.printf("[test_xxhash_large] All at once. Size: %v\n", i) - - xxh32 := xxhash.XXH32(b) - xxh64 := xxhash.XXH64(b) - xxh3_64 := xxhash.XXH3_64(b) - xxh3_128 := xxhash.XXH3_128(b) - - xxh32_error := fmt.tprintf("[ XXH32(%03d) ] Expected: %08x. Got: %08x.", i, v.xxh_32, xxh32) - xxh64_error := fmt.tprintf("[ XXH64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh_64, xxh64) - xxh3_64_error := fmt.tprintf("[XXH3_64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64) - xxh3_128_error := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128) - - expect(t, xxh32 == v.xxh_32, xxh32_error) - expect(t, xxh64 == v.xxh_64, xxh64_error) - expect(t, xxh3_64 == v.xxh3_64, xxh3_64_error) - expect(t, xxh3_128 == v.xxh3_128, xxh3_128_error) - } - - when #config(RAND_STATE, -1) >= 0 && #config(RAND_INC, -1) >= 0 { - random_seed := rand.Rand{ - state = u64(#config(RAND_STATE, -1)), - inc = u64(#config(RAND_INC, -1)), - } - fmt.printf("Using user-selected seed {{%v,%v}} for update size randomness.\n", random_seed.state, random_seed.inc) - } else { - random_seed := rand.create(u64(intrinsics.read_cycle_counter())) - fmt.printf("Randonly selected seed {{%v,%v}} for update size randomness.\n", random_seed.state, random_seed.inc) - } - - // Streamed - for i, v in ZERO_VECTORS { - b := many_zeroes[:i] - - fmt.printf("[test_xxhash_large] Streamed. Size: %v\n", i) - - // bytes_per_update := []int{1, 42, 13, 7, 16, 5, 23, 74, 1024, 511, 1023, 47} - // update_size_idx: int - - xxh_32_state, xxh_32_err := xxhash.XXH32_create_state() - defer xxhash.XXH32_destroy_state(xxh_32_state) - expect(t, xxh_32_err == nil, "Problem initializing XXH_32 state.") - - xxh_64_state, xxh_64_err := xxhash.XXH64_create_state() - defer xxhash.XXH64_destroy_state(xxh_64_state) - expect(t, xxh_64_err == nil, "Problem initializing XXH_64 state.") - - xxh3_64_state, xxh3_64_err := xxhash.XXH3_create_state() - defer xxhash.XXH3_destroy_state(xxh3_64_state) - expect(t, xxh3_64_err == nil, "Problem initializing XXH3_64 state.") - - xxh3_128_state, xxh3_128_err := xxhash.XXH3_create_state() - defer xxhash.XXH3_destroy_state(xxh3_128_state) - expect(t, xxh3_128_err == nil, "Problem initializing XXH3_128 state.") - - // XXH3_128_update - - for len(b) > 0 { - update_size := min(len(b), rand.int_max(8192, &random_seed)) - if update_size > 4096 { - update_size %= 73 - } - xxhash.XXH32_update (xxh_32_state, b[:update_size]) - xxhash.XXH64_update (xxh_64_state, b[:update_size]) - - xxhash.XXH3_64_update (xxh3_64_state, b[:update_size]) - xxhash.XXH3_128_update(xxh3_128_state, b[:update_size]) - - b = b[update_size:] - } - - // Now finalize - xxh32 := xxhash.XXH32_digest(xxh_32_state) - xxh64 := xxhash.XXH64_digest(xxh_64_state) - - xxh3_64 := xxhash.XXH3_64_digest(xxh3_64_state) - xxh3_128 := xxhash.XXH3_128_digest(xxh3_128_state) - - xxh32_error := fmt.tprintf("[ XXH32(%03d) ] Expected: %08x. Got: %08x.", i, v.xxh_32, xxh32) - xxh64_error := fmt.tprintf("[ XXH64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh_64, xxh64) - xxh3_64_error := fmt.tprintf("[XXH3_64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64) - xxh3_128_error := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128) - - expect(t, xxh32 == v.xxh_32, xxh32_error) - expect(t, xxh64 == v.xxh_64, xxh64_error) - expect(t, xxh3_64 == v.xxh3_64, xxh3_64_error) - expect(t, xxh3_128 == v.xxh3_128, xxh3_128_error) - } -} - -@test -test_xxhash_vectors :: proc(t: ^testing.T) { - fmt.println("Verifying against XXHASH_TEST_VECTOR_SEEDED:") - - buf := make([]u8, 256) - defer delete(buf) - - for seed, table in XXHASH_TEST_VECTOR_SEEDED { - fmt.printf("\tSeed: %v\n", seed) - - for v, i in table { - b := buf[:i] - - xxh32 := xxhash.XXH32(b, u32(seed)) - xxh64 := xxhash.XXH64(b, seed) - xxh3_64 := xxhash.XXH3_64(b, seed) - xxh3_128 := xxhash.XXH3_128(b, seed) - - xxh32_error := fmt.tprintf("[ XXH32(%03d) ] Expected: %08x. Got: %08x.", i, v.xxh_32, xxh32) - xxh64_error := fmt.tprintf("[ XXH64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh_64, xxh64) - - xxh3_64_error := fmt.tprintf("[XXH3_64(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64) - xxh3_128_error := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128) - - expect(t, xxh32 == v.xxh_32, xxh32_error) - expect(t, xxh64 == v.xxh_64, xxh64_error) - expect(t, xxh3_64 == v.xxh3_64, xxh3_64_error) - expect(t, xxh3_128 == v.xxh3_128, xxh3_128_error) - - if len(b) > xxhash.XXH3_MIDSIZE_MAX { - fmt.printf("XXH3 - size: %v\n", len(b)) - - xxh3_state, _ := xxhash.XXH3_create_state() - xxhash.XXH3_64_reset_with_seed(xxh3_state, seed) - xxhash.XXH3_64_update(xxh3_state, b) - xxh3_64_streamed := xxhash.XXH3_64_digest(xxh3_state) - xxhash.XXH3_destroy_state(xxh3_state) - xxh3_64s_error := fmt.tprintf("[XXH3_64s(%03d) ] Expected: %16x. Got: %16x.", i, v.xxh3_64, xxh3_64_streamed) - expect(t, xxh3_64_streamed == v.xxh3_64, xxh3_64s_error) - - xxh3_state2, _ := xxhash.XXH3_create_state() - xxhash.XXH3_128_reset_with_seed(xxh3_state2, seed) - xxhash.XXH3_128_update(xxh3_state2, b) - xxh3_128_streamed := xxhash.XXH3_128_digest(xxh3_state2) - xxhash.XXH3_destroy_state(xxh3_state2) - xxh3_128s_error := fmt.tprintf("[XXH3_128s(%03d) ] Expected: %32x. Got: %32x.", i, v.xxh3_128, xxh3_128_streamed) - expect(t, xxh3_128_streamed == v.xxh3_128, xxh3_128s_error) - } - } - } - - fmt.println("Verifying against XXHASH_TEST_VECTOR_SECRET:") - for secret, table in XXHASH_TEST_VECTOR_SECRET { - fmt.printf("\tSecret:\n\t\t\"%v\"\n", secret) - - secret_bytes := transmute([]u8)secret - - for v, i in table { - b := buf[:i] - - xxh3_128 := xxhash.XXH3_128(b, secret_bytes) - xxh3_128_error := fmt.tprintf("[XXH3_128(%03d)] Expected: %32x. Got: %32x.", i, v.xxh3_128_secret, xxh3_128) - - expect(t, xxh3_128 == v.xxh3_128_secret, xxh3_128_error) - } - } -} - -@test -test_crc64_vectors :: proc(t: ^testing.T) { - fmt.println("Verifying CRC-64:") - - vectors := map[string][4]u64 { - "123456789" = { - 0x6c40df5f0b497347, // ECMA-182, - 0x995dc9bbdf1939fa, // XZ - 0x46a5a9388a5beffe, // ISO 3306 - 0xb90956c775a41001, // ISO 3306, input and output inverted - }, - "This is a test of the emergency broadcast system." = { - 0x344fe1d09c983d13, // ECMA-182 - 0x27db187fc15bbc72, // XZ - 0x187184d744afc49e, // ISO 3306 - 0xe7fcf1006b503b61, // ISO 3306, input and output inverted - }, - } - - for vector, expected in vectors { - fmt.println("\tVector:", vector) - b := transmute([]u8)vector - ecma := hash.crc64_ecma_182(b) - xz := hash.crc64_xz(b) - iso := hash.crc64_iso_3306(b) - iso2 := hash.crc64_iso_3306_inverse(b) - - ecma_error := fmt.tprintf("[ CRC-64 ECMA ] Expected: %016x. Got: %016x.", expected[0], ecma) - xz_error := fmt.tprintf("[ CRC-64 XZ ] Expected: %016x. Got: %016x.", expected[1], xz) - iso_error := fmt.tprintf("[ CRC-64 ISO 3306] Expected: %016x. Got: %016x.", expected[2], iso) - iso2_error := fmt.tprintf("[~CRC-64 ISO 3306] Expected: %016x. Got: %016x.", expected[3], iso2) - - expect(t, ecma == expected[0], ecma_error) - expect(t, xz == expected[1], xz_error) - expect(t, iso == expected[2], iso_error) - expect(t, iso2 == expected[3], iso2_error) - } } \ No newline at end of file diff --git a/tests/core/hash/test_vectors_xxhash.odin b/tests/core/hash/test_vectors_xxhash.odin index 6a37aef30..f72e2699a 100644 --- a/tests/core/hash/test_vectors_xxhash.odin +++ b/tests/core/hash/test_vectors_xxhash.odin @@ -1,6 +1,4 @@ -/* - Hash Test Vectors -*/ +// Hash Test Vectors package test_core_hash XXHASH_Test_Vectors :: struct #packed { @@ -6789,4 +6787,4 @@ XXHASH_TEST_VECTOR_SECRET := map[string][257]XXHASH_Test_Vectors_With_Secret{ /* XXH3_128_with_secret */ 0x0f9b41191242ade48bbde48dff0d38ec, }, }, -} +} \ No newline at end of file From d7bfbe05523ec4a786e57a916d32b6e849de1681 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 18:38:35 +0200 Subject: [PATCH 079/270] Port `testing\core\text\i18n` --- core/text/i18n/qt_linguist.odin | 2 - tests/core/Makefile | 6 +- tests/core/build.bat | 10 +- tests/core/text/i18n/test_core_text_i18n.odin | 169 +++++++----------- 4 files changed, 75 insertions(+), 112 deletions(-) diff --git a/core/text/i18n/qt_linguist.odin b/core/text/i18n/qt_linguist.odin index 0e75df873..bdd3f5fd7 100644 --- a/core/text/i18n/qt_linguist.odin +++ b/core/text/i18n/qt_linguist.odin @@ -162,8 +162,6 @@ parse_qt_linguist_file :: proc(filename: string, options := DEFAULT_PARSE_OPTION context.allocator = allocator data, data_ok := os.read_entire_file(filename) - defer delete(data) - if !data_ok { return {}, .File_Error } return parse_qt_linguist_from_bytes(data, options, pluralizer, allocator) diff --git a/tests/core/Makefile b/tests/core/Makefile index 873bd24af..6a01653f0 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -62,12 +62,12 @@ fmt_test: hash_test: $(ODIN) test hash $(COMMON) -o:speed -out:test_hash -i18n_test: - $(ODIN) run text/i18n $(COMMON) -out:test_core_i18n - image_test: $(ODIN) test image $(COMMON) -out:test_core_image +i18n_test: + $(ODIN) test text/i18n $(COMMON) -out:test_core_i18n + linalg_glsl_math_test: $(ODIN) run math/linalg/glsl $(COMMON) $(COLLECTION) -out:test_linalg_glsl_math diff --git a/tests/core/build.bat b/tests/core/build.bat index 4748c3071..c06a9269f 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -54,6 +54,11 @@ echo Running core:image tests echo --- %PATH_TO_ODIN% test image %COMMON% -out:test_core_image.exe || exit /b +echo --- +echo Running core:text/i18n tests +echo --- +%PATH_TO_ODIN% test text\i18n %COMMON% -out:test_core_i18n.exe || exit /b + echo --- echo Running core:math tests echo --- @@ -99,11 +104,6 @@ echo Running core:strings tests echo --- %PATH_TO_ODIN% run strings %COMMON% -out:test_core_strings.exe || exit /b -echo --- -echo Running core:text/i18n tests -echo --- -%PATH_TO_ODIN% run text\i18n %COMMON% -out:test_core_i18n.exe || exit /b - echo --- echo Running core:thread tests echo --- diff --git a/tests/core/text/i18n/test_core_text_i18n.odin b/tests/core/text/i18n/test_core_text_i18n.odin index dcbdeb0c4..f6cffc318 100644 --- a/tests/core/text/i18n/test_core_text_i18n.odin +++ b/tests/core/text/i18n/test_core_text_i18n.odin @@ -1,31 +1,9 @@ package test_core_text_i18n -import "core:mem" -import "core:fmt" -import "core:os" +import "base:runtime" import "core:testing" import "core:text/i18n" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} T :: i18n.get Test :: struct { @@ -37,25 +15,28 @@ Test :: struct { Test_Suite :: struct { file: string, - loader: proc(string, i18n.Parse_Options, proc(int) -> int, mem.Allocator) -> (^i18n.Translation, i18n.Error), + loader: proc(string, i18n.Parse_Options, proc(int) -> int, runtime.Allocator) -> (^i18n.Translation, i18n.Error), plural: proc(int) -> int, err: i18n.Error, options: i18n.Parse_Options, tests: []Test, } -// Custom pluralizer for plur.mo -plur_mo_pluralizer :: proc(n: int) -> (slot: int) { - switch { - case n == 1: return 0 - case n != 0 && n % 1_000_000 == 0: return 1 - case: return 2 - } -} +TEST_SUITE_PATH :: ODIN_ROOT + "tests/core/assets/I18N/" -TESTS := []Test_Suite{ - { - file = "assets/I18N/plur.mo", +@(test) +test_custom_pluralizer :: proc(t: ^testing.T) { + // Custom pluralizer for plur.mo + plur_mo_pluralizer :: proc(n: int) -> (slot: int) { + switch { + case n == 1: return 0 + case n != 0 && n % 1_000_000 == 0: return 1 + case: return 2 + } + } + + test(t, { + file = TEST_SUITE_PATH + "plur.mo", loader = i18n.parse_mo_file, plural = plur_mo_pluralizer, tests = { @@ -66,14 +47,16 @@ TESTS := []Test_Suite{ {"", "Message1/plural", "This is message 1", 1}, {"", "Message1/plural", "This is message 1 - plural A", 1_000_000}, {"", "Message1/plural", "This is message 1 - plural B", 42}, - // This isn't in the catalog, so should ruturn the key. {"", "Come visit us on Discord!", "Come visit us on Discord!", 1}, }, - }, + }) +} - { - file = "assets/I18N/mixed_context.mo", +@(test) +test_mixed_context :: proc(t: ^testing.T) { + test(t, { + file = TEST_SUITE_PATH + "mixed_context.mo", loader = i18n.parse_mo_file, plural = nil, tests = { @@ -84,19 +67,25 @@ TESTS := []Test_Suite{ // This isn't in the catalog, so should ruturn the key. {"", "Come visit us on Discord!", "Come visit us on Discord!", 1}, }, - }, + }) +} - { - file = "assets/I18N/mixed_context.mo", +@(test) +test_mixed_context_dupe :: proc(t: ^testing.T) { + test(t, { + file = TEST_SUITE_PATH + "mixed_context.mo", loader = i18n.parse_mo_file, plural = nil, // Message1 exists twice, once within Context, which has been merged into "" err = .Duplicate_Key, options = {merge_sections = true}, - }, + }) +} - { - file = "assets/I18N/nl_NL.mo", +@(test) +test_nl_mo :: proc(t: ^testing.T) { + test(t, { + file = TEST_SUITE_PATH + "nl_NL.mo", loader = i18n.parse_mo_file, plural = nil, // Default pluralizer tests = { @@ -111,12 +100,13 @@ TESTS := []Test_Suite{ // This isn't in the catalog, so should ruturn the key. {"", "Come visit us on Discord!", "Come visit us on Discord!", 1}, }, - }, + }) +} - - // QT Linguist with default loader options. - { - file = "assets/I18N/nl_NL-qt-ts.ts", +@(test) +test_qt_linguist :: proc(t: ^testing.T) { + test(t, { + file = TEST_SUITE_PATH + "nl_NL-qt-ts.ts", loader = i18n.parse_qt_linguist_file, plural = nil, // Default pluralizer tests = { @@ -131,11 +121,13 @@ TESTS := []Test_Suite{ {"", "Come visit us on Discord!", "Come visit us on Discord!", 1}, {"Fake_Section", "Come visit us on Discord!", "Come visit us on Discord!", 1}, }, - }, + }) +} - // QT Linguist, merging sections. - { - file = "assets/I18N/nl_NL-qt-ts.ts", +@(test) +test_qt_linguist_merge_sections :: proc(t: ^testing.T) { + test(t, { + file = TEST_SUITE_PATH + "nl_NL-qt-ts.ts", loader = i18n.parse_qt_linguist_file, plural = nil, // Default pluralizer options = {merge_sections = true}, @@ -154,65 +146,38 @@ TESTS := []Test_Suite{ {"apple_count", "%d apple(s)", "%d apple(s)", 1}, {"apple_count", "%d apple(s)", "%d apple(s)", 42}, }, - }, + }) +} - // QT Linguist, merging sections. Expecting .Duplicate_Key error because same key exists in more than 1 section. - { - file = "assets/I18N/duplicate-key.ts", +@(test) +test_qt_linguist_duplicate_key_err :: proc(t: ^testing.T) { + test(t, { // QT Linguist, merging sections. Expecting .Duplicate_Key error because same key exists in more than 1 section. + file = TEST_SUITE_PATH + "duplicate-key.ts", loader = i18n.parse_qt_linguist_file, plural = nil, // Default pluralizer options = {merge_sections = true}, err = .Duplicate_Key, - }, + }) +} - // QT Linguist, not merging sections. Shouldn't return error despite same key existing in more than 1 section. - { - file = "assets/I18N/duplicate-key.ts", +@(test) +test_qt_linguist_duplicate_key :: proc(t: ^testing.T) { + test(t, { // QT Linguist, not merging sections. Shouldn't return error despite same key existing in more than 1 section. + file = TEST_SUITE_PATH + "duplicate-key.ts", loader = i18n.parse_qt_linguist_file, plural = nil, // Default pluralizer - }, + }) } -@test -tests :: proc(t: ^testing.T) { - cat: ^i18n.Translation - err: i18n.Error +test :: proc(t: ^testing.T, suite: Test_Suite, loc := #caller_location) { + cat, err := suite.loader(suite.file, suite.options, suite.plural, context.allocator) + testing.expectf(t, err == suite.err, "Expected loading %v to return %v, got %v", suite.file, suite.err, err, loc=loc) - for suite in TESTS { - cat, err = suite.loader(suite.file, suite.options, suite.plural, context.allocator) - - msg := fmt.tprintf("Expected loading %v to return %v, got %v", suite.file, suite.err, err) - expect(t, err == suite.err, msg) - - if err == .None { - for test in suite.tests { - val := T(test.section, test.key, test.n, cat) - - msg = fmt.tprintf("Expected key `%v` from section `%v`'s form for value `%v` to equal `%v`, got `%v`", test.key, test.section, test.n, test.val, val) - expect(t, val == test.val, msg) - } - } - i18n.destroy(cat) - } -} - -main :: proc() { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - t := testing.T{} - tests(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } - - if len(track.allocation_map) > 0 { - fmt.println() - for _, v in track.allocation_map { - fmt.printf("%v Leaked %v bytes.\n", v.location, v.size) + if err == .None { + for test in suite.tests { + val := T(test.section, test.key, test.n, cat) + testing.expectf(t, val == test.val, "Expected key `%v` from section `%v`'s form for value `%v` to equal `%v`, got `%v`", test.key, test.section, test.n, test.val, val, loc=loc) } } + i18n.destroy(cat) } \ No newline at end of file From b0faab29e0c4cfcde54b5ffa66f5b43c77283d3d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 19:42:44 +0200 Subject: [PATCH 080/270] Port `tests\core\math`, `math\linalg\glsl` and `math\noise` --- tests/core/Makefile | 14 +- tests/core/build.bat | 6 +- .../linalg/glsl/test_linalg_glsl_math.odin | 20 +- .../core/math/noise/test_core_math_noise.odin | 195 +++++-------- tests/core/math/test_core_math.odin | 258 +++++++----------- 5 files changed, 180 insertions(+), 313 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 6a01653f0..eb16f6645 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -68,21 +68,21 @@ image_test: i18n_test: $(ODIN) test text/i18n $(COMMON) -out:test_core_i18n +math_test: + $(ODIN) test math $(COMMON) -out:test_core_math + linalg_glsl_math_test: - $(ODIN) run math/linalg/glsl $(COMMON) $(COLLECTION) -out:test_linalg_glsl_math + $(ODIN) test math/linalg/glsl $(COMMON) -out:test_linalg_glsl_math + +noise_test: + $(ODIN) test math/noise $(COMMON) -out:test_noise match_test: $(ODIN) run text/match $(COMMON) -out:test_core_match -math_test: - $(ODIN) run math $(COMMON) $(COLLECTION) -out:test_core_math - net_test: $(ODIN) run net $(COMMON) -out:test_core_net -noise_test: - $(ODIN) run math/noise $(COMMON) -out:test_noise - os_exit_test: $(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0 diff --git a/tests/core/build.bat b/tests/core/build.bat index c06a9269f..f14579056 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -62,17 +62,17 @@ echo --- echo --- echo Running core:math tests echo --- -%PATH_TO_ODIN% run math %COMMON% %COLLECTION% -out:test_core_math.exe || exit /b +%PATH_TO_ODIN% test math %COMMON% -out:test_core_math.exe || exit /b echo --- echo Running core:math/linalg/glsl tests echo --- -%PATH_TO_ODIN% run math/linalg/glsl %COMMON% %COLLECTION% -out:test_linalg_glsl.exe || exit /b +%PATH_TO_ODIN% test math/linalg/glsl %COMMON% -out:test_linalg_glsl.exe || exit /b echo --- echo Running core:math/noise tests echo --- -%PATH_TO_ODIN% run math/noise %COMMON% -out:test_noise.exe || exit /b +%PATH_TO_ODIN% test math/noise %COMMON% -out:test_noise.exe || exit /b echo --- echo Running core:net diff --git a/tests/core/math/linalg/glsl/test_linalg_glsl_math.odin b/tests/core/math/linalg/glsl/test_linalg_glsl_math.odin index cf91b8a97..6d4571b24 100644 --- a/tests/core/math/linalg/glsl/test_linalg_glsl_math.odin +++ b/tests/core/math/linalg/glsl/test_linalg_glsl_math.odin @@ -1,24 +1,10 @@ // Tests "linalg_glsl_math.odin" in "core:math/linalg/glsl". -// Must be run with `-collection:tests=` flag, e.g. -// ./odin run tests/core/math/linalg/glsl/test_linalg_glsl_math.odin -collection:tests=./tests package test_core_math_linalg_glsl_math import glsl "core:math/linalg/glsl" -import "core:fmt" import "core:math" import "core:testing" -import tc "tests:common" - -main :: proc() { - - t := testing.T{} - - test_fract_f32(&t) - test_fract_f64(&t) - - tc.report(&t) -} @test test_fract_f32 :: proc(t: ^testing.T) { @@ -45,7 +31,7 @@ test_fract_f32 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = glsl.fract(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%v (%h)) -> %v (%h) != %v", i, #procedure, d.v, d.v, r, r, d.e)) + testing.expectf(t, r == d.e, "%v (%h) -> %v (%h) != %v", d.v, d.v, r, r, d.e) } } @@ -74,6 +60,6 @@ test_fract_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = glsl.fract(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%v (%h)) -> %v (%h) != %v", i, #procedure, d.v, d.v, r, r, d.e)) + testing.expectf(t, r == d.e, "%v (%h) -> %v (%h) != %v", d.v, d.v, r, r, d.e) } -} +} \ No newline at end of file diff --git a/tests/core/math/noise/test_core_math_noise.odin b/tests/core/math/noise/test_core_math_noise.odin index a0360e695..f835cf58c 100644 --- a/tests/core/math/noise/test_core_math_noise.odin +++ b/tests/core/math/noise/test_core_math_noise.odin @@ -2,42 +2,6 @@ package test_core_math_noise import "core:testing" import "core:math/noise" -import "core:fmt" -import "core:os" - -TEST_count := 0 -TEST_fail := 0 - -V2 :: noise.Vec2 -V3 :: noise.Vec3 -V4 :: noise.Vec4 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - noise_test(&t) - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} Test_Vector :: struct { seed: i64, @@ -51,6 +15,10 @@ Test_Vector :: struct { }, } +V2 :: noise.Vec2 +V3 :: noise.Vec3 +V4 :: noise.Vec4 + SEED_1 :: 2324223232 SEED_2 :: 932466901 SEED_3 :: 9321 @@ -59,93 +27,78 @@ COORD_1 :: V4{ 242.0, 3433.0, 920.0, 222312.0} COORD_2 :: V4{ 590.0, 9411.0, 5201.0, 942124256.0} COORD_3 :: V4{12090.0, 19411.0, 81950901.0, 4224219.0} -Noise_Tests := []Test_Vector{ - /* - `noise_2d` tests. - */ - {SEED_1, COORD_1.xy, 0.25010583, noise.noise_2d}, - {SEED_2, COORD_2.xy, -0.92513955, noise.noise_2d}, - {SEED_3, COORD_3.xy, 0.67327416, noise.noise_2d}, - - /* - `noise_2d_improve_x` tests. - */ - {SEED_1, COORD_1.xy, 0.17074019, noise.noise_2d_improve_x}, - {SEED_2, COORD_2.xy, 0.72330487, noise.noise_2d_improve_x}, - {SEED_3, COORD_3.xy, -0.032076947, noise.noise_2d_improve_x}, - - /* - `noise_3d_improve_xy` tests. - */ - {SEED_1, COORD_1.xyz, 0.14819577, noise.noise_3d_improve_xy}, - {SEED_2, COORD_2.xyz, -0.065345764, noise.noise_3d_improve_xy}, - {SEED_3, COORD_3.xyz, -0.37761918, noise.noise_3d_improve_xy}, - - /* - `noise_3d_improve_xz` tests. - */ - {SEED_1, COORD_1.xyz, -0.50075006, noise.noise_3d_improve_xz}, - {SEED_2, COORD_2.xyz, -0.36039603, noise.noise_3d_improve_xz}, - {SEED_3, COORD_3.xyz, -0.3479203, noise.noise_3d_improve_xz}, - - /* - `noise_3d_fallback` tests. - */ - {SEED_1, COORD_1.xyz, 0.6557345, noise.noise_3d_fallback}, - {SEED_2, COORD_2.xyz, 0.55452216, noise.noise_3d_fallback}, - {SEED_3, COORD_3.xyz, -0.26408964, noise.noise_3d_fallback}, - - /* - `noise_3d_fallback` tests. - */ - {SEED_1, COORD_1.xyz, 0.6557345, noise.noise_3d_fallback}, - {SEED_2, COORD_2.xyz, 0.55452216, noise.noise_3d_fallback}, - {SEED_3, COORD_3.xyz, -0.26408964, noise.noise_3d_fallback}, - - /* - `noise_4d_improve_xyz_improve_xy` tests. - */ - {SEED_1, COORD_1, 0.44929826, noise.noise_4d_improve_xyz_improve_xy}, - {SEED_2, COORD_2, -0.13270882, noise.noise_4d_improve_xyz_improve_xy}, - {SEED_3, COORD_3, 0.10298563, noise.noise_4d_improve_xyz_improve_xy}, - - /* - `noise_4d_improve_xyz_improve_xz` tests. - */ - {SEED_1, COORD_1, -0.078514606, noise.noise_4d_improve_xyz_improve_xz}, - {SEED_2, COORD_2, -0.032157656, noise.noise_4d_improve_xyz_improve_xz}, - {SEED_3, COORD_3, -0.38607058, noise.noise_4d_improve_xyz_improve_xz}, - - /* - `noise_4d_improve_xyz` tests. - */ - {SEED_1, COORD_1, -0.4442258, noise.noise_4d_improve_xyz}, - {SEED_2, COORD_2, 0.36822623, noise.noise_4d_improve_xyz}, - {SEED_3, COORD_3, 0.22628775, noise.noise_4d_improve_xyz}, - - /* - `noise_4d_fallback` tests. - */ - {SEED_1, COORD_1, -0.14233987, noise.noise_4d_fallback}, - {SEED_2, COORD_2, 0.1354035, noise.noise_4d_fallback}, - {SEED_3, COORD_3, 0.14565045, noise.noise_4d_fallback}, - +@(test) +test_noise_2d :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1.xy, 0.25010583, noise.noise_2d}) + test(t, {SEED_2, COORD_2.xy, -0.92513955, noise.noise_2d}) + test(t, {SEED_3, COORD_3.xy, 0.67327416, noise.noise_2d}) } -noise_test :: proc(t: ^testing.T) { - for test in Noise_Tests { - output: f32 +@(test) +test_noise_2d_improve_x :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1.xy, 0.17074019, noise.noise_2d_improve_x}) + test(t, {SEED_2, COORD_2.xy, 0.72330487, noise.noise_2d_improve_x}) + test(t, {SEED_3, COORD_3.xy, -0.032076947, noise.noise_2d_improve_x}) +} - switch coord in test.coord { - case V2: - output = test.test_proc.(proc(_: i64, _: V2) -> f32)(test.seed, test.coord.(V2)) - case V3: - output = test.test_proc.(proc(_: i64, _: V3) -> f32)(test.seed, test.coord.(V3)) - case V4: - output = test.test_proc.(proc(_: i64, _: V4) -> f32)(test.seed, test.coord.(V4)) - } - - error := fmt.tprintf("Seed %v, Coord: %v, Expected: %3.8f. Got %3.8f", test.seed, test.coord, test.expected, output) - expect(t, test.expected == output, error) +@(test) +test_noise_3d_improve_xy :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1.xyz, 0.14819577, noise.noise_3d_improve_xy}) + test(t, {SEED_2, COORD_2.xyz, -0.065345764, noise.noise_3d_improve_xy}) + test(t, {SEED_3, COORD_3.xyz, -0.37761918, noise.noise_3d_improve_xy}) +} + +@(test) +test_noise_3d_improve_xz :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1.xyz, -0.50075006, noise.noise_3d_improve_xz}) + test(t, {SEED_2, COORD_2.xyz, -0.36039603, noise.noise_3d_improve_xz}) + test(t, {SEED_3, COORD_3.xyz, -0.3479203, noise.noise_3d_improve_xz}) +} + +@(test) +test_noise_3d_fallback :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1.xyz, 0.6557345, noise.noise_3d_fallback}) + test(t, {SEED_2, COORD_2.xyz, 0.55452216, noise.noise_3d_fallback}) + test(t, {SEED_3, COORD_3.xyz, -0.26408964, noise.noise_3d_fallback}) +} + +@(test) +test_noise_4d_improve_xyz_improve_xy :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1, 0.44929826, noise.noise_4d_improve_xyz_improve_xy}) + test(t, {SEED_2, COORD_2, -0.13270882, noise.noise_4d_improve_xyz_improve_xy}) + test(t, {SEED_3, COORD_3, 0.10298563, noise.noise_4d_improve_xyz_improve_xy}) +} + +@(test) +test_noise_4d_improve_xyz_improve_xz :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1, -0.078514606, noise.noise_4d_improve_xyz_improve_xz}) + test(t, {SEED_2, COORD_2, -0.032157656, noise.noise_4d_improve_xyz_improve_xz}) + test(t, {SEED_3, COORD_3, -0.38607058, noise.noise_4d_improve_xyz_improve_xz}) +} + +@(test) +test_noise_4d_improve_xyz :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1, -0.4442258, noise.noise_4d_improve_xyz}) + test(t, {SEED_2, COORD_2, 0.36822623, noise.noise_4d_improve_xyz}) + test(t, {SEED_3, COORD_3, 0.22628775, noise.noise_4d_improve_xyz}) +} + +@(test) +test_noise_4d_fallback :: proc(t: ^testing.T) { + test(t, {SEED_1, COORD_1, -0.14233987, noise.noise_4d_fallback}) + test(t, {SEED_2, COORD_2, 0.1354035, noise.noise_4d_fallback}) + test(t, {SEED_3, COORD_3, 0.14565045, noise.noise_4d_fallback}) +} + +test :: proc(t: ^testing.T, test: Test_Vector) { + output: f32 + switch coord in test.coord { + case V2: + output = test.test_proc.(proc(_: i64, _: V2) -> f32)(test.seed, test.coord.(V2)) + case V3: + output = test.test_proc.(proc(_: i64, _: V3) -> f32)(test.seed, test.coord.(V3)) + case V4: + output = test.test_proc.(proc(_: i64, _: V4) -> f32)(test.seed, test.coord.(V4)) } + testing.expectf(t, test.expected == output, "Seed %v, Coord: %v, Expected: %3.8f. Got %3.8f", test.seed, test.coord, test.expected, output) } \ No newline at end of file diff --git a/tests/core/math/test_core_math.odin b/tests/core/math/test_core_math.odin index df989bff6..2a752e366 100644 --- a/tests/core/math/test_core_math.odin +++ b/tests/core/math/test_core_math.odin @@ -1,49 +1,8 @@ // Tests "math.odin" in "core:math". -// Must be run with `-collection:tests=` flag, e.g. -// ./odin run tests/core/math/test_core_math.odin -collection:tests=./tests package test_core_math -import "core:fmt" import "core:math" import "core:testing" -import tc "tests:common" - -main :: proc() { - t := testing.T{} - - test_classify_f16(&t) - test_classify_f32(&t) - test_classify_f64(&t) - - test_trunc_f16(&t) - test_trunc_f32(&t) - test_trunc_f64(&t) - - test_round_f16(&t) - test_round_f32(&t) - test_round_f64(&t) - - test_nan(&t) - test_acos(&t) - test_acosh(&t) - test_asin(&t) - test_asinh(&t) - test_atan(&t) - test_atanh(&t) - test_atan2(&t) - test_cos(&t) - test_cosh(&t) - test_sin(&t) - test_sinh(&t) - test_sqrt(&t) - test_tan(&t) - test_tanh(&t) - test_large_cos(&t) - test_large_sin(&t) - test_large_tan(&t) - - tc.report(&t) -} @test test_classify_f16 :: proc(t: ^testing.T) { @@ -68,7 +27,7 @@ test_classify_f16 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.classify_f16(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %v != %v", d.v, r, d.e) } /* Check all subnormals (exponent 0, 10-bit significand non-zero) */ @@ -76,7 +35,7 @@ test_classify_f16 :: proc(t: ^testing.T) { v := transmute(f16)i r = math.classify_f16(v) e :: math.Float_Class.Subnormal - tc.expect(t, r == e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, v, r, e)) + testing.expectf(t, r == e, "%h -> %v != %v", v, r, e) } } @@ -103,7 +62,7 @@ test_classify_f32 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.classify_f32(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %v != %v", d.v, r, d.e) } } @@ -130,7 +89,7 @@ test_classify_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.classify_f64(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %v != %v", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %v != %v", d.v, r, d.e) } } @@ -175,16 +134,16 @@ test_trunc_f16 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.trunc_f16(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e) } v = math.SNAN_F16 r = math.trunc_f16(v) - tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f16(r), "%f != NaN", v, r) v = math.QNAN_F16 r = math.trunc_f16(v) - tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f16(r), "%f != NaN", v, r) } @test @@ -237,16 +196,16 @@ test_trunc_f32 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.trunc_f32(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e) } v = math.SNAN_F32 r = math.trunc_f32(v) - tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r) v = math.QNAN_F32 r = math.trunc_f32(v) - tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r) } @test @@ -299,16 +258,16 @@ test_trunc_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.trunc_f64(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e) } v = math.SNAN_F64 r = math.trunc_f64(v) - tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r) v = math.QNAN_F64 r = math.trunc_f64(v) - tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r) } @test @@ -352,16 +311,16 @@ test_round_f16 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.round_f16(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e) } v = math.SNAN_F16 r = math.round_f16(v) - tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f16(r), "%f -> %f != NaN", v, r) v = math.QNAN_F16 r = math.round_f16(v) - tc.expect(t, math.is_nan_f16(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f16(r), "%f -> %f != NaN", v, r) } @test @@ -414,16 +373,16 @@ test_round_f32 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.round_f32(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", i, d.v, r, d.e) } v = math.SNAN_F32 r = math.round_f32(v) - tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r) v = math.QNAN_F32 r = math.round_f32(v) - tc.expect(t, math.is_nan_f32(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f32(r), "%f -> %f != NaN", v, r) } @test @@ -476,16 +435,16 @@ test_round_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r = math.round_f64(d.v) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(%h) -> %h != %h", i, #procedure, d.v, r, d.e)) + testing.expectf(t, r == d.e, "%h -> %h != %h", d.v, r, d.e) } v = math.SNAN_F64 r = math.round_f64(v) - tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r) v = math.QNAN_F64 r = math.round_f64(v) - tc.expect(t, math.is_nan_f64(r), fmt.tprintf("%s(%f) -> %f != NaN", #procedure, v, r)) + testing.expectf(t, math.is_nan_f64(r), "%f -> %f != NaN", v, r) } @@ -1033,17 +992,17 @@ tolerance :: proc(a, b, e: f64) -> bool { } close :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool { ok := tolerance(a, b, 1e-9) - // tc.expect(t, ok, fmt.tprintf("%.15g is not close to %.15g", a, b), loc) + testing.expectf(t, ok, "%.15g is not close to %.15g", a, b, loc=loc) return ok } veryclose :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool { ok := tolerance(a, b, 4e-14) - // tc.expect(t, ok, fmt.tprintf("%.15g is not veryclose to %.15g", a, b), loc) + testing.expectf(t, ok, "%.15g is not veryclose to %.15g", a, b, loc=loc) return ok } soclose :: proc(t: ^testing.T, a, b, e: f64, loc := #caller_location) -> bool { ok := tolerance(a, b, e) - // tc.expect(t, ok, fmt.tprintf("%.15g is not soclose to %.15g", a, b), loc) + testing.expectf(t, ok, "%.15g is not soclose to %.15g", a, b, loc=loc) return ok } alike :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool { @@ -1054,34 +1013,34 @@ alike :: proc(t: ^testing.T, a, b: f64, loc := #caller_location) -> bool { case a == b: ok = math.signbit(a) == math.signbit(b) } - // tc.expect(t, ok, fmt.tprintf("%.15g is not alike to %.15g", a, b), loc) + testing.expectf(t, ok, "%.15g is not alike to %.15g", a, b, loc=loc) return ok } @test -test_nan :: proc(t: ^testing.T) { +test_nan32 :: proc(t: ^testing.T) { + float32 := f32(NaN) + equal := float32 == float32 + testing.expectf(t, !equal, "float32(NaN) is %.15g, expected NaN", float32) +} + +@test +test_nan64 :: proc(t: ^testing.T) { float64 := NaN - if float64 == float64 { - tc.errorf(t, "NaN returns %.15g, expected NaN", float64) - } - float32 := f32(float64) - if float32 == float32 { - tc.errorf(t, "float32(NaN) is %.15g, expected NaN", float32) - } + equal := float64 == float64 + testing.expectf(t, !equal, "NaN returns %.15g, expected NaN", float64) } @test test_acos :: proc(t: ^testing.T) { for _, i in vf { a := vf[i] / 10 - if f := math.acos(a); !close(t, acos[i], f) { - tc.errorf(t, "math.acos(%.15g) = %.15g, want %.15g", a, f, acos[i]) - } + f := math.acos(a) + testing.expectf(t, close(t, acos[i], f), "math.acos(%.15g) = %.15g, want %.15g", a, f, acos[i]) } for _, i in vfacos_sc { - if f := math.acos(vfacos_sc[i]); !alike(t, acos_sc[i], f) { - tc.errorf(t, "math.acos(%.15g) = %.15g, want %.15g", vfacos_sc[i], f, acos_sc[i]) - } + f := math.acos(vfacos_sc[i]) + testing.expectf(t, alike(t, acos_sc[i], f), "math.acos(%.15g) = %.15g, want %.15g", vfacos_sc[i], f, acos_sc[i]) } } @@ -1089,14 +1048,12 @@ test_acos :: proc(t: ^testing.T) { test_acosh :: proc(t: ^testing.T) { for _, i in vf { a := 1 + abs(vf[i]) - if f := math.acosh(a); !veryclose(t, acosh[i], f) { - tc.errorf(t, "math.acosh(%.15g) = %.15g, want %.15g", a, f, acosh[i]) - } + f := math.acosh(a) + testing.expectf(t, veryclose(t, acosh[i], f), "math.acosh(%.15g) = %.15g, want %.15g", a, f, acosh[i]) } for _, i in vfacosh_sc { - if f := math.acosh(vfacosh_sc[i]); !alike(t, acosh_sc[i], f) { - tc.errorf(t, "math.acosh(%.15g) = %.15g, want %.15g", vfacosh_sc[i], f, acosh_sc[i]) - } + f := math.acosh(vfacosh_sc[i]) + testing.expectf(t, alike(t, acosh_sc[i], f), "math.acosh(%.15g) = %.15g, want %.15g", vfacosh_sc[i], f, acosh_sc[i]) } } @@ -1104,42 +1061,36 @@ test_acosh :: proc(t: ^testing.T) { test_asin :: proc(t: ^testing.T) { for _, i in vf { a := vf[i] / 10 - if f := math.asin(a); !veryclose(t, asin[i], f) { - tc.errorf(t, "math.asin(%.15g) = %.15g, want %.15g", a, f, asin[i]) - } + f := math.asin(a) + testing.expectf(t, veryclose(t, asin[i], f), "math.asin(%.15g) = %.15g, want %.15g", a, f, asin[i]) } for _, i in vfasin_sc { - if f := math.asin(vfasin_sc[i]); !alike(t, asin_sc[i], f) { - tc.errorf(t, "math.asin(%.15g) = %.15g, want %.15g", vfasin_sc[i], f, asin_sc[i]) - } + f := math.asin(vfasin_sc[i]) + testing.expectf(t, alike(t, asin_sc[i], f), "math.asin(%.15g) = %.15g, want %.15g", vfasin_sc[i], f, asin_sc[i]) } } @test test_asinh :: proc(t: ^testing.T) { for _, i in vf { - if f := math.asinh(vf[i]); !veryclose(t, asinh[i], f) { - tc.errorf(t, "math.asinh(%.15g) = %.15g, want %.15g", vf[i], f, asinh[i]) - } + f := math.asinh(vf[i]) + testing.expectf(t, veryclose(t, asinh[i], f), "math.asinh(%.15g) = %.15g, want %.15g", vf[i], f, asinh[i]) } for _, i in vfasinh_sc { - if f := math.asinh(vfasinh_sc[i]); !alike(t, asinh_sc[i], f) { - tc.errorf(t, "math.asinh(%.15g) = %.15g, want %.15g", vfasinh_sc[i], f, asinh_sc[i]) - } + f := math.asinh(vfasinh_sc[i]) + testing.expectf(t, alike(t, asinh_sc[i], f), "math.asinh(%.15g) = %.15g, want %.15g", vfasinh_sc[i], f, asinh_sc[i]) } } @test test_atan :: proc(t: ^testing.T) { for _, i in vf { - if f := math.atan(vf[i]); !veryclose(t, atan[i], f) { - tc.errorf(t, "math.atan(%.15g) = %.15g, want %.15g", vf[i], f, atan[i]) - } + f := math.atan(vf[i]) + testing.expectf(t, veryclose(t, atan[i], f), "math.atan(%.15g) = %.15g, want %.15g", vf[i], f, atan[i]) } for _, i in vfatan_sc { - if f := math.atan(vfatan_sc[i]); !alike(t, atan_sc[i], f) { - tc.errorf(t, "math.atan(%.15g) = %.15g, want %.15g", vfatan_sc[i], f, atan_sc[i]) - } + f := math.atan(vfatan_sc[i]) + testing.expectf(t, alike(t, atan_sc[i], f), "math.atan(%.15g) = %.15g, want %.15g", vfatan_sc[i], f, atan_sc[i]) } } @@ -1147,84 +1098,72 @@ test_atan :: proc(t: ^testing.T) { test_atanh :: proc(t: ^testing.T) { for _, i in vf { a := vf[i] / 10 - if f := math.atanh(a); !veryclose(t, atanh[i], f) { - tc.errorf(t, "math.atanh(%.15g) = %.15g, want %.15g", a, f, atanh[i]) - } + f := math.atanh(a) + testing.expectf(t, veryclose(t, atanh[i], f), "math.atanh(%.15g) = %.15g, want %.15g", a, f, atanh[i]) } for _, i in vfatanh_sc { - if f := math.atanh(vfatanh_sc[i]); !alike(t, atanh_sc[i], f) { - tc.errorf(t, "math.atanh(%.15g) = %.15g, want %.15g", vfatanh_sc[i], f, atanh_sc[i]) - } + f := math.atanh(vfatanh_sc[i]) + testing.expectf(t, alike(t, atanh_sc[i], f), "math.atanh(%.15g) = %.15g, want %.15g", vfatanh_sc[i], f, atanh_sc[i]) } } @test test_atan2 :: proc(t: ^testing.T) { for _, i in vf { - if f := math.atan2(10, vf[i]); !veryclose(t, atan2[i], f) { - tc.errorf(t, "math.atan2(10, %.15g) = %.15g, want %.15g", vf[i], f, atan2[i]) - } + f := math.atan2(10, vf[i]) + testing.expectf(t, veryclose(t, atan2[i], f), "math.atan2(10, %.15g) = %.15g, want %.15g", vf[i], f, atan2[i]) } for _, i in vfatan2_sc { - if f := math.atan2(vfatan2_sc[i][0], vfatan2_sc[i][1]); !alike(t, atan2_sc[i], f) { - tc.errorf(t, "math.atan2(%.15g, %.15g) = %.15g, want %.15g", vfatan2_sc[i][0], vfatan2_sc[i][1], f, atan2_sc[i]) - } + f := math.atan2(vfatan2_sc[i][0], vfatan2_sc[i][1]) + testing.expectf(t, alike(t, atan2_sc[i], f), "math.atan2(%.15g, %.15g) = %.15g, want %.15g", vfatan2_sc[i][0], vfatan2_sc[i][1], f, atan2_sc[i]) } } @test test_cos :: proc(t: ^testing.T) { for _, i in vf { - if f := math.cos(vf[i]); !veryclose(t, cos[i], f) { - tc.errorf(t, "math.cos(%.15g) = %.15g, want %.15g", vf[i], f, cos[i]) - } + f := math.cos(vf[i]) + testing.expectf(t, veryclose(t, cos[i], f), "math.cos(%.15g) = %.15g, want %.15g", vf[i], f, cos[i]) } for _, i in vfcos_sc { - if f := math.cos(vfcos_sc[i]); !alike(t, cos_sc[i], f) { - tc.errorf(t, "math.cos(%.15g) = %.15g, want %.15g", vfcos_sc[i], f, cos_sc[i]) - } + f := math.cos(vfcos_sc[i]) + testing.expectf(t, alike(t, cos_sc[i], f), "math.cos(%.15g) = %.15g, want %.15g", vfcos_sc[i], f, cos_sc[i]) } } @test test_cosh :: proc(t: ^testing.T) { for _, i in vf { - if f := math.cosh(vf[i]); !close(t, cosh[i], f) { - tc.errorf(t, "math.cosh(%.15g) = %.15g, want %.15g", vf[i], f, cosh[i]) - } + f := math.cosh(vf[i]) + testing.expectf(t, close(t, cosh[i], f), "math.cosh(%.15g) = %.15g, want %.15g", vf[i], f, cosh[i]) } for _, i in vfcosh_sc { - if f := math.cosh(vfcosh_sc[i]); !alike(t, cosh_sc[i], f) { - tc.errorf(t, "math.cosh(%.15g) = %.15g, want %.15g", vfcosh_sc[i], f, cosh_sc[i]) - } + f := math.cosh(vfcosh_sc[i]) + testing.expectf(t, alike(t, cosh_sc[i], f), "math.cosh(%.15g) = %.15g, want %.15g", vfcosh_sc[i], f, cosh_sc[i]) } } @test test_sin :: proc(t: ^testing.T) { for _, i in vf { - if f := math.sin(vf[i]); !veryclose(t, sin[i], f) { - tc.errorf(t, "math.sin(%.15g) = %.15g, want %.15g", vf[i], f, sin[i]) - } + f := math.sin(vf[i]) + testing.expectf(t, veryclose(t, sin[i], f), "math.sin(%.15g) = %.15g, want %.15g", vf[i], f, sin[i]) } for _, i in vfsin_sc { - if f := math.sin(vfsin_sc[i]); !alike(t, sin_sc[i], f) { - tc.errorf(t, "math.sin(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i]) - } + f := math.sin(vfsin_sc[i]) + testing.expectf(t, alike(t, sin_sc[i], f), "math.sin(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i]) } } @test test_sinh :: proc(t: ^testing.T) { for _, i in vf { - if f := math.sinh(vf[i]); !close(t, sinh[i], f) { - tc.errorf(t, "math.sinh(%.15g) = %.15g, want %.15g", vf[i], f, sinh[i]) - } + f := math.sinh(vf[i]) + testing.expectf(t, close(t, sinh[i], f), "math.sinh(%.15g) = %.15g, want %.15g", vf[i], f, sinh[i]) } for _, i in vfsinh_sc { - if f := math.sinh(vfsinh_sc[i]); !alike(t, sinh_sc[i], f) { - tc.errorf(t, "math.sinh(%.15g) = %.15g, want %.15g", vfsinh_sc[i], f, sinh_sc[i]) - } + f := math.sinh(vfsinh_sc[i]) + testing.expectf(t, alike(t, sinh_sc[i], f), "math.sinh(%.15g) = %.15g, want %.15g", vfsinh_sc[i], f, sinh_sc[i]) } } @@ -1232,38 +1171,33 @@ test_sinh :: proc(t: ^testing.T) { test_sqrt :: proc(t: ^testing.T) { for _, i in vf { a := abs(vf[i]) - if f := math.sqrt(a); !veryclose(t, sqrt[i], f) { - tc.errorf(t, "math.sqrt(%.15g) = %.15g, want %.15g", a, f, sqrt[i]) - } + f := math.sqrt(a) + testing.expectf(t, veryclose(t, sqrt[i], f), "math.sqrt(%.15g) = %.15g, want %.15g", a, f, sqrt[i]) } } @test test_tan :: proc(t: ^testing.T) { for _, i in vf { - if f := math.tan(vf[i]); !veryclose(t, tan[i], f) { - tc.errorf(t, "math.tan(%.15g) = %.15g, want %.15g", vf[i], f, tan[i]) - } + f := math.tan(vf[i]) + testing.expectf(t, veryclose(t, tan[i], f), "math.tan(%.15g) = %.15g, want %.15g", vf[i], f, tan[i]) } // same special cases as Sin for _, i in vfsin_sc { - if f := math.tan(vfsin_sc[i]); !alike(t, sin_sc[i], f) { - tc.errorf(t, "math.tan(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i]) - } + f := math.tan(vfsin_sc[i]) + testing.expectf(t, alike(t, sin_sc[i], f), "math.tan(%.15g) = %.15g, want %.15g", vfsin_sc[i], f, sin_sc[i]) } } @test test_tanh :: proc(t: ^testing.T) { for _, i in vf { - if f := math.tanh(vf[i]); !veryclose(t, tanh[i], f) { - tc.errorf(t, "math.tanh(%.15g) = %.15g, want %.15g", vf[i], f, tanh[i]) - } + f := math.tanh(vf[i]) + testing.expectf(t, veryclose(t, tanh[i], f), "math.tanh(%.15g) = %.15g, want %.15g", vf[i], f, tanh[i]) } for _, i in vftanh_sc { - if f := math.tanh(vftanh_sc[i]); !alike(t, tanh_sc[i], f) { - tc.errorf(t, "math.tanh(%.15g) = %.15g, want %.15g", vftanh_sc[i], f, tanh_sc[i]) - } + f := math.tanh(vftanh_sc[i]) + testing.expectf(t, alike(t, tanh_sc[i], f), "math.tanh(%.15g) = %.15g, want %.15g", vftanh_sc[i], f, tanh_sc[i]) } } @@ -1273,9 +1207,7 @@ test_large_cos :: proc(t: ^testing.T) { for _, i in vf { f1 := cosLarge[i] f2 := math.cos(vf[i] + large) - if !close(t, f1, f2) { - tc.errorf(t, "math.cos(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) - } + testing.expectf(t, close(t, f1, f2), "math.cos(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) } } @@ -1285,9 +1217,7 @@ test_large_sin :: proc(t: ^testing.T) { for _, i in vf { f1 := sinLarge[i] f2 := math.sin(vf[i] + large) - if !close(t, f1, f2) { - tc.errorf(t, "math.sin(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) - } + testing.expectf(t, close(t, f1, f2), "math.sin(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) } } @@ -1297,8 +1227,6 @@ test_large_tan :: proc(t: ^testing.T) { for _, i in vf { f1 := tanLarge[i] f2 := math.tan(vf[i] + large) - if !close(t, f1, f2) { - tc.errorf(t, "math.tan(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) - } + testing.expectf(t, close(t, f1, f2), "math.tan(%.15g) = %.15g, want %.15g", vf[i]+large, f2, f1) } } \ No newline at end of file From 8383a45b62df9a3f28df0e0967bdd2b864c90cb9 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 30 May 2024 23:25:34 +0200 Subject: [PATCH 081/270] Port `tests\core\text\match` --- tests/core/Makefile | 6 +- tests/core/build.bat | 5 + .../core/text/match/test_core_text_match.odin | 161 +++++++----------- 3 files changed, 71 insertions(+), 101 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index eb16f6645..ad8209b86 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -68,6 +68,9 @@ image_test: i18n_test: $(ODIN) test text/i18n $(COMMON) -out:test_core_i18n +match_test: + $(ODIN) test text/match $(COMMON) -out:test_core_match + math_test: $(ODIN) test math $(COMMON) -out:test_core_math @@ -77,9 +80,6 @@ linalg_glsl_math_test: noise_test: $(ODIN) test math/noise $(COMMON) -out:test_noise -match_test: - $(ODIN) run text/match $(COMMON) -out:test_core_match - net_test: $(ODIN) run net $(COMMON) -out:test_core_net diff --git a/tests/core/build.bat b/tests/core/build.bat index f14579056..f77c4adfc 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -59,6 +59,11 @@ echo Running core:text/i18n tests echo --- %PATH_TO_ODIN% test text\i18n %COMMON% -out:test_core_i18n.exe || exit /b +echo --- +echo Running text:match tests +echo --- +%PATH_TO_ODIN% test text/match %COMMON% -out:test_core_match.exe || exit /b + echo --- echo Running core:math tests echo --- diff --git a/tests/core/text/match/test_core_text_match.odin b/tests/core/text/match/test_core_text_match.odin index eadd17433..5716b06fb 100644 --- a/tests/core/text/match/test_core_text_match.odin +++ b/tests/core/text/match/test_core_text_match.odin @@ -2,31 +2,6 @@ package test_strlib import "core:text/match" import "core:testing" -import "core:fmt" -import "core:os" -import "core:io" - -TEST_count: int -TEST_fail: int - -// inline expect with custom props -failed :: proc(t: ^testing.T, ok: bool, loc := #caller_location) -> bool { - TEST_count += 1 - - if !ok { - fmt.printf(/*t.w,*/ "%v: ", loc) - t.error_count += 1 - TEST_fail += 1 - } - - return !ok -} - -expect :: testing.expect - -logf :: proc(t: ^testing.T, format: string, args: ..any) { - fmt.printf(/*t.w,*/ format, ..args) -} // find correct byte offsets @test @@ -61,18 +36,17 @@ test_find :: proc(t: ^testing.T) { { "helelo", "h.-l", 0, { 0, 3, true } }, } - for entry, i in ENTRIES { + for entry in ENTRIES { matcher := match.matcher_init(entry.s, entry.p, entry.offset) start, end, ok := match.matcher_find(&matcher) success := entry.match.ok == ok && start == entry.match.start && end == entry.match.end - if failed(t, success) { - logf(t, "Find %d failed!\n", i) - logf(t, "\tHAYSTACK %s\tPATTERN %s\n", entry.s, entry.p) - logf(t, "\tSTART: %d == %d?\n", entry.match.start, start) - logf(t, "\tEND: %d == %d?\n", entry.match.end, end) - logf(t, "\tErr: %v\tLength %d\n", matcher.err, matcher.captures_length) - } + testing.expectf( + t, + success, + "HAYSTACK %q PATTERN %q, START: %d == %d? END: %d == %d? Err: %v Length %d", + entry.s, entry.p, entry.match.start, start, entry.match.end, end, matcher.err, matcher.captures_length, + ) } } @@ -178,17 +152,17 @@ test_match :: proc(t: ^testing.T) { { "testing _this_ out", "%b_", "", false }, } - for entry, i in ENTRIES { + for entry in ENTRIES { matcher := match.matcher_init(entry.s, entry.p) result, ok := match.matcher_match(&matcher) success := entry.ok == ok && result == entry.result - if failed(t, success) { - logf(t, "Match %d failed!\n", i) - logf(t, "\tHAYSTACK %s\tPATTERN %s\n", entry.s, entry.p) - logf(t, "\tResults: WANTED %s\tGOT %s\n", entry.result, result) - logf(t, "\tErr: %v\tLength %d\n", matcher.err, matcher.captures_length) - } + testing.expectf( + t, + success, + "HAYSTACK %q PATTERN %q WANTED %q GOT %q Err: %v Length %d", + entry.s, entry.p, entry.result, result, matcher.err, matcher.captures_length, + ) } } @@ -203,19 +177,23 @@ test_captures :: proc(t: ^testing.T) { compare_captures :: proc(t: ^testing.T, test: ^Temp, haystack: string, comp: []string, loc := #caller_location) { length, err := match.find_aux(haystack, test.pattern, 0, false, &test.captures) result := len(comp) == length && err == .OK - if failed(t, result == true) { - logf(t, "Captures Compare Failed!\n") - logf(t, "\tErr: %v\n", err) - logf(t, "\tLengths: %v != %v\n", len(comp), length) - } + testing.expectf( + t, + result, + "Captures Compare Failed! Lengths: %v != %v Err: %v", + len(comp), length, err, + ) for i in 0.. %s != %s\n", comp[i], text) - } + testing.expectf( + t, + comp[i] == text, + "Capture don't equal -> %q != %q\n", + comp[i], text, + ) } } @@ -224,11 +202,12 @@ test_captures :: proc(t: ^testing.T) { length, err := match.find_aux(haystack, test.pattern, 0, false, &test.captures) result := length > 0 && err == .OK - if failed(t, result == ok) { - logf(t, "Capture match failed!\n") - logf(t, "\tErr: %v\n", err) - logf(t, "\tLength: %v\n", length) - } + testing.expectf( + t, + result == ok, + "Capture match failed! Length: %v Pattern: %q Haystack: %q Err: %v", + length, test.pattern, haystack, err, + ) } temp := Temp { pattern = "(one).+" } @@ -253,15 +232,8 @@ test_captures :: proc(t: ^testing.T) { cap2 := captures[2] text1 := haystack[cap1.byte_start:cap1.byte_end] text2 := haystack[cap2.byte_start:cap2.byte_end] - expect(t, text1 == "233", "Multi-Capture failed at 1") - expect(t, text2 == "hello", "Multi-Capture failed at 2") - } -} - -gmatch_check :: proc(t: ^testing.T, index: int, a: []string, b: string) { - if failed(t, a[index] == b) { - logf(t, "GMATCH %d failed!\n", index) - logf(t, "\t%s != %s\n", a[index], b) + testing.expect(t, text1 == "233", "Multi-Capture failed at 1") + testing.expect(t, text2 == "hello", "Multi-Capture failed at 2") } } @@ -298,9 +270,9 @@ test_gmatch :: proc(t: ^testing.T) { @test test_gsub :: proc(t: ^testing.T) { result := match.gsub("testing123testing", "%d+", " sup ", context.temp_allocator) - expect(t, result == "testing sup testing", "GSUB 0: failed") + testing.expect(t, result == "testing sup testing", "GSUB 0: failed") result = match.gsub("testing123testing", "%a+", "345", context.temp_allocator) - expect(t, result == "345123345", "GSUB 1: failed") + testing.expect(t, result == "345123345", "GSUB 1: failed") } @test @@ -313,10 +285,12 @@ test_gfind :: proc(t: ^testing.T) { index: int for word in match.gfind(s, pattern, &captures) { - if failed(t, output[index] == word) { - logf(t, "GFIND %d failed!\n", index) - logf(t, "\t%s != %s\n", output[index], word) - } + testing.expectf( + t, + output[index] == word, + "GFIND %d failed! %q != %q", + index, output[index], word, + ) index += 1 } } @@ -332,11 +306,12 @@ test_frontier :: proc(t: ^testing.T) { call :: proc(data: rawptr, word: string, haystack: string, captures: []match.Match) { temp := cast(^Temp) data - if failed(temp.t, word == temp.output[temp.index]) { - logf(temp.t, "GSUB_WITH %d failed!\n", temp.index) - logf(temp.t, "\t%s != %s\n", temp.output[temp.index], word) - } - + testing.expectf( + temp.t, + word == temp.output[temp.index], + "GSUB_WITH %d failed! %q != %q", + temp.index, temp.output[temp.index], word, + ) temp.index += 1 } @@ -369,31 +344,21 @@ test_case_insensitive :: proc(t: ^testing.T) { pattern := match.pattern_case_insensitive("test", 256, context.temp_allocator) goal := "[tT][eE][sS][tT]" - if failed(t, pattern == goal) { - logf(t, "Case Insensitive Pattern doesn't match result\n") - logf(t, "\t%s != %s\n", pattern, goal) - } + testing.expectf( + t, + pattern == goal, + "Case Insensitive Pattern doesn't match result. %q != %q", + pattern, goal, + ) } } -main :: proc() { - t: testing.T - stream := os.stream_from_handle(os.stdout) - w := io.to_writer(stream) - // t.w = w - - test_find(&t) - test_match(&t) - test_captures(&t) - test_gmatch(&t) - test_gsub(&t) - test_gfind(&t) - test_frontier(&t) - test_utf8(&t) - test_case_insensitive(&t) - - fmt.wprintf(w, "%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} +@(private) +gmatch_check :: proc(t: ^testing.T, index: int, a: []string, b: string) { + testing.expectf( + t, + a[index] == b, + "GMATCH %d failed! %q != %q", + index, a[index], b, + ) +} \ No newline at end of file From 9829a02571250a27b81e5560e5c4add4df3b3f62 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 00:12:18 +0200 Subject: [PATCH 082/270] Port `tests\core\odin` --- tests/core/Makefile | 11 +++++-- tests/core/build.bat | 2 +- tests/core/odin/test_parser.odin | 53 +++++++------------------------ tests/core/test_core_odin | Bin 0 -> 1401192 bytes 4 files changed, 22 insertions(+), 44 deletions(-) create mode 100644 tests/core/test_core_odin diff --git a/tests/core/Makefile b/tests/core/Makefile index ad8209b86..2cd7304ac 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -21,6 +21,7 @@ all_bsd: download_test_assets \ match_test \ math_test \ noise_test \ + odin_test \ os_exit_test \ reflect_test \ runtime_test \ @@ -86,8 +87,11 @@ net_test: os_exit_test: $(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0 +odin_test: + $(ODIN) test odin $(COMMON) -out:test_core_odin + reflect_test: - $(ODIN) run reflect $(COMMON) $(COLLECTION) -out:test_core_reflect + $(ODIN) test reflect $(COMMON) -out:test_core_reflect runtime_test: $(ODIN) run runtime $(COMMON) -out:test_core_runtime @@ -102,4 +106,7 @@ thread_test: $(ODIN) run thread $(COMMON) -out:test_core_thread time_test: - $(ODIN) run time $(COMMON) -out:test_core_time \ No newline at end of file + $(ODIN) run time $(COMMON) -out:test_core_time + +clean: + rm test_* \ No newline at end of file diff --git a/tests/core/build.bat b/tests/core/build.bat index f77c4adfc..418908884 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -87,7 +87,7 @@ echo --- echo --- echo Running core:odin tests echo --- -%PATH_TO_ODIN% run odin %COMMON% -o:size -out:test_core_odin.exe || exit /b +%PATH_TO_ODIN% test odin %COMMON% -o:size -out:test_core_odin.exe || exit /b echo --- echo Running core:reflect tests diff --git a/tests/core/odin/test_parser.odin b/tests/core/odin/test_parser.odin index 821b7a53c..772ae5982 100644 --- a/tests/core/odin/test_parser.odin +++ b/tests/core/odin/test_parser.odin @@ -1,58 +1,29 @@ package test_core_odin_parser -import "core:fmt" import "core:odin/ast" import "core:odin/parser" -import "core:os" +import "base:runtime" import "core:testing" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_parse_demo(&t) - test_parse_bitfield(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - - @test test_parse_demo :: proc(t: ^testing.T) { - pkg, ok := parser.parse_package_from_path("examples/demo") + context.allocator = context.temp_allocator + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + pkg, ok := parser.parse_package_from_path(ODIN_ROOT + "examples/demo") - expect(t, ok == true, "parser.parse_package_from_path failed") + testing.expect(t, ok, "parser.parse_package_from_path failed") for key, value in pkg.files { - expect(t, value.syntax_error_count == 0, fmt.tprintf("%v should contain zero errors", key)) + testing.expectf(t, value.syntax_error_count == 0, "%v should contain zero errors", key) } } @test test_parse_bitfield :: proc(t: ^testing.T) { + context.allocator = context.temp_allocator + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + file := ast.File{ fullpath = "test.odin", src = ` @@ -78,5 +49,5 @@ Foo :: bit_field uint { p := parser.default_parser() ok := parser.parse_file(&p, &file) - expect(t, ok == true, "bad parse") -} + testing.expect(t, ok, "bad parse") +} \ No newline at end of file diff --git a/tests/core/test_core_odin b/tests/core/test_core_odin new file mode 100644 index 0000000000000000000000000000000000000000..bdf316228a8bddc5f4a0998789b00dc1f182e9f4 GIT binary patch literal 1401192 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 B9g6?} literal 0 HcmV?d00001 From 80b115748f319cc4d0d556cb89e30493afd92a30 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 00:27:30 +0200 Subject: [PATCH 083/270] Port `tests\core\reflect` --- tests/core/Makefile | 2 +- tests/core/build.bat | 4 +- tests/core/reflect/test_core_reflect.odin | 87 +++++++++-------------- 3 files changed, 36 insertions(+), 57 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 2cd7304ac..fa2c25810 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -94,7 +94,7 @@ reflect_test: $(ODIN) test reflect $(COMMON) -out:test_core_reflect runtime_test: - $(ODIN) run runtime $(COMMON) -out:test_core_runtime + $(ODIN) test runtime $(COMMON) -out:test_core_runtime slice_test: $(ODIN) run slice $(COMMON) -out:test_core_slice diff --git a/tests/core/build.bat b/tests/core/build.bat index 418908884..4eca48866 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -92,12 +92,12 @@ echo --- echo --- echo Running core:reflect tests echo --- -%PATH_TO_ODIN% run reflect %COMMON% %COLLECTION% -out:test_core_reflect.exe || exit /b +%PATH_TO_ODIN% test reflect %COMMON% -out:test_core_reflect.exe || exit /b echo --- echo Running core:runtime tests echo --- -%PATH_TO_ODIN% run runtime %COMMON% %COLLECTION% -out:test_core_runtime.exe || exit /b +%PATH_TO_ODIN% test runtime %COMMON% -out:test_core_runtime.exe || exit /b echo --- echo Running core:slice tests diff --git a/tests/core/reflect/test_core_reflect.odin b/tests/core/reflect/test_core_reflect.odin index a3a66f968..7d2394688 100644 --- a/tests/core/reflect/test_core_reflect.odin +++ b/tests/core/reflect/test_core_reflect.odin @@ -1,21 +1,8 @@ // Tests "core:reflect/reflect". -// Must be run with `-collection:tests=` flag, e.g. -// ./odin run tests/core/reflect/test_core_reflect.odin -out=tests/core/test_core_reflect -collection:tests=./tests package test_core_reflect -import "core:fmt" import "core:reflect" import "core:testing" -import tc "tests:common" - -main :: proc() { - t := testing.T{} - - test_as_u64(&t) - test_as_f64(&t) - - tc.report(&t) -} @test test_as_u64 :: proc(t: ^testing.T) { @@ -31,9 +18,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i8 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i8 %v) -> %v (0x%X) != %v (0x%X)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i8 %v !valid", d.v) + testing.expectf(t, r == d.e, "i8 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e) } } { @@ -48,9 +34,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i16 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i16 %v) -> %v (0x%X) != %v (0x%X)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i16 %v !valid", d.v) + testing.expectf(t, r == d.e, "i16 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e) } } { @@ -65,9 +50,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i32 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i32 %v) -> %v (0x%X) != %v (0x%X)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i32 %v !valid", d.v) + testing.expectf(t, r == d.e, "i32 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e) } } { @@ -82,9 +66,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i64 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i64 %v) -> %v (0x%X) != %v (0x%X)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i64 %v !valid", d.v) + testing.expectf(t, r == d.e, "i64 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e) } } { @@ -102,9 +85,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i128 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i128 %v) -> %v (0x%X) != %v (0x%X)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i128 %v !valid", d.v) + testing.expectf(t, r == d.e, "i128 %v -> %v (0x%X) != %v (0x%X)", d.v, r, r, d.e, d.e) } } { @@ -118,8 +100,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f16 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f16 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "f16 %v !valid", d.v) + testing.expectf(t, r == d.e, "f16 %v -> %v != %v", d.v, r, d.e) } } { @@ -132,8 +114,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f32 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f32 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "f32 %v !valid", d.v) + testing.expectf(t, r == d.e, "f32 %v -> %v != %v", d.v, r, d.e) } } { @@ -146,8 +128,8 @@ test_as_u64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_u64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f64 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "f64 %v !valid", d.v) + testing.expectf(t, r == d.e, "f64 %v -> %v != %v", d.v, r, d.e) } } } @@ -166,8 +148,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i8 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i8 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "i8 %v !valid", d.v) + testing.expectf(t, r == d.e, "i8 %v -> %v != %v", d.v, r, d.e) } } { @@ -182,8 +164,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i16 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i16 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "i16 %v !valid", d.v) + testing.expectf(t, r == d.e, "i16 %v -> %v != %v", d.v, r, d.e) } } { @@ -198,8 +180,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i32 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i32 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "i32 %v !valid", d.v) + testing.expectf(t, r == d.e, "i32 %v -> %v != %v", d.v, r, d.e) } } { @@ -214,8 +196,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i64 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "i64 %v !valid", d.v) + testing.expectf(t, r == d.e, "i64 %v -> %v != %v", d.v, r, d.e) } } { @@ -231,9 +213,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(i128 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(i128 %v) -> %v (%H) != %v (%H)\n", - i, #procedure, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "i128 %v !valid", d.v) + testing.expectf(t, r == d.e, "i128 %v -> %v (%H) != %v (%H)", d.v, r, r, d.e, d.e) } } { @@ -247,9 +228,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f16 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f16 %v (%H)) -> %v (%H) != %v (%H)\n", - i, #procedure, d.v, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "f16 %v !valid", d.v) + testing.expectf(t, r == d.e, "f16 %v (%H) -> %v (%H) != %v (%H)", d.v, d.v, r, r, d.e, d.e) } } { @@ -262,9 +242,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f32 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f32 %v (%H)) -> %v (%H) != %v (%H)\n", - i, #procedure, d.v, d.v, r, r, d.e, d.e)) + testing.expectf(t, valid, "f32 %v !valid", d.v) + testing.expectf(t, r == d.e, "f32 %v (%H) -> %v (%H) != %v (%H)", d.v, d.v, r, r, d.e, d.e) } } { @@ -277,8 +256,8 @@ test_as_f64 :: proc(t: ^testing.T) { for d, i in data { assert(i == d.i) r, valid := reflect.as_f64(d.v) - tc.expect(t, valid, fmt.tprintf("i:%d %s(f64 %v) !valid\n", i, #procedure, d.v)) - tc.expect(t, r == d.e, fmt.tprintf("i:%d %s(f64 %v) -> %v != %v\n", i, #procedure, d.v, r, d.e)) + testing.expectf(t, valid, "f64 %v !valid", d.v) + testing.expectf(t, r == d.e, "f64 %v -> %v != %v", d.v, r, d.e) } } -} +} \ No newline at end of file From ed0384c102c97edc6c35f74c7c6c0a8fa15dad9f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 00:35:57 +0200 Subject: [PATCH 084/270] Port `tests\core\runtime` --- tests/core/runtime/test_core_runtime.odin | 43 +++-------------------- 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/tests/core/runtime/test_core_runtime.odin b/tests/core/runtime/test_core_runtime.odin index 786cf003a..008146dcf 100644 --- a/tests/core/runtime/test_core_runtime.odin +++ b/tests/core/runtime/test_core_runtime.odin @@ -1,43 +1,10 @@ package test_core_runtime -import "core:fmt" import "base:intrinsics" import "core:mem" -import "core:os" -import "core:reflect" import "base:runtime" import "core:testing" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect_value :: testing.expect_value -} else { - expect_value :: proc(t: ^testing.T, value, expected: $T, loc := #caller_location) -> bool where intrinsics.type_is_comparable(T) { - TEST_count += 1 - ok := value == expected || reflect.is_nil(value) && reflect.is_nil(expected) - if !ok { - TEST_fail += 1 - fmt.printf("[%v] expected %v, got %v\n", loc, expected, value) - } - return ok - } -} - -main :: proc() { - t := testing.T{} - - test_temp_allocator_big_alloc_and_alignment(&t) - test_temp_allocator_alignment_boundary(&t) - test_temp_allocator_returns_correct_size(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - // Tests that having space for the allocation, but not for the allocation and alignment // is handled correctly. @(test) @@ -47,7 +14,7 @@ test_temp_allocator_alignment_boundary :: proc(t: ^testing.T) { _, _ = mem.alloc(int(runtime.DEFAULT_ARENA_GROWING_MINIMUM_BLOCK_SIZE)-120) _, err := mem.alloc(112, 32) - expect_value(t, err, nil) + testing.expect(t, err == nil) } // Tests that big allocations with big alignments are handled correctly. @@ -58,7 +25,7 @@ test_temp_allocator_big_alloc_and_alignment :: proc(t: ^testing.T) { mappy: map[[8]int]int err := reserve(&mappy, 50000) - expect_value(t, err, nil) + testing.expect(t, err == nil) } @(test) @@ -67,6 +34,6 @@ test_temp_allocator_returns_correct_size :: proc(t: ^testing.T) { context.allocator = runtime.arena_allocator(&arena) bytes, err := mem.alloc_bytes(10, 16) - expect_value(t, err, nil) - expect_value(t, len(bytes), 10) -} + testing.expect(t, err == nil) + testing.expect(t, len(bytes) == 10) +} \ No newline at end of file From 9ba02e888ddf5e448eb5f2463070f39d183be052 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 00:50:58 +0200 Subject: [PATCH 085/270] Port `tests\core\slice` --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/slice/test_core_slice.odin | 136 ++++++++------------------ 3 files changed, 41 insertions(+), 99 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index fa2c25810..453b84dbe 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -97,7 +97,7 @@ runtime_test: $(ODIN) test runtime $(COMMON) -out:test_core_runtime slice_test: - $(ODIN) run slice $(COMMON) -out:test_core_slice + $(ODIN) test slice $(COMMON) -out:test_core_slice strings_test: $(ODIN) run strings $(COMMON) -out:test_core_strings diff --git a/tests/core/build.bat b/tests/core/build.bat index 4eca48866..9a21d6b79 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -102,7 +102,7 @@ echo --- echo --- echo Running core:slice tests echo --- -%PATH_TO_ODIN% run slice %COMMON% -out:test_core_slice.exe || exit /b +%PATH_TO_ODIN% test slice %COMMON% -out:test_core_slice.exe || exit /b echo --- echo Running core:strings tests diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index 06329ddda..4a464503f 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -1,56 +1,16 @@ package test_core_slice import "core:slice" -import "core:strings" import "core:testing" -import "core:fmt" -import "core:os" import "core:math/rand" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_sort_with_indices(&t) - test_binary_search(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - @test test_sort_with_indices :: proc(t: ^testing.T) { - seed := rand.uint64() - fmt.printf("Random seed: %v\n", seed) - // Test sizes are all prime. test_sizes :: []int{7, 13, 347, 1031, 10111, 100003} for test_size in test_sizes { - fmt.printf("Sorting %v random u64 values along with index.\n", test_size) - - r := rand.create(seed) + r := rand.create(t.seed) vals := make([]u64, test_size) r_idx := make([]int, test_size) // Reverse index @@ -61,7 +21,7 @@ test_sort_with_indices :: proc(t: ^testing.T) { // Set up test values for _, i in vals { - vals[i] = rand.uint64(&r) + vals[i] = rand.uint64(&r) } // Sort @@ -69,7 +29,7 @@ test_sort_with_indices :: proc(t: ^testing.T) { defer delete(f_idx) // Verify sorted test values - rand.init(&r, seed) + rand.init(&r, t.seed) for v, i in f_idx { r_idx[v] = i @@ -79,14 +39,14 @@ test_sort_with_indices :: proc(t: ^testing.T) { for v, i in vals { if i > 0 { val_pass := v >= last - expect(t, val_pass, "Expected values to have been sorted.") + testing.expect(t, val_pass, "Expected randomized test values to have been sorted") if !val_pass { break } } idx_pass := vals[r_idx[i]] == rand.uint64(&r) - expect(t, idx_pass, "Expected index to have been sorted.") + testing.expect(t, idx_pass, "Expected index to have been sorted") if !idx_pass { break } @@ -97,16 +57,11 @@ test_sort_with_indices :: proc(t: ^testing.T) { @test test_sort_by_indices :: proc(t: ^testing.T) { - seed := rand.uint64() - fmt.printf("Random seed: %v\n", seed) - // Test sizes are all prime. test_sizes :: []int{7, 13, 347, 1031, 10111, 100003} for test_size in test_sizes { - fmt.printf("Sorting %v random u64 values along with index.\n", test_size) - - r := rand.create(seed) + r := rand.create(t.seed) vals := make([]u64, test_size) r_idx := make([]int, test_size) // Reverse index @@ -117,7 +72,7 @@ test_sort_by_indices :: proc(t: ^testing.T) { // Set up test values for _, i in vals { - vals[i] = rand.uint64(&r) + vals[i] = rand.uint64(&r) } // Sort @@ -125,7 +80,7 @@ test_sort_by_indices :: proc(t: ^testing.T) { defer delete(f_idx) // Verify sorted test values - rand.init(&r, seed) + rand.init(&r, t.seed) { indices := make([]int, test_size) @@ -138,7 +93,7 @@ test_sort_by_indices :: proc(t: ^testing.T) { defer delete(sorted_indices) for v, i in sorted_indices { idx_pass := v == f_idx[i] - expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") + testing.expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") if !idx_pass { break } @@ -154,7 +109,7 @@ test_sort_by_indices :: proc(t: ^testing.T) { slice.sort_by_indices_overwrite(indices, f_idx) for v, i in indices { idx_pass := v == f_idx[i] - expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") + testing.expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") if !idx_pass { break } @@ -174,7 +129,7 @@ test_sort_by_indices :: proc(t: ^testing.T) { slice.sort_by_indices(indices, swap, f_idx) for v, i in swap { idx_pass := v == f_idx[i] - expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") + testing.expect(t, idx_pass, "Expected the sorted index to be the same as the result from sort_with_indices") if !idx_pass { break } @@ -185,61 +140,48 @@ test_sort_by_indices :: proc(t: ^testing.T) { @test test_binary_search :: proc(t: ^testing.T) { - builder := strings.Builder{} - defer strings.builder_destroy(&builder) - - test_search :: proc(t: ^testing.T, b: ^strings.Builder, s: []i32, v: i32) -> (int, bool) { - log(t, fmt.sbprintf(b, "Searching for %v in %v", v, s)) - strings.builder_reset(b) - index, found := slice.binary_search(s, v) - log(t, fmt.sbprintf(b, "index: %v, found: %v", index, found)) - strings.builder_reset(b ) - - return index, found - } - index: int found: bool s := []i32{0, 1, 1, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55} - index, found = test_search(t, &builder, s, 13) - expect(t, index == 9, "Expected index to be 9.") - expect(t, found == true, "Expected found to be true.") + index, found = slice.binary_search(s, 13) + testing.expect(t, index == 9, "Expected index to be 9") + testing.expect(t, found == true, "Expected found to be true") - index, found = test_search(t, &builder, s, 4) - expect(t, index == 7, "Expected index to be 7.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(s, 4) + testing.expect(t, index == 7, "Expected index to be 7.") + testing.expect(t, found == false, "Expected found to be false.") - index, found = test_search(t, &builder, s, 100) - expect(t, index == 13, "Expected index to be 13.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(s, 100) + testing.expect(t, index == 13, "Expected index to be 13.") + testing.expect(t, found == false, "Expected found to be false.") - index, found = test_search(t, &builder, s, 1) - expect(t, index >= 1 && index <= 4, "Expected index to be 1, 2, 3, or 4.") - expect(t, found == true, "Expected found to be true.") + index, found = slice.binary_search(s, 1) + testing.expect(t, index >= 1 && index <= 4, "Expected index to be 1, 2, 3, or 4.") + testing.expect(t, found == true, "Expected found to be true.") - index, found = test_search(t, &builder, s, -1) - expect(t, index == 0, "Expected index to be 0.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(s, -1) + testing.expect(t, index == 0, "Expected index to be 0.") + testing.expect(t, found == false, "Expected found to be false.") a := []i32{} - index, found = test_search(t, &builder, a, 13) - expect(t, index == 0, "Expected index to be 0.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(a, 13) + testing.expect(t, index == 0, "Expected index to be 0.") + testing.expect(t, found == false, "Expected found to be false.") b := []i32{1} - index, found = test_search(t, &builder, b, 13) - expect(t, index == 1, "Expected index to be 1.") - expect(t, found == false, "Expected found to be false.") + index, found = slice.binary_search(b, 13) + testing.expect(t, index == 1, "Expected index to be 1.") + testing.expect(t, found == false, "Expected found to be false.") - index, found = test_search(t, &builder, b, 1) - expect(t, index == 0, "Expected index to be 0.") - expect(t, found == true, "Expected found to be true.") + index, found = slice.binary_search(b, 1) + testing.expect(t, index == 0, "Expected index to be 0.") + testing.expect(t, found == true, "Expected found to be true.") - index, found = test_search(t, &builder, b, 0) - expect(t, index == 0, "Expected index to be 0.") - expect(t, found == false, "Expected found to be false.") -} + index, found = slice.binary_search(b, 0) + testing.expect(t, index == 0, "Expected index to be 0.") + testing.expect(t, found == false, "Expected found to be false.") +} \ No newline at end of file From a406ff70636b03a91024791c22838c1a67ce4817 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 00:57:05 +0200 Subject: [PATCH 086/270] Port `tests\core\strings` --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/strings/test_core_strings.odin | 86 +++++++---------------- 3 files changed, 26 insertions(+), 64 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 453b84dbe..34e490c69 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -100,7 +100,7 @@ slice_test: $(ODIN) test slice $(COMMON) -out:test_core_slice strings_test: - $(ODIN) run strings $(COMMON) -out:test_core_strings + $(ODIN) test strings $(COMMON) -out:test_core_strings thread_test: $(ODIN) run thread $(COMMON) -out:test_core_thread diff --git a/tests/core/build.bat b/tests/core/build.bat index 9a21d6b79..eee489dec 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -107,7 +107,7 @@ echo --- echo --- echo Running core:strings tests echo --- -%PATH_TO_ODIN% run strings %COMMON% -out:test_core_strings.exe || exit /b +%PATH_TO_ODIN% test strings %COMMON% -out:test_core_strings.exe || exit /b echo --- echo Running core:thread tests diff --git a/tests/core/strings/test_core_strings.odin b/tests/core/strings/test_core_strings.odin index f49476765..0ee2b3eb9 100644 --- a/tests/core/strings/test_core_strings.odin +++ b/tests/core/strings/test_core_strings.odin @@ -2,81 +2,42 @@ package test_core_strings import "core:strings" import "core:testing" -import "core:fmt" -import "core:os" import "base:runtime" -import "core:mem" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_index_any_small_string_not_found(&t) - test_index_any_larger_string_not_found(&t) - test_index_any_small_string_found(&t) - test_index_any_larger_string_found(&t) - test_cut(&t) - test_case_conversion(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} @test test_index_any_small_string_not_found :: proc(t: ^testing.T) { index := strings.index_any(".", "/:\"") - expect(t, index == -1, "index_any should be negative") + testing.expect(t, index == -1, "index_any should be negative") } @test test_index_any_larger_string_not_found :: proc(t: ^testing.T) { index := strings.index_any("aaaaaaaa.aaaaaaaa", "/:\"") - expect(t, index == -1, "index_any should be negative") + testing.expect(t, index == -1, "index_any should be negative") } @test test_index_any_small_string_found :: proc(t: ^testing.T) { index := strings.index_any(".", "/:.\"") - expect(t, index == 0, "index_any should be 0") + testing.expect(t, index == 0, "index_any should be 0") } @test test_index_any_larger_string_found :: proc(t: ^testing.T) { index := strings.index_any("aaaaaaaa:aaaaaaaa", "/:\"") - expect(t, index == 8, "index_any should be 8") + testing.expect(t, index == 8, "index_any should be 8") } @test test_last_index_any_small_string_found :: proc(t: ^testing.T) { index := strings.last_index_any(".", "/:.\"") - expect(t, index == 0, "last_index_any should be 0") + testing.expect(t, index == 0, "last_index_any should be 0") } @test test_last_index_any_small_string_not_found :: proc(t: ^testing.T) { index := strings.last_index_any(".", "/:\"") - expect(t, index == -1, "last_index_any should be -1") + testing.expect(t, index == -1, "last_index_any should be -1") } Cut_Test :: struct { @@ -100,9 +61,12 @@ test_cut :: proc(t: ^testing.T) { res := strings.cut(test.input, test.offset, test.length) defer delete(res) - msg := fmt.tprintf("cut(\"%v\", %v, %v) expected to return \"%v\", got \"%v\"", - test.input, test.offset, test.length, test.output, res) - expect(t, res == test.output, msg) + testing.expectf( + t, + res == test.output, + "cut(\"%v\", %v, %v) expected to return \"%v\", got \"%v\"", + test.input, test.offset, test.length, test.output, res, + ) } } @@ -118,7 +82,7 @@ Case_Kind :: enum { Ada_Case, } -Case_Proc :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) +Case_Proc :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) test_cases := [Case_Kind]struct{s: string, p: Case_Proc}{ .Lower_Space_Case = {"hellope world", to_lower_space_case}, @@ -132,33 +96,31 @@ test_cases := [Case_Kind]struct{s: string, p: Case_Proc}{ .Ada_Case = {"Hellope_World", to_ada_case}, } -to_lower_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { +to_lower_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_delimiter_case(r, ' ', false, allocator) } -to_upper_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { +to_upper_space_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_delimiter_case(r, ' ', true, allocator) } // NOTE: we have these wrappers as having #optional_allocator_error changes the type to not be equivalent -to_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_snake_case(r, allocator) } -to_upper_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_upper_snake_case(r, allocator) } -to_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_kebab_case(r, allocator) } -to_upper_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_upper_kebab_case(r, allocator) } -to_camel_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_camel_case(r, allocator) } -to_pascal_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_pascal_case(r, allocator) } -to_ada_case :: proc(r: string, allocator: runtime.Allocator) -> (string, mem.Allocator_Error) { return strings.to_ada_case(r, allocator) } +to_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_snake_case(r, allocator) } +to_upper_snake_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_upper_snake_case(r, allocator) } +to_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_kebab_case(r, allocator) } +to_upper_kebab_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_upper_kebab_case(r, allocator) } +to_camel_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_camel_case(r, allocator) } +to_pascal_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_pascal_case(r, allocator) } +to_ada_case :: proc(r: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { return strings.to_ada_case(r, allocator) } @test test_case_conversion :: proc(t: ^testing.T) { for entry in test_cases { for test_case, case_kind in test_cases { result, err := entry.p(test_case.s, context.allocator) - msg := fmt.tprintf("ERROR: We got the allocation error '{}'\n", err) - expect(t, err == nil, msg) + testing.expectf(t, err == nil, "ERROR: We got the allocation error '{}'\n", err) defer delete(result) - msg = fmt.tprintf("ERROR: Input `{}` to converter {} does not match `{}`, got `{}`.\n", test_case.s, case_kind, entry.s, result) - expect(t, result == entry.s, msg) + testing.expectf(t, result == entry.s, "ERROR: Input `{}` to converter {} does not match `{}`, got `{}`.\n", test_case.s, case_kind, entry.s, result) } } } \ No newline at end of file From 5b1ffba915286209b83c444d40f9fcb4d44c9bc8 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 01:12:35 +0200 Subject: [PATCH 087/270] Port `testing\core\time` --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/time/test_core_time.odin | 170 +++++++++++++++------------- 3 files changed, 94 insertions(+), 80 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 34e490c69..ec4cdcafd 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -106,7 +106,7 @@ thread_test: $(ODIN) run thread $(COMMON) -out:test_core_thread time_test: - $(ODIN) run time $(COMMON) -out:test_core_time + $(ODIN) test time $(COMMON) -out:test_core_time clean: rm test_* \ No newline at end of file diff --git a/tests/core/build.bat b/tests/core/build.bat index eee489dec..090369848 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -117,4 +117,4 @@ echo --- echo --- echo Running core:time tests echo --- -%PATH_TO_ODIN% run time %COMMON% %COLLECTION% -out:test_core_time.exe || exit /b \ No newline at end of file +%PATH_TO_ODIN% test time %COMMON% -out:test_core_time.exe || exit /b \ No newline at end of file diff --git a/tests/core/time/test_core_time.odin b/tests/core/time/test_core_time.odin index c6c6869a7..aeae44ca1 100644 --- a/tests/core/time/test_core_time.odin +++ b/tests/core/time/test_core_time.odin @@ -1,68 +1,17 @@ package test_core_time -import "core:fmt" -import "core:mem" -import "core:os" import "core:testing" import "core:time" import dt "core:time/datetime" is_leap_year :: time.is_leap_year -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - expect_value :: testing.expect_value - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - defer mem.tracking_allocator_destroy(&track) - context.allocator = mem.tracking_allocator(&track) - - test_ordinal_date_roundtrip(&t) - test_component_to_time_roundtrip(&t) - test_parse_rfc3339_string(&t) - test_parse_iso8601_string(&t) - - for _, leak in track.allocation_map { - expect(&t, false, fmt.tprintf("%v leaked %m\n", leak.location, leak.size)) - } - for bad_free in track.bad_free_array { - expect(&t, false, fmt.tprintf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)) - } - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - @test test_ordinal_date_roundtrip :: proc(t: ^testing.T) { - expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MIN_DATE)) == dt.MIN_DATE, "Roundtripping MIN_DATE failed.") - expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MIN_ORD)) == dt.MIN_ORD, "Roundtripping MIN_ORD failed.") - expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MAX_DATE)) == dt.MAX_DATE, "Roundtripping MAX_DATE failed.") - expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MAX_ORD)) == dt.MAX_ORD, "Roundtripping MAX_ORD failed.") + testing.expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MIN_DATE)) == dt.MIN_DATE, "Roundtripping MIN_DATE failed.") + testing.expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MIN_ORD)) == dt.MIN_ORD, "Roundtripping MIN_ORD failed.") + testing.expect(t, dt.unsafe_ordinal_to_date(dt.unsafe_date_to_ordinal(dt.MAX_DATE)) == dt.MAX_DATE, "Roundtripping MAX_DATE failed.") + testing.expect(t, dt.unsafe_date_to_ordinal(dt.unsafe_ordinal_to_date(dt.MAX_ORD)) == dt.MAX_ORD, "Roundtripping MAX_ORD failed.") } /* @@ -160,22 +109,51 @@ test_parse_rfc3339_string :: proc(t: ^testing.T) { is_leap := false if test.apply_offset { res, consumed := time.rfc3339_to_time_utc(test.rfc_3339, &is_leap) - msg := fmt.tprintf("[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v", test.rfc_3339, res, res._nsec, test.consumed, consumed) - expect(t, test.consumed == consumed, msg) + testing.expectf( + t, + test.consumed == consumed, + "[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v", + test.rfc_3339, res, res._nsec, test.consumed, consumed, + ) if test.consumed == consumed { - expect(t, test.datetime == res, fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec)) - expect(t, test.is_leap == is_leap, "Expected a leap second, got none.") + testing.expectf( + t, + test.datetime == res, + "Time didn't match. Expected %v (%v), got %v (%v)", + test.datetime, test.datetime._nsec, res, res._nsec, + ) + testing.expect( + t, + test.is_leap == is_leap, + "Expected a leap second, got none", + ) } } else { res, offset, consumed := time.rfc3339_to_time_and_offset(test.rfc_3339) - msg := fmt.tprintf("Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v", test.rfc_3339, res, res._nsec, offset, test.consumed, consumed) - expect(t, test.consumed == consumed, msg) + testing.expectf( + t, + test.consumed == consumed, + "Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v", + test.rfc_3339, res, res._nsec, offset, test.consumed, consumed, + ) if test.consumed == consumed { - expect(t, test.datetime == res, fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec)) - expect(t, test.utc_offset == offset, fmt.tprintf("UTC offset didn't match. Expected %v, got %v", test.utc_offset, offset)) - expect(t, test.is_leap == is_leap, "Expected a leap second, got none.") + testing.expectf( + t, test.datetime == res, + "Time didn't match. Expected %v (%v), got %v (%v)", + test.datetime, test.datetime._nsec, res, res._nsec, + ) + testing.expectf( + t, + test.utc_offset == offset, + "UTC offset didn't match. Expected %v, got %v", + test.utc_offset, offset, + ) + testing.expect( + t, test.is_leap == is_leap, + "Expected a leap second, got none", + ) } } } @@ -187,22 +165,52 @@ test_parse_iso8601_string :: proc(t: ^testing.T) { is_leap := false if test.apply_offset { res, consumed := time.iso8601_to_time_utc(test.iso_8601, &is_leap) - msg := fmt.tprintf("[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v", test.iso_8601, res, res._nsec, test.consumed, consumed) - expect(t, test.consumed == consumed, msg) + testing.expectf( + t, + test.consumed == consumed, + "[apply offet] Parsing failed: %v -> %v (nsec: %v). Expected %v consumed, got %v", + test.iso_8601, res, res._nsec, test.consumed, consumed, + ) if test.consumed == consumed { - expect(t, test.datetime == res, fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec)) - expect(t, test.is_leap == is_leap, "Expected a leap second, got none.") + testing.expectf( + t, + test.datetime == res, + "Time didn't match. Expected %v (%v), got %v (%v)", + test.datetime, test.datetime._nsec, res, res._nsec, + ) + testing.expect( + t, + test.is_leap == is_leap, + "Expected a leap second, got none", + ) } } else { res, offset, consumed := time.iso8601_to_time_and_offset(test.iso_8601) - msg := fmt.tprintf("Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v", test.iso_8601, res, res._nsec, offset, test.consumed, consumed) - expect(t, test.consumed == consumed, msg) + testing.expectf( + t, + test.consumed == consumed, + "Parsing failed: %v -> %v (nsec: %v), offset: %v. Expected %v consumed, got %v", + test.iso_8601, res, res._nsec, offset, test.consumed, consumed, + ) if test.consumed == consumed { - expect(t, test.datetime == res, fmt.tprintf("Time didn't match. Expected %v (%v), got %v (%v)", test.datetime, test.datetime._nsec, res, res._nsec)) - expect(t, test.utc_offset == offset, fmt.tprintf("UTC offset didn't match. Expected %v, got %v", test.utc_offset, offset)) - expect(t, test.is_leap == is_leap, "Expected a leap second, got none.") + testing.expectf( + t, test.datetime == res, + "Time didn't match. Expected %v (%v), got %v (%v)", + test.datetime, test.datetime._nsec, res, res._nsec, + ) + testing.expectf( + t, + test.utc_offset == offset, + "UTC offset didn't match. Expected %v, got %v", + test.utc_offset, offset, + ) + testing.expect( + t, + test.is_leap == is_leap, + "Expected a leap second, got none", + ) } } } @@ -231,15 +239,21 @@ test_component_to_time_roundtrip :: proc(t: ^testing.T) { date_component_roundtrip_test :: proc(t: ^testing.T, moment: dt.DateTime) { res, ok := time.datetime_to_time(moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second) - expect(t, ok, "Couldn't convert date components into date") + testing.expect( + t, + ok, + "Couldn't convert date components into date", + ) YYYY, MM, DD := time.date(res) hh, mm, ss := time.clock(res) - expected := fmt.tprintf("Expected %4d-%2d-%2d %2d:%2d:%2d, got %4d-%2d-%2d %2d:%2d:%2d", - moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, YYYY, MM, DD, hh, mm, ss) - ok = moment.year == i64(YYYY) && moment.month == i8(MM) && moment.day == i8(DD) ok &= moment.hour == i8(hh) && moment.minute == i8(mm) && moment.second == i8(ss) - expect(t, ok, expected) + testing.expectf( + t, + ok, + "Expected %4d-%2d-%2d %2d:%2d:%2d, got %4d-%2d-%2d %2d:%2d:%2d", + moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, YYYY, MM, DD, hh, mm, ss, + ) } \ No newline at end of file From dacb0f7786f5eb1e05b06179a5f68b587aa9db8a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 12:14:33 +0200 Subject: [PATCH 088/270] Port `tests\core\thread` --- tests/common/common.odin | 81 ------------------------ tests/core/Makefile | 3 +- tests/core/build.bat | 3 +- tests/core/test_core_odin | Bin 1401192 -> 0 bytes tests/core/thread/test_core_thread.odin | 56 ++++------------ 5 files changed, 14 insertions(+), 129 deletions(-) delete mode 100644 tests/common/common.odin delete mode 100644 tests/core/test_core_odin diff --git a/tests/common/common.odin b/tests/common/common.odin deleted file mode 100644 index 021fb21c5..000000000 --- a/tests/common/common.odin +++ /dev/null @@ -1,81 +0,0 @@ -// Boilerplate for tests -package common - -import "core:testing" -import "core:fmt" -import "core:os" -import "core:strings" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log - errorf :: testing.errorf -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v:%s] FAIL %v\n", loc, loc.procedure, message) - return - } - } - errorf :: proc(t: ^testing.T, message: string, args: ..any, loc := #caller_location) { - TEST_fail += 1 - fmt.printf("[%v:%s] Error %v\n", loc, loc.procedure, fmt.tprintf(message, ..args)) - return - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -report :: proc(t: ^testing.T) { - if TEST_fail > 0 { - if TEST_fail > 1 { - fmt.printf("%v/%v tests successful, %v tests failed.\n", TEST_count - TEST_fail, TEST_count, TEST_fail) - } else { - fmt.printf("%v/%v tests successful, 1 test failed.\n", TEST_count - TEST_fail, TEST_count) - } - os.exit(1) - } else { - fmt.printf("%v/%v tests successful.\n", TEST_count, TEST_count) - } -} - -// Returns absolute path to `sub_path` where `sub_path` is within the "tests/" sub-directory of the Odin project root -// and we're being run from the Odin project root or from a sub-directory of "tests/" -// e.g. get_data_path("assets/blah") will return "/Odin_root/tests/assets/blah" if run within "/Odin_root", -// "/Odin_root/tests" or "/Odin_root/tests/subdir" etc -get_data_path :: proc(t: ^testing.T, sub_path: string) -> (data_path: string) { - - cwd := os.get_current_directory() - defer delete(cwd) - - when ODIN_OS == .Windows { - norm, was_allocation := strings.replace_all(cwd, "\\", "/") - if !was_allocation { - norm = strings.clone(norm) - } - defer delete(norm) - } else { - norm := cwd - } - - last_index := strings.last_index(norm, "/tests/") - if last_index == -1 { - len := len(norm) - if len >= 6 && norm[len-6:] == "/tests" { - data_path = fmt.tprintf("%s/%s", norm, sub_path) - } else { - data_path = fmt.tprintf("%s/tests/%s", norm, sub_path) - } - } else { - data_path = fmt.tprintf("%s/tests/%s", norm[:last_index], sub_path) - } - - return data_path -} diff --git a/tests/core/Makefile b/tests/core/Makefile index ec4cdcafd..4e13733b0 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,7 +1,6 @@ ODIN=../../odin PYTHON=$(shell which python3) COMMON=-no-bounds-check -vet -strict-style -COLLECTION=-collection:tests=.. all: all_bsd \ net_test @@ -103,7 +102,7 @@ strings_test: $(ODIN) test strings $(COMMON) -out:test_core_strings thread_test: - $(ODIN) run thread $(COMMON) -out:test_core_thread + $(ODIN) test thread $(COMMON) -out:test_core_thread time_test: $(ODIN) test time $(COMMON) -out:test_core_time diff --git a/tests/core/build.bat b/tests/core/build.bat index 090369848..accf0808a 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -1,6 +1,5 @@ @echo off set COMMON=-no-bounds-check -vet -strict-style -set COLLECTION=-collection:tests=.. set PATH_TO_ODIN==..\..\odin python3 download_assets.py echo --- @@ -112,7 +111,7 @@ echo --- echo --- echo Running core:thread tests echo --- -%PATH_TO_ODIN% run thread %COMMON% %COLLECTION% -out:test_core_thread.exe || exit /b +%PATH_TO_ODIN% test thread %COMMON% -out:test_core_thread.exe || exit /b echo --- echo Running core:time tests diff --git a/tests/core/test_core_odin b/tests/core/test_core_odin deleted file mode 100644 index bdf316228a8bddc5f4a0998789b00dc1f182e9f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1401192 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 B9g6?} diff --git a/tests/core/thread/test_core_thread.odin b/tests/core/thread/test_core_thread.odin index c0c7396a7..0b77ad511 100644 --- a/tests/core/thread/test_core_thread.odin +++ b/tests/core/thread/test_core_thread.odin @@ -2,39 +2,7 @@ package test_core_thread import "core:testing" import "core:thread" -import "core:fmt" -import "core:os" - -TEST_count := 0 -TEST_fail := 0 - -t := &testing.T{} - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - poly_data_test(t) - - if TEST_fail > 0 { - os.exit(1) - } -} +import "base:intrinsics" @(test) poly_data_test :: proc(_t: ^testing.T) { @@ -46,7 +14,7 @@ poly_data_test :: proc(_t: ^testing.T) { b: [MAX]byte = 8 t1 := thread.create_and_start_with_poly_data(b, proc(b: [MAX]byte) { b_expect: [MAX]byte = 8 - expect(poly_data_test_t, b == b_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, b == b_expect, "thread poly data not correct") }) defer free(t1) @@ -55,8 +23,8 @@ poly_data_test :: proc(_t: ^testing.T) { t2 := thread.create_and_start_with_poly_data2(b1, b2, proc(b: [3]uintptr, b2: [MAX / 2]byte) { b_expect: [3]uintptr = 1 b2_expect: [MAX / 2]byte = 3 - expect(poly_data_test_t, b == b_expect, "thread poly data not correct") - expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, b == b_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct") }) defer free(t2) @@ -64,21 +32,21 @@ poly_data_test :: proc(_t: ^testing.T) { b_expect: [3]uintptr = 1 b2_expect: [MAX / 2]byte = 3 - expect(poly_data_test_t, b == b_expect, "thread poly data not correct") - expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct") - expect(poly_data_test_t, b3 == 333, "thread poly data not correct") + testing.expect(poly_data_test_t, b == b_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, b2 == b2_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, b3 == 333, "thread poly data not correct") }) defer free(t3) t4 := thread.create_and_start_with_poly_data4(uintptr(111), b1, uintptr(333), u8(5), proc(n: uintptr, b: [3]uintptr, n2: uintptr, n4: u8) { b_expect: [3]uintptr = 1 - expect(poly_data_test_t, n == 111, "thread poly data not correct") - expect(poly_data_test_t, b == b_expect, "thread poly data not correct") - expect(poly_data_test_t, n2 == 333, "thread poly data not correct") - expect(poly_data_test_t, n4 == 5, "thread poly data not correct") + testing.expect(poly_data_test_t, n == 111, "thread poly data not correct") + testing.expect(poly_data_test_t, b == b_expect, "thread poly data not correct") + testing.expect(poly_data_test_t, n2 == 333, "thread poly data not correct") + testing.expect(poly_data_test_t, n4 == 5, "thread poly data not correct") }) defer free(t4) thread.join_multiple(t1, t2, t3, t4) -} +} \ No newline at end of file From 62b7d8de9732eef542dcb878b089b30972cb8afc Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 16:32:12 +0200 Subject: [PATCH 089/270] Port `tests\core\net` --- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/net/test_core_net.odin | 276 +++++++++--------------------- 3 files changed, 85 insertions(+), 195 deletions(-) diff --git a/tests/core/Makefile b/tests/core/Makefile index 4e13733b0..9b12f5d76 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -81,7 +81,7 @@ noise_test: $(ODIN) test math/noise $(COMMON) -out:test_noise net_test: - $(ODIN) run net $(COMMON) -out:test_core_net + $(ODIN) test net $(COMMON) -out:test_core_net os_exit_test: $(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0 diff --git a/tests/core/build.bat b/tests/core/build.bat index accf0808a..3e67b6e13 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -81,7 +81,7 @@ echo --- echo --- echo Running core:net echo --- -%PATH_TO_ODIN% run net %COMMON% -out:test_core_net.exe || exit /b +%PATH_TO_ODIN% test net %COMMON% -out:test_core_net.exe || exit /b echo --- echo Running core:odin tests diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index 9df03414c..3cbb7fa34 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -11,71 +11,12 @@ package test_core_net import "core:testing" -import "core:mem" -import "core:fmt" import "core:net" import "core:strconv" import "core:sync" import "core:time" import "core:thread" -import "core:os" - -_, _ :: time, thread - -TEST_count := 0 -TEST_fail := 0 - -t := &testing.T{} - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -_tracking_allocator := mem.Tracking_Allocator{} - -print_tracking_allocator_report :: proc() { - for _, leak in _tracking_allocator.allocation_map { - fmt.printf("%v leaked %v bytes\n", leak.location, leak.size) - } - - for bf in _tracking_allocator.bad_free_array { - fmt.printf("%v allocation %p was freed badly\n", bf.location, bf.memory) - } -} - -main :: proc() { - mem.tracking_allocator_init(&_tracking_allocator, context.allocator) - context.allocator = mem.tracking_allocator(&_tracking_allocator) - - address_parsing_test(t) - - tcp_tests(t) - - split_url_test(t) - join_url_test(t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - - print_tracking_allocator_report() - - if TEST_fail > 0 { - os.exit(1) - } -} +import "core:fmt" @test address_parsing_test :: proc(t: ^testing.T) { @@ -89,127 +30,66 @@ address_parsing_test :: proc(t: ^testing.T) { } valid := len(vector.binstr) > 0 - - fmt.printf("%v %v\n", kind, vector.input) - - msg := "-set a proper message-" switch vector.family { case .IP4, .IP4_Alt: - /* - Does `net.parse_ip4_address` think we parsed the address properly? - */ + // Does `net.parse_ip4_address` think we parsed the address properly? non_decimal := vector.family == .IP4_Alt + any_addr := net.parse_address(vector.input, non_decimal) + parsed_ok := any_addr != nil + parsed: net.IP4_Address - any_addr := net.parse_address(vector.input, non_decimal) - parsed_ok := any_addr != nil - parsed: net.IP4_Address - - /* - Ensure that `parse_address` doesn't parse IPv4 addresses into IPv6 addreses by mistake. - */ + // Ensure that `parse_address` doesn't parse IPv4 addresses into IPv6 addreses by mistake. switch addr in any_addr { case net.IP4_Address: parsed = addr case net.IP6_Address: parsed_ok = false - msg = fmt.tprintf("parse_address mistook %v as IPv6 address %04x", vector.input, addr) - expect(t, false, msg) + testing.expectf(t, false, "parse_address mistook %v as IPv6 address %04x", vector.input, addr) } if !parsed_ok && valid { - msg = fmt.tprintf("parse_ip4_address failed to parse %v, expected %v", vector.input, binstr_to_address(vector.binstr)) + testing.expectf(t, parsed_ok == valid, "parse_ip4_address failed to parse %v, expected %v", vector.input, binstr_to_address(t, vector.binstr)) } else if parsed_ok && !valid { - msg = fmt.tprintf("parse_ip4_address parsed %v into %v, expected failure", vector.input, parsed) + testing.expectf(t, parsed_ok == valid, "parse_ip4_address parsed %v into %v, expected failure", vector.input, parsed) } - expect(t, parsed_ok == valid, msg) if valid && parsed_ok { actual_binary := address_to_binstr(parsed) - msg = fmt.tprintf("parse_ip4_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) - expect(t, actual_binary == vector.binstr, msg) + testing.expectf(t, actual_binary == vector.binstr, "parse_ip4_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) - /* - Do we turn an address back into the same string properly? - No point in testing the roundtrip if the first part failed. - */ + // Do we turn an address back into the same string properly? No point in testing the roundtrip if the first part failed. if len(vector.output) > 0 && actual_binary == vector.binstr { stringified := net.address_to_string(parsed) - msg = fmt.tprintf("address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) - expect(t, stringified == vector.output, msg) + testing.expectf(t, stringified == vector.output, "address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) } } case .IP6: - /* - Do we parse the address properly? - */ + // Do we parse the address properly? parsed, parsed_ok := net.parse_ip6_address(vector.input) if !parsed_ok && valid { - msg = fmt.tprintf("parse_ip6_address failed to parse %v, expected %04x", vector.input, binstr_to_address(vector.binstr)) + testing.expectf(t, parsed_ok == valid, "parse_ip6_address failed to parse %v, expected %04x", vector.input, binstr_to_address(t, vector.binstr)) } else if parsed_ok && !valid { - msg = fmt.tprintf("parse_ip6_address parsed %v into %04x, expected failure", vector.input, parsed) + testing.expectf(t, parsed_ok == valid, "parse_ip6_address parsed %v into %04x, expected failure", vector.input, parsed) } - expect(t, parsed_ok == valid, msg) if valid && parsed_ok { actual_binary := address_to_binstr(parsed) - msg = fmt.tprintf("parse_ip6_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) - expect(t, actual_binary == vector.binstr, msg) + testing.expectf(t, actual_binary == vector.binstr, "parse_ip6_address parsed %v into %v, expected %v", vector.input, actual_binary, vector.binstr) - /* - Do we turn an address back into the same string properly? - No point in testing the roundtrip if the first part failed. - */ + // Do we turn an address back into the same string properly? No point in testing the roundtrip if the first part failed. if len(vector.output) > 0 && actual_binary == vector.binstr { stringified := net.address_to_string(parsed) - msg = fmt.tprintf("address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) - expect(t, stringified == vector.output, msg) + testing.expectf(t, stringified == vector.output, "address_to_string turned %v into %v, expected %v", parsed, stringified, vector.output) } } } } } -address_to_binstr :: proc(address: net.Address) -> (binstr: string) { - switch t in address { - case net.IP4_Address: - b := transmute(u32be)t - return fmt.tprintf("%08x", b) - case net.IP6_Address: - b := transmute(u128be)t - return fmt.tprintf("%32x", b) - case: - return "" - } - unreachable() -} - -binstr_to_address :: proc(binstr: string) -> (address: net.Address) { - switch len(binstr) { - case 8: // IPv4 - a, ok := strconv.parse_u64_of_base(binstr, 16) - expect(t, ok, "failed to parse test case bin string") - - ipv4 := u32be(a) - return net.IP4_Address(transmute([4]u8)ipv4) - - - case 32: // IPv6 - a, ok := strconv.parse_u128_of_base(binstr, 16) - expect(t, ok, "failed to parse test case bin string") - - ipv4 := u128be(a) - return net.IP6_Address(transmute([8]u16be)ipv4) - - case 0: - return nil - } - panic("Invalid test case") -} - Kind :: enum { IP4, // Decimal IPv4 IP4_Alt, // Non-decimal address @@ -223,10 +103,7 @@ IP_Address_Parsing_Test_Vector :: struct { // Input address to try and parse. input: string, - /* - Hexadecimal representation of the expected numeric value of the address. - Zero length means input is invalid and the parser should report failure. - */ + // Hexadecimal representation of the expected numeric value of the address. Zero length means input is invalid and the parser should report failure. binstr: string, // Expected `address_to_string` output, if a valid input and this string is non-empty. @@ -335,38 +212,30 @@ IP_Address_Parsing_Test_Vectors :: []IP_Address_Parsing_Test_Vector{ { .IP6, "c0a8", "", ""}, } -tcp_tests :: proc(t: ^testing.T) { - fmt.println("Testing two servers trying to bind to the same endpoint...") - two_servers_binding_same_endpoint(t) - fmt.println("Testing client connecting to a closed port...") - client_connects_to_closed_port(t) - fmt.println("Testing client sending server data...") - client_sends_server_data(t) -} - -ENDPOINT := net.Endpoint{ - net.IP4_Address{127, 0, 0, 1}, - 9999, -} +ENDPOINT_TWO_SERVERS := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9991} +ENDPOINT_CLOSED_PORT := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9992} +ENDPOINT_SERVER_SENDS := net.Endpoint{net.IP4_Address{127, 0, 0, 1}, 9993} @(test) two_servers_binding_same_endpoint :: proc(t: ^testing.T) { - skt1, err1 := net.listen_tcp(ENDPOINT) + skt1, err1 := net.listen_tcp(ENDPOINT_TWO_SERVERS) defer net.close(skt1) - skt2, err2 := net.listen_tcp(ENDPOINT) + skt2, err2 := net.listen_tcp(ENDPOINT_TWO_SERVERS) defer net.close(skt2) - expect(t, err1 == nil, "expected first server binding to endpoint to do so without error") - expect(t, err2 == net.Bind_Error.Address_In_Use, "expected second server to bind to an endpoint to return .Address_In_Use") + testing.expect(t, err1 == nil, "expected first server binding to endpoint to do so without error") + testing.expect(t, err2 == net.Bind_Error.Address_In_Use, "expected second server to bind to an endpoint to return .Address_In_Use") } @(test) client_connects_to_closed_port :: proc(t: ^testing.T) { - skt, err := net.dial_tcp(ENDPOINT) + + skt, err := net.dial_tcp(ENDPOINT_CLOSED_PORT) defer net.close(skt) - expect(t, err == net.Dial_Error.Refused, "expected dial of a closed endpoint to return .Refused") + testing.expect(t, err == net.Dial_Error.Refused, "expected dial of a closed endpoint to return .Refused") } + @(test) client_sends_server_data :: proc(t: ^testing.T) { CONTENT: string: "Hellope!" @@ -390,8 +259,8 @@ client_sends_server_data :: proc(t: ^testing.T) { defer sync.wait_group_done(r.wg) - if r.skt, r.err = net.dial_tcp(ENDPOINT); r.err != nil { - log(r.t, r.err) + if r.skt, r.err = net.dial_tcp(ENDPOINT_SERVER_SENDS); r.err != nil { + testing.expectf(r.t, false, "[tcp_client:dial_tcp] %v", r.err) return } @@ -405,19 +274,17 @@ client_sends_server_data :: proc(t: ^testing.T) { defer sync.wait_group_done(r.wg) - log(r.t, "tcp_server listen") - if r.skt, r.err = net.listen_tcp(ENDPOINT); r.err != nil { + if r.skt, r.err = net.listen_tcp(ENDPOINT_SERVER_SENDS); r.err != nil { sync.wait_group_done(r.wg) - log(r.t, r.err) + testing.expectf(r.t, false, "[tcp_server:listen_tcp] %v", r.err) return } sync.wait_group_done(r.wg) - log(r.t, "tcp_server accept") client: net.TCP_Socket if client, _, r.err = net.accept_tcp(r.skt.(net.TCP_Socket)); r.err != nil { - log(r.t, r.err) + testing.expectf(r.t, false, "[tcp_server:accept_tcp] %v", r.err) return } defer net.close(client) @@ -437,10 +304,7 @@ client_sends_server_data :: proc(t: ^testing.T) { thread_data[0].wg = &wg thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context) - log(t, "waiting for server to start listening") sync.wait_group_wait(&wg) - log(t, "starting up client") - sync.wait_group_add(&wg, 2) thread_data[1].t = t @@ -454,20 +318,15 @@ client_sends_server_data :: proc(t: ^testing.T) { net.close(thread_data[1].skt) thread.destroy(thread_data[1].tid) } - - log(t, "waiting for threads to finish") sync.wait_group_wait(&wg) - log(t, "threads finished") okay := thread_data[0].err == nil && thread_data[1].err == nil - msg := fmt.tprintf("Expected client and server to return `nil`, got %v and %v", thread_data[0].err, thread_data[1].err) - expect(t, okay, msg) + testing.expectf(t, okay, "Expected client and server to return `nil`, got %v and %v", thread_data[0].err, thread_data[1].err) received := string(thread_data[0].data[:thread_data[0].length]) okay = received == CONTENT - msg = fmt.tprintf("Expected client to send \"{}\", got \"{}\"", CONTENT, received) - expect(t, okay, msg) + testing.expectf(t, okay, "Expected client to send \"{}\", got \"{}\"", CONTENT, received) } URL_Test :: struct { @@ -559,22 +418,15 @@ split_url_test :: proc(t: ^testing.T) { delete(test.queries) } - msg := fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.scheme, scheme) - expect(t, scheme == test.scheme, msg) - msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.host, host) - expect(t, host == test.host, msg) - msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.path, path) - expect(t, path == test.path, msg) - msg = fmt.tprintf("Expected `net.split_url` to return %d queries, got %d queries", len(test.queries), len(queries)) - expect(t, len(queries) == len(test.queries), msg) + testing.expectf(t, scheme == test.scheme, "Expected `net.split_url` to return %s, got %s", test.scheme, scheme) + testing.expectf(t, host == test.host, "Expected `net.split_url` to return %s, got %s", test.host, host) + testing.expectf(t, path == test.path, "Expected `net.split_url` to return %s, got %s", test.path, path) + testing.expectf(t, len(queries) == len(test.queries), "Expected `net.split_url` to return %d queries, got %d queries", len(test.queries), len(queries)) for k, v in queries { expected := test.queries[k] - msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", expected, v) - expect(t, v == expected, msg) + testing.expectf(t, v == expected, "Expected `net.split_url` to return %s, got %s", expected, v) } - msg = fmt.tprintf("Expected `net.split_url` to return %s, got %s", test.fragment, fragment) - expect(t, fragment == test.fragment, msg) - + testing.expectf(t, fragment == test.fragment, "Expected `net.split_url` to return %s, got %s", test.fragment, fragment) } } @@ -659,7 +511,45 @@ join_url_test :: proc(t: ^testing.T) { for test_url in test.url { pass |= url == test_url } - msg := fmt.tprintf("Expected `net.join_url` to return one of %s, got %s", test.url, url) - expect(t, pass, msg) + testing.expectf(t, pass, "Expected `net.join_url` to return one of %s, got %s", test.url, url) } } + +@(private) +address_to_binstr :: proc(address: net.Address) -> (binstr: string) { + switch t in address { + case net.IP4_Address: + b := transmute(u32be)t + return fmt.tprintf("%08x", b) + case net.IP6_Address: + b := transmute(u128be)t + return fmt.tprintf("%32x", b) + case: + return "" + } + unreachable() +} + +@(private) +binstr_to_address :: proc(t: ^testing.T, binstr: string) -> (address: net.Address) { + switch len(binstr) { + case 8: // IPv4 + a, ok := strconv.parse_u64_of_base(binstr, 16) + testing.expect(t, ok, "failed to parse test case bin string") + + ipv4 := u32be(a) + return net.IP4_Address(transmute([4]u8)ipv4) + + + case 32: // IPv6 + a, ok := strconv.parse_u128_of_base(binstr, 16) + testing.expect(t, ok, "failed to parse test case bin string") + + ipv4 := u128be(a) + return net.IP6_Address(transmute([8]u16be)ipv4) + + case 0: + return nil + } + panic("Invalid test case") +} \ No newline at end of file From 8d93379e299b8115c2727ba1a442609ac4293416 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 20:10:55 +0200 Subject: [PATCH 090/270] Factor benchmarks out into tests\benchmark\ --- .github/workflows/ci.yml | 23 ++ tests/benchmark/Makefile | 14 + tests/benchmark/crypto/benchmark_crypto.odin | 356 +++++++++++++++++++ tests/benchmark/hash/benchmark_hash.odin | 218 ++++++++++++ tests/core/crypto/test_crypto_benchmark.odin | 353 ------------------ tests/core/hash/test_core_hash.odin | 232 +----------- 6 files changed, 615 insertions(+), 581 deletions(-) create mode 100644 tests/benchmark/Makefile create mode 100644 tests/benchmark/crypto/benchmark_crypto.odin create mode 100644 tests/benchmark/hash/benchmark_hash.odin delete mode 100644 tests/core/crypto/test_crypto_benchmark.odin diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffb2077d1..67b2bfbda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,7 @@ jobs: (cd tests/core; gmake all_bsd) (cd tests/internal; gmake all_bsd) (cd tests/issues; ./run.sh) + (cd tests/benchmark; gmake all) build_linux: name: Ubuntu Build, Check, and Test runs-on: ubuntu-latest @@ -80,6 +81,11 @@ jobs: cd tests/internal make timeout-minutes: 10 + - name: Odin core library benchmarks + run: | + cd tests/benchmark + make + timeout-minutes: 10 - name: Odin check examples/all for Linux i386 run: ./odin check examples/all -vet -strict-style -target:linux_i386 timeout-minutes: 10 @@ -131,6 +137,11 @@ jobs: cd tests/internal make timeout-minutes: 10 + - name: Odin core library benchmarks + run: | + cd tests/benchmark + make + timeout-minutes: 10 build_macOS_arm: name: MacOS ARM Build, Check, and Test runs-on: macos-14 # This is an arm/m1 runner. @@ -170,6 +181,11 @@ jobs: cd tests/internal make timeout-minutes: 10 + - name: Odin core library benchmarks + run: | + cd tests/benchmark + make + timeout-minutes: 10 build_windows: name: Windows Build, Check, and Test runs-on: windows-2022 @@ -245,6 +261,13 @@ jobs: cd tests\core\math\big call build.bat timeout-minutes: 10 + - name: core library benchmarks + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat + cd tests\benchmark + call build.bat + timeout-minutes: 10 - name: Odin check examples/all for Windows 32bits shell: cmd run: | diff --git a/tests/benchmark/Makefile b/tests/benchmark/Makefile new file mode 100644 index 000000000..8a8e08b24 --- /dev/null +++ b/tests/benchmark/Makefile @@ -0,0 +1,14 @@ +ODIN=../../odin +COMMON=-no-bounds-check -vet -strict-style + +all: crypto_bench \ + hash_bench + +crypto_bench: + $(ODIN) test crypto $(COMMON) -o:speed -out:bench_crypto + +hash_bench: + $(ODIN) test hash $(COMMON) -o:speed -out:bench_hash + +clean: + rm bench_* \ No newline at end of file diff --git a/tests/benchmark/crypto/benchmark_crypto.odin b/tests/benchmark/crypto/benchmark_crypto.odin new file mode 100644 index 000000000..e90216ad6 --- /dev/null +++ b/tests/benchmark/crypto/benchmark_crypto.odin @@ -0,0 +1,356 @@ +package benchmark_core_crypto + +import "base:runtime" +import "core:encoding/hex" +import "core:fmt" +import "core:log" +import "core:strings" +import "core:testing" +import "core:time" + +import "core:crypto/aes" +import "core:crypto/chacha20" +import "core:crypto/chacha20poly1305" +import "core:crypto/ed25519" +import "core:crypto/poly1305" +import "core:crypto/x25519" + +// Cryptographic primitive benchmarks. + +@(test) +benchmark_crypto :: proc(t: ^testing.T) { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + str: strings.Builder + strings.builder_init(&str, context.allocator) + defer { + log.info(strings.to_string(str)) + strings.builder_destroy(&str) + } + + { + name := "ChaCha20 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_chacha20, + teardown = _teardown_sized_buf, + } + + err := time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "ChaCha20 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "ChaCha20 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + } + { + name := "Poly1305 64 zero bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_poly1305, + teardown = _teardown_sized_buf, + } + + err := time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "Poly1305 1024 zero bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + } + { + name := "chacha20poly1305 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_chacha20poly1305, + teardown = _teardown_sized_buf, + } + + err := time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "chacha20poly1305 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "chacha20poly1305 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + } + { + name := "AES256-GCM 64 bytes" + options := &time.Benchmark_Options { + rounds = 1_000, + bytes = 64, + setup = _setup_sized_buf, + bench = _benchmark_aes256_gcm, + teardown = _teardown_sized_buf, + } + + key := [aes.KEY_SIZE_256]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + ctx: aes.Context_GCM + aes.init_gcm(&ctx, key[:]) + + context.user_ptr = &ctx + + err := time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "AES256-GCM 1024 bytes" + options.bytes = 1024 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + + name = "AES256-GCM 65536 bytes" + options.bytes = 65536 + err = time.benchmark(options, context.allocator) + testing.expect(t, err == nil, name) + benchmark_print(&str, name, options) + } + { + iters :: 10000 + + priv_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" + priv_bytes, _ := hex.decode(transmute([]byte)(priv_str), context.temp_allocator) + priv_key: ed25519.Private_Key + start := time.now() + for i := 0; i < iters; i = i + 1 { + ok := ed25519.private_key_set_bytes(&priv_key, priv_bytes) + assert(ok, "private key should deserialize") + } + elapsed := time.since(start) + fmt.sbprintfln(&str, + "ed25519.private_key_set_bytes: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) + + pub_bytes := priv_key._pub_key._b[:] // "I know what I am doing" + pub_key: ed25519.Public_Key + start = time.now() + for i := 0; i < iters; i = i + 1 { + ok := ed25519.public_key_set_bytes(&pub_key, pub_bytes[:]) + assert(ok, "public key should deserialize") + } + elapsed = time.since(start) + fmt.sbprintfln(&str, + "ed25519.public_key_set_bytes: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) + + msg := "Got a job for you, 621." + sig_bytes: [ed25519.SIGNATURE_SIZE]byte + msg_bytes := transmute([]byte)(msg) + start = time.now() + for i := 0; i < iters; i = i + 1 { + ed25519.sign(&priv_key, msg_bytes, sig_bytes[:]) + } + elapsed = time.since(start) + fmt.sbprintfln(&str, + "ed25519.sign: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) + + start = time.now() + for i := 0; i < iters; i = i + 1 { + ok := ed25519.verify(&pub_key, msg_bytes, sig_bytes[:]) + assert(ok, "signature should validate") + } + elapsed = time.since(start) + fmt.sbprintfln(&str, + "ed25519.verify: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) + } + { + point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" + scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" + + point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator) + scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator) + out: [x25519.POINT_SIZE]byte = --- + + iters :: 10000 + start := time.now() + for i := 0; i < iters; i = i + 1 { + x25519.scalarmult(out[:], scalar[:], point[:]) + } + elapsed := time.since(start) + + fmt.sbprintfln(&str, + "x25519.scalarmult: ~%f us/op", + time.duration_microseconds(elapsed) / iters, + ) + } +} + +@(private) +_setup_sized_buf :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + assert(options != nil) + + options.input = make([]u8, options.bytes, allocator) + return nil if len(options.input) == options.bytes else .Allocation_Error +} + +@(private) +_teardown_sized_buf :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + assert(options != nil) + + delete(options.input) + return nil +} + +@(private) +_benchmark_chacha20 :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + key := [chacha20.KEY_SIZE]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + nonce := [chacha20.NONCE_SIZE]byte { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + } + + ctx: chacha20.Context = --- + chacha20.init(&ctx, key[:], nonce[:]) + + for _ in 0 ..= options.rounds { + chacha20.xor_bytes(&ctx, buf, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + +@(private) +_benchmark_poly1305 :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + key := [poly1305.KEY_SIZE]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + + tag: [poly1305.TAG_SIZE]byte = --- + for _ in 0 ..= options.rounds { + poly1305.sum(tag[:], buf, key[:]) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + //options.hash = u128(h) + return nil +} + +@(private) +_benchmark_chacha20poly1305 :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + key := [chacha20.KEY_SIZE]byte { + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + } + nonce := [chacha20.NONCE_SIZE]byte { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + } + + tag: [chacha20poly1305.TAG_SIZE]byte = --- + + for _ in 0 ..= options.rounds { + chacha20poly1305.encrypt(buf, tag[:], key[:], nonce[:], nil, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + +_benchmark_aes256_gcm :: proc( + options: ^time.Benchmark_Options, + allocator := context.allocator, +) -> ( + err: time.Benchmark_Error, +) { + buf := options.input + nonce: [aes.GCM_NONCE_SIZE]byte + tag: [aes.GCM_TAG_SIZE]byte = --- + + ctx := transmute(^aes.Context_GCM)context.user_ptr + + for _ in 0 ..= options.rounds { + aes.seal_gcm(ctx, buf, tag[:], nonce[:], nil, buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + return nil +} + +@(private) +benchmark_print :: proc(str: ^strings.Builder, name: string, options: ^time.Benchmark_Options, loc := #caller_location) { + fmt.sbprintfln(str, "[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", + name, + options.rounds, + options.processed, + time.duration_nanoseconds(options.duration), + options.rounds_per_second, + options.megabytes_per_second, + ) +} diff --git a/tests/benchmark/hash/benchmark_hash.odin b/tests/benchmark/hash/benchmark_hash.odin new file mode 100644 index 000000000..84eb827e7 --- /dev/null +++ b/tests/benchmark/hash/benchmark_hash.odin @@ -0,0 +1,218 @@ +package benchmark_core_hash + +import "core:fmt" +import "core:hash/xxhash" +import "base:intrinsics" +import "core:strings" +import "core:testing" +import "core:time" + +@(test) +benchmark_hash :: proc(t: ^testing.T) { + str: strings.Builder + strings.builder_init(&str, context.allocator) + defer { + fmt.println(strings.to_string(str)) + strings.builder_destroy(&str) + } + + { + name := "XXH32 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh32, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x85f6413c) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH32 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh32, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x9430f97f) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH64 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x17bb1103c92c502f) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH64 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x87d2a1b6e1163ef1) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH3_64 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh3_64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x801fedc74ccd608c) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH3_64 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh3_64, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x918780b90550bf34) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH3_128 100 zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 100, + setup = setup_xxhash, + bench = benchmark_xxh3_128, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0x6ba30a4e9dffe1ff801fedc74ccd608c) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } + { + name := "XXH3_128 1 MiB zero bytes" + options := &time.Benchmark_Options{ + rounds = 1_000, + bytes = 1_048_576, + setup = setup_xxhash, + bench = benchmark_xxh3_128, + teardown = teardown_xxhash, + } + err := time.benchmark(options, context.allocator) + testing.expectf(t, err == nil, "%s failed with err %v", name, err) + hash := u128(0xb6ef17a3448492b6918780b90550bf34) + testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) + benchmark_print(&str, name, options) + } +} + +// Benchmarks + +setup_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + assert(options != nil) + + options.input = make([]u8, options.bytes, allocator) + return nil if len(options.input) == options.bytes else .Allocation_Error +} + +teardown_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + assert(options != nil) + + delete(options.input) + return nil +} + +benchmark_xxh32 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + buf := options.input + + h: u32 + for _ in 0..=options.rounds { + h = xxhash.XXH32(buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + options.hash = u128(h) + return nil +} + +benchmark_xxh64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + buf := options.input + + h: u64 + for _ in 0..=options.rounds { + h = xxhash.XXH64(buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + options.hash = u128(h) + return nil +} + +benchmark_xxh3_64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + buf := options.input + + h: u64 + for _ in 0..=options.rounds { + h = xxhash.XXH3_64(buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + options.hash = u128(h) + return nil +} + +benchmark_xxh3_128 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { + buf := options.input + + h: u128 + for _ in 0..=options.rounds { + h = xxhash.XXH3_128(buf) + } + options.count = options.rounds + options.processed = options.rounds * options.bytes + options.hash = h + return nil +} + +benchmark_print :: proc(str: ^strings.Builder, name: string, options: ^time.Benchmark_Options, loc := #caller_location) { + fmt.sbprintfln(str, "[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s\n", + name, + options.rounds, + options.processed, + time.duration_nanoseconds(options.duration), + options.rounds_per_second, + options.megabytes_per_second, + ) +} \ No newline at end of file diff --git a/tests/core/crypto/test_crypto_benchmark.odin b/tests/core/crypto/test_crypto_benchmark.odin deleted file mode 100644 index 5bfc579bd..000000000 --- a/tests/core/crypto/test_crypto_benchmark.odin +++ /dev/null @@ -1,353 +0,0 @@ -package test_core_crypto - -import "base:runtime" -import "core:encoding/hex" -import "core:log" -import "core:testing" -import "core:time" - -import "core:crypto/aes" -import "core:crypto/chacha20" -import "core:crypto/chacha20poly1305" -import "core:crypto/ed25519" -import "core:crypto/poly1305" -import "core:crypto/x25519" - -// Cryptographic primitive benchmarks. - -@(test) -bench_crypto :: proc(t: ^testing.T) { - runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() - bench_chacha20(t) - bench_poly1305(t) - bench_chacha20poly1305(t) - bench_aes256_gcm(t) - bench_ed25519(t) - bench_x25519(t) -} - -_setup_sized_buf :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - assert(options != nil) - - options.input = make([]u8, options.bytes, allocator) - return nil if len(options.input) == options.bytes else .Allocation_Error -} - -_teardown_sized_buf :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - assert(options != nil) - - delete(options.input) - return nil -} - -_benchmark_chacha20 :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - buf := options.input - key := [chacha20.KEY_SIZE]byte { - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - } - nonce := [chacha20.NONCE_SIZE]byte { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - } - - ctx: chacha20.Context = --- - chacha20.init(&ctx, key[:], nonce[:]) - - for _ in 0 ..= options.rounds { - chacha20.xor_bytes(&ctx, buf, buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - return nil -} - -_benchmark_poly1305 :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - buf := options.input - key := [poly1305.KEY_SIZE]byte { - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - } - - tag: [poly1305.TAG_SIZE]byte = --- - for _ in 0 ..= options.rounds { - poly1305.sum(tag[:], buf, key[:]) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - //options.hash = u128(h) - return nil -} - -_benchmark_chacha20poly1305 :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - buf := options.input - key := [chacha20.KEY_SIZE]byte { - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - } - nonce := [chacha20.NONCE_SIZE]byte { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - } - - tag: [chacha20poly1305.TAG_SIZE]byte = --- - - for _ in 0 ..= options.rounds { - chacha20poly1305.encrypt(buf, tag[:], key[:], nonce[:], nil, buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - return nil -} - -_benchmark_aes256_gcm :: proc( - options: ^time.Benchmark_Options, - allocator := context.allocator, -) -> ( - err: time.Benchmark_Error, -) { - buf := options.input - nonce: [aes.GCM_NONCE_SIZE]byte - tag: [aes.GCM_TAG_SIZE]byte = --- - - ctx := transmute(^aes.Context_GCM)context.user_ptr - - for _ in 0 ..= options.rounds { - aes.seal_gcm(ctx, buf, tag[:], nonce[:], nil, buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - return nil -} - -benchmark_print :: proc(name: string, options: ^time.Benchmark_Options) { - log.infof( - "\n\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s", - name, - options.rounds, - options.processed, - time.duration_nanoseconds(options.duration), - options.rounds_per_second, - options.megabytes_per_second, - ) -} - -bench_chacha20 :: proc(t: ^testing.T) { - name := "ChaCha20 64 bytes" - options := &time.Benchmark_Options { - rounds = 1_000, - bytes = 64, - setup = _setup_sized_buf, - bench = _benchmark_chacha20, - teardown = _teardown_sized_buf, - } - - err := time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "ChaCha20 1024 bytes" - options.bytes = 1024 - err = time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "ChaCha20 65536 bytes" - options.bytes = 65536 - err = time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) -} - -bench_poly1305 :: proc(t: ^testing.T) { - name := "Poly1305 64 zero bytes" - options := &time.Benchmark_Options { - rounds = 1_000, - bytes = 64, - setup = _setup_sized_buf, - bench = _benchmark_poly1305, - teardown = _teardown_sized_buf, - } - - err := time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "Poly1305 1024 zero bytes" - options.bytes = 1024 - err = time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) -} - -bench_chacha20poly1305 :: proc(t: ^testing.T) { - name := "chacha20poly1305 64 bytes" - options := &time.Benchmark_Options { - rounds = 1_000, - bytes = 64, - setup = _setup_sized_buf, - bench = _benchmark_chacha20poly1305, - teardown = _teardown_sized_buf, - } - - err := time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "chacha20poly1305 1024 bytes" - options.bytes = 1024 - err = time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "chacha20poly1305 65536 bytes" - options.bytes = 65536 - err = time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) -} - -bench_aes256_gcm :: proc(t: ^testing.T) { - name := "AES256-GCM 64 bytes" - options := &time.Benchmark_Options { - rounds = 1_000, - bytes = 64, - setup = _setup_sized_buf, - bench = _benchmark_aes256_gcm, - teardown = _teardown_sized_buf, - } - - key := [aes.KEY_SIZE_256]byte { - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - } - ctx: aes.Context_GCM - aes.init_gcm(&ctx, key[:]) - - context.user_ptr = &ctx - - err := time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "AES256-GCM 1024 bytes" - options.bytes = 1024 - err = time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) - - name = "AES256-GCM 65536 bytes" - options.bytes = 65536 - err = time.benchmark(options, context.allocator) - testing.expect(t, err == nil, name) - benchmark_print(name, options) -} - -bench_ed25519 :: proc(t: ^testing.T) { - iters :: 10000 - - priv_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" - priv_bytes, _ := hex.decode(transmute([]byte)(priv_str), context.temp_allocator) - priv_key: ed25519.Private_Key - start := time.now() - for i := 0; i < iters; i = i + 1 { - ok := ed25519.private_key_set_bytes(&priv_key, priv_bytes) - assert(ok, "private key should deserialize") - } - elapsed := time.since(start) - log.infof( - "ed25519.private_key_set_bytes: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ) - - pub_bytes := priv_key._pub_key._b[:] // "I know what I am doing" - pub_key: ed25519.Public_Key - start = time.now() - for i := 0; i < iters; i = i + 1 { - ok := ed25519.public_key_set_bytes(&pub_key, pub_bytes[:]) - assert(ok, "public key should deserialize") - } - elapsed = time.since(start) - log.infof( - "ed25519.public_key_set_bytes: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ) - - msg := "Got a job for you, 621." - sig_bytes: [ed25519.SIGNATURE_SIZE]byte - msg_bytes := transmute([]byte)(msg) - start = time.now() - for i := 0; i < iters; i = i + 1 { - ed25519.sign(&priv_key, msg_bytes, sig_bytes[:]) - } - elapsed = time.since(start) - log.infof( - "ed25519.sign: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ) - - start = time.now() - for i := 0; i < iters; i = i + 1 { - ok := ed25519.verify(&pub_key, msg_bytes, sig_bytes[:]) - assert(ok, "signature should validate") - } - elapsed = time.since(start) - log.infof( - "ed25519.verify: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ) -} - -bench_x25519 :: proc(t: ^testing.T) { - point_str := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" - scalar_str := "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" - - point, _ := hex.decode(transmute([]byte)(point_str), context.temp_allocator) - scalar, _ := hex.decode(transmute([]byte)(scalar_str), context.temp_allocator) - out: [x25519.POINT_SIZE]byte = --- - - iters :: 10000 - start := time.now() - for i := 0; i < iters; i = i + 1 { - x25519.scalarmult(out[:], scalar[:], point[:]) - } - elapsed := time.since(start) - - log.infof( - "x25519.scalarmult: ~%f us/op", - time.duration_microseconds(elapsed) / iters, - ) -} diff --git a/tests/core/hash/test_core_hash.odin b/tests/core/hash/test_core_hash.odin index a6294de55..c332383e7 100644 --- a/tests/core/hash/test_core_hash.odin +++ b/tests/core/hash/test_core_hash.odin @@ -2,10 +2,7 @@ package test_core_hash import "core:hash/xxhash" import "core:hash" -import "core:time" import "core:testing" -import "core:fmt" -import "core:log" import "core:math/rand" import "base:intrinsics" @@ -78,15 +75,10 @@ test_xxhash_zero_streamed_random_updates :: proc(t: ^testing.T) { xxh3_64 := xxhash.XXH3_64_digest(xxh3_64_state) xxh3_128 := xxhash.XXH3_128_digest(xxh3_128_state) - xxh32_error := fmt.tprintf("[ XXH32(%03d) ] Expected: %08x, got: %08x", i, v.xxh_32, xxh32) - xxh64_error := fmt.tprintf("[ XXH64(%03d) ] Expected: %16x, got: %16x", i, v.xxh_64, xxh64) - xxh3_64_error := fmt.tprintf("[XXH3_64(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64) - xxh3_128_error := fmt.tprintf("[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128) - - testing.expect(t, xxh32 == v.xxh_32, xxh32_error) - testing.expect(t, xxh64 == v.xxh_64, xxh64_error) - testing.expect(t, xxh3_64 == v.xxh3_64, xxh3_64_error) - testing.expect(t, xxh3_128 == v.xxh3_128, xxh3_128_error) + testing.expectf(t, xxh32 == v.xxh_32, "[ XXH32(%03d) ] Expected: %08x, got: %08x", i, v.xxh_32, xxh32) + testing.expectf(t, xxh64 == v.xxh_64, "[ XXH64(%03d) ] Expected: %16x, got: %16x", i, v.xxh_64, xxh64) + testing.expectf(t, xxh3_64 == v.xxh3_64, "[XXH3_64(%03d) ] Expected: %16x, got: %16x", i, v.xxh3_64, xxh3_64) + testing.expectf(t, xxh3_128 == v.xxh3_128, "[XXH3_128(%03d) ] Expected: %32x, got: %32x", i, v.xxh3_128, xxh3_128) } } @@ -174,220 +166,4 @@ test_crc64_vectors :: proc(t: ^testing.T) { testing.expectf(t, iso == expected[2], "[ CRC-64 ISO 3306] Expected: %016x, got: %016x", expected[2], iso) testing.expectf(t, iso2 == expected[3], "[~CRC-64 ISO 3306] Expected: %016x, got: %016x", expected[3], iso2) } -} - -@(test) -test_benchmark_xxh32 :: proc(t: ^testing.T) { - name := "XXH32 100 zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 100, - setup = setup_xxhash, - bench = benchmark_xxh32, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0x85f6413c) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -@(test) -test_benchmark_xxh32_1MB :: proc(t: ^testing.T) { - name := "XXH32 1 MiB zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 1_048_576, - setup = setup_xxhash, - bench = benchmark_xxh32, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0x9430f97f) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -@(test) -test_benchmark_xxh64 :: proc(t: ^testing.T) { - name := "XXH64 100 zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 100, - setup = setup_xxhash, - bench = benchmark_xxh64, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0x17bb1103c92c502f) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -@(test) -test_benchmark_xxh64_1MB :: proc(t: ^testing.T) { - name := "XXH64 1 MiB zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 1_048_576, - setup = setup_xxhash, - bench = benchmark_xxh64, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0x87d2a1b6e1163ef1) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -@(test) -test_benchmark_xxh3_64 :: proc(t: ^testing.T) { - name := "XXH3_64 100 zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 100, - setup = setup_xxhash, - bench = benchmark_xxh3_64, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0x801fedc74ccd608c) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -@(test) -test_benchmark_xxh3_64_1MB :: proc(t: ^testing.T) { - name := "XXH3_64 1 MiB zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 1_048_576, - setup = setup_xxhash, - bench = benchmark_xxh3_64, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0x918780b90550bf34) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -@(test) -test_benchmark_xxh3_128 :: proc(t: ^testing.T) { - name := "XXH3_128 100 zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 100, - setup = setup_xxhash, - bench = benchmark_xxh3_128, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0x6ba30a4e9dffe1ff801fedc74ccd608c) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -@(test) -test_benchmark_xxh3_128_1MB :: proc(t: ^testing.T) { - name := "XXH3_128 1 MiB zero bytes" - options := &time.Benchmark_Options{ - rounds = 1_000, - bytes = 1_048_576, - setup = setup_xxhash, - bench = benchmark_xxh3_128, - teardown = teardown_xxhash, - } - err := time.benchmark(options, context.allocator) - testing.expectf(t, err == nil, "%s failed with err %v", name, err) - hash := u128(0xb6ef17a3448492b6918780b90550bf34) - testing.expectf(t, options.hash == hash, "%v hash expected to be %v, got %v", name, hash, options.hash) - benchmark_print(name, options) -} - -// Benchmarks - -setup_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - assert(options != nil) - - options.input = make([]u8, options.bytes, allocator) - return nil if len(options.input) == options.bytes else .Allocation_Error -} - -teardown_xxhash :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - assert(options != nil) - - delete(options.input) - return nil -} - -benchmark_xxh32 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - buf := options.input - - h: u32 - for _ in 0..=options.rounds { - h = xxhash.XXH32(buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - options.hash = u128(h) - return nil -} - -benchmark_xxh64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - buf := options.input - - h: u64 - for _ in 0..=options.rounds { - h = xxhash.XXH64(buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - options.hash = u128(h) - return nil -} - -benchmark_xxh3_64 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - buf := options.input - - h: u64 - for _ in 0..=options.rounds { - h = xxhash.XXH3_64(buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - options.hash = u128(h) - return nil -} - -benchmark_xxh3_128 :: proc(options: ^time.Benchmark_Options, allocator := context.allocator) -> (err: time.Benchmark_Error) { - buf := options.input - - h: u128 - for _ in 0..=options.rounds { - h = xxhash.XXH3_128(buf) - } - options.count = options.rounds - options.processed = options.rounds * options.bytes - options.hash = h - return nil -} - -benchmark_print :: proc(name: string, options: ^time.Benchmark_Options, loc := #caller_location) { - log.infof("\n\t[%v] %v rounds, %v bytes processed in %v ns\n\t\t%5.3f rounds/s, %5.3f MiB/s", - name, - options.rounds, - options.processed, - time.duration_nanoseconds(options.duration), - options.rounds_per_second, - options.megabytes_per_second, - location=loc, - ) } \ No newline at end of file From 54dae06ad1d727a6607024cf7d118e53e9981289 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 20:19:17 +0200 Subject: [PATCH 091/270] Update CI --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67b2bfbda..7180fbd15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -261,7 +261,7 @@ jobs: cd tests\core\math\big call build.bat timeout-minutes: 10 - - name: core library benchmarks + - name: Core library benchmarks shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat From 3354212f8eda24f1807bc04247911db000c0159f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 31 May 2024 20:32:25 +0200 Subject: [PATCH 092/270] Update ci.yml Disable benchmark on Windows for a moment. --- .github/workflows/ci.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7180fbd15..2ee557846 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -261,13 +261,6 @@ jobs: cd tests\core\math\big call build.bat timeout-minutes: 10 - - name: Core library benchmarks - shell: cmd - run: | - call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - cd tests\benchmark - call build.bat - timeout-minutes: 10 - name: Odin check examples/all for Windows 32bits shell: cmd run: | From 306169699c6f59d910fc38e357124de6f93c78dd Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Jun 2024 13:07:40 +0200 Subject: [PATCH 093/270] Update ci.yml --- .github/workflows/ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ee557846..c5493775f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -233,6 +233,13 @@ jobs: cd tests\core call build.bat timeout-minutes: 10 + - name: Core library benchmarks + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat + cd tests\benchmark + call build.bat + timeout-minutes: 10 - name: Vendor library tests shell: cmd run: | From 6050bc3bf66f5b2159f339a1b62f855fd774b966 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 1 Jun 2024 13:17:59 +0200 Subject: [PATCH 094/270] Add missing benchmarks build.bat. --- tests/benchmark/build.bat | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/benchmark/build.bat diff --git a/tests/benchmark/build.bat b/tests/benchmark/build.bat new file mode 100644 index 000000000..e9051855b --- /dev/null +++ b/tests/benchmark/build.bat @@ -0,0 +1,13 @@ +@echo off +set COMMON=-no-bounds-check -vet -strict-style +set PATH_TO_ODIN==..\..\odin + +echo --- +echo Running core:crypto benchmarks +echo --- +%PATH_TO_ODIN% test crypto %COMMON% -o:speed -out:bench_crypto.exe || exit /b + +echo --- +echo Running core:hash benchmarks +echo --- +%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:bench_hash.exe || exit /b \ No newline at end of file From fb37572c4ce9d8058cd73c0b7c21c1ecd2fdad65 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 30 May 2024 17:02:32 -0400 Subject: [PATCH 095/270] Rename `signal_handler.odin` to `signal_handler_libc.odin` --- core/testing/{signal_handler.odin => signal_handler_libc.odin} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename core/testing/{signal_handler.odin => signal_handler_libc.odin} (100%) diff --git a/core/testing/signal_handler.odin b/core/testing/signal_handler_libc.odin similarity index 100% rename from core/testing/signal_handler.odin rename to core/testing/signal_handler_libc.odin From d1723664a79a5818e8b43dc43210270dfdcb7b92 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 30 May 2024 17:46:28 -0400 Subject: [PATCH 096/270] Catch `SIGILL`, `SIGFPE`, `SIGSEGV` in the test runner --- core/testing/runner.odin | 65 ++++++++++++- core/testing/signal_handler.odin | 33 +++++++ core/testing/signal_handler_libc.odin | 126 +++++++++++++++++++++++-- core/testing/signal_handler_other.odin | 12 ++- 4 files changed, 224 insertions(+), 12 deletions(-) create mode 100644 core/testing/signal_handler.odin diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 47ae5d528..63b3864dd 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -69,6 +69,8 @@ Task_Timeout :: struct { run_test_task :: proc(task: thread.Task) { data := cast(^Task_Data)(task.data) + setup_task_signal_handler(task.user_index) + chan.send(data.t.channel, Event_New_Test { test_index = task.user_index, }) @@ -76,6 +78,8 @@ run_test_task :: proc(task: thread.Task) { chan.send(data.t.channel, Event_State_Change { new_state = .Running, }) + + context.assertion_failure_proc = test_assertion_failure_proc context.logger = { procedure = test_logger_proc, @@ -389,6 +393,8 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE) when FANCY_OUTPUT { + signals_were_raised := false + redraw_report(stdout, report) draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count) } @@ -557,12 +563,57 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { } } - if should_abort() { + if should_stop_runner() { fmt.wprintln(stderr, "\nCaught interrupt signal. Stopping all tests.") thread.pool_shutdown(&pool) 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 + } + + if test_index, reason, ok := should_stop_test(); ok { + #no_bounds_check report.all_test_states[test_index] = .Failed + #no_bounds_check it := internal_tests[test_index] + #no_bounds_check pkg := report.packages_by_name[it.pkg] + pkg.frame_ready = false + + fmt.assertf(thread.pool_stop_task(&pool, test_index), + "A signal (%v) was raised to stop test #%i %s.%s, but it was unable to be found.", + reason, test_index, it.pkg, it.name) + + if test_index not_in failed_test_reason_map { + // We only write a new error message here if there wasn't one + // already, because the message we can provide based only on + // the signal won't be very useful, whereas asserts and panics + // will provide a user-written error message. + failed_test_reason_map[test_index] = fmt.aprintf("Signal caught: %v", reason, allocator = shared_log_allocator) + pkg_log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason) + + when FANCY_OUTPUT { + signals_were_raised = true + bypass_progress_overwrite = true + } + } + + total_failure_count += 1 + total_done_count += 1 + } + // -- Redraw. when FANCY_OUTPUT { @@ -570,7 +621,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { continue main_loop } - fmt.wprintf(stdout, ansi_redraw_string, total_done_count, total_test_count) + if !bypass_progress_overwrite { + fmt.wprintf(stdout, ansi_redraw_string, total_done_count, total_test_count) + } } else { if total_done_count != last_done_count { fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count) @@ -698,6 +751,14 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW) + when FANCY_OUTPUT { + 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. +To partly mitigate this, redirect STDERR to a file or use the -define:ODIN_TEST_FANCY=false option.`) + } + } + fmt.wprintln(stderr, bytes.buffer_to_string(&batch_buffer)) return total_success_count == total_test_count diff --git a/core/testing/signal_handler.odin b/core/testing/signal_handler.odin new file mode 100644 index 000000000..891f6bbb6 --- /dev/null +++ b/core/testing/signal_handler.odin @@ -0,0 +1,33 @@ +//+private +package testing + +import "base:runtime" +import pkg_log "core:log" + +Stop_Reason :: enum { + Unknown, + Illegal_Instruction, + Arithmetic_Error, + Segmentation_Fault, +} + +test_assertion_failure_proc :: proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! { + pkg_log.fatalf("%s: %s", prefix, message, location = loc) + runtime.trap() +} + +setup_signal_handler :: proc() { + _setup_signal_handler() +} + +setup_task_signal_handler :: proc(test_index: int) { + _setup_task_signal_handler(test_index) +} + +should_stop_runner :: proc() -> bool { + return _should_stop_runner() +} + +should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) { + return _should_stop_test() +} diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index 2dd0ff192..ff3dbe135 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -4,16 +4,126 @@ package testing import "base:intrinsics" import "core:c/libc" +import "core:encoding/ansi" +import "core:sync" + +@(private="file") stop_runner_flag: libc.sig_atomic_t + +@(private="file") stop_test_gate: sync.Mutex +@(private="file") stop_test_index: libc.sig_atomic_t +@(private="file") stop_test_reason: libc.sig_atomic_t +@(private="file") stop_test_alert: libc.sig_atomic_t + +@(private="file", thread_local) +local_test_index: libc.sig_atomic_t @(private="file") -abort_flag: libc.sig_atomic_t - -setup_signal_handler :: proc() { - libc.signal(libc.SIGINT, proc "c" (sig: libc.int) { - intrinsics.atomic_add(&abort_flag, 1) - }) +stop_runner_callback :: proc "c" (sig: libc.int) { + intrinsics.atomic_store(&stop_runner_flag, 1) } -should_abort :: proc() -> bool { - return intrinsics.atomic_load(&abort_flag) > 0 +@(private="file") +stop_test_callback :: proc "c" (sig: libc.int) { + if local_test_index == -1 { + // We're the test runner, and we ourselves have caught a signal from + // which there is no recovery. + // + // The most we can do now is make sure the user's cursor is visible, + // nuke the entire processs, and hope a useful core dump survives. + + // 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) + + // This is an attempt at being compliant by avoiding printf. + sigbuf: [8]byte + sigstr: string + { + signum := cast(int)sig + i := len(sigbuf) - 2 + for signum > 0 { + m := signum % 10 + signum /= 10 + sigbuf[i] = cast(u8)('0' + m) + i -= 1 + } + sigstr = cast(string)sigbuf[i:] + } + + advisory_a := ` +The test runner's main thread has caught an unrecoverable error (signal ` + advisory_b := `) and will now forcibly terminate. +This is a dire bug and should be reported to the Odin developers. +` + libc.fwrite(raw_data(advisory_a), size_of(byte), len(advisory_a), libc.stderr) + libc.fwrite(raw_data(sigstr), size_of(byte), len(sigstr), libc.stderr) + libc.fwrite(raw_data(advisory_b), size_of(byte), len(advisory_b), libc.stderr) + + // Try to get a core dump. + libc.abort() + } + + if sync.mutex_guard(&stop_test_gate) { + intrinsics.atomic_store(&stop_test_index, local_test_index) + intrinsics.atomic_store(&stop_test_reason, cast(libc.sig_atomic_t)sig) + intrinsics.atomic_store(&stop_test_alert, 1) + + for { + // Idle until this thread is terminated by the runner, + // otherwise we may continue to generate signals. + intrinsics.cpu_relax() + } + } +} + +_setup_signal_handler :: proc() { + local_test_index = -1 + + // Catch user interrupt / CTRL-C. + libc.signal(libc.SIGINT, stop_runner_callback) + // Catch polite termination request. + libc.signal(libc.SIGTERM, stop_runner_callback) + + // For tests: + // Catch asserts and panics. + libc.signal(libc.SIGILL, stop_test_callback) + // Catch arithmetic errors. + libc.signal(libc.SIGFPE, stop_test_callback) + // Catch segmentation faults (illegal memory access). + libc.signal(libc.SIGSEGV, stop_test_callback) +} + +_setup_task_signal_handler :: proc(test_index: int) { + local_test_index = cast(libc.sig_atomic_t)test_index +} + +_should_stop_runner :: proc() -> bool { + return intrinsics.atomic_load(&stop_runner_flag) == 1 +} + +@(private="file") +unlock_stop_test_gate :: proc(_: int, _: Stop_Reason, ok: bool) { + if ok { + sync.mutex_unlock(&stop_test_gate) + } +} + +@(deferred_out=unlock_stop_test_gate) +_should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) { + if intrinsics.atomic_load(&stop_test_alert) == 1 { + intrinsics.atomic_store(&stop_test_alert, 0) + + test_index = cast(int)intrinsics.atomic_load(&stop_test_index) + switch intrinsics.atomic_load(&stop_test_reason) { + case libc.SIGFPE: reason = .Arithmetic_Error + case libc.SIGILL: reason = .Illegal_Instruction + case libc.SIGSEGV: reason = .Segmentation_Fault + } + ok = true + } + + return } diff --git a/core/testing/signal_handler_other.odin b/core/testing/signal_handler_other.odin index b2e2ea906..8621349f0 100644 --- a/core/testing/signal_handler_other.odin +++ b/core/testing/signal_handler_other.odin @@ -2,10 +2,18 @@ //+build js, wasi, freestanding package testing -setup_signal_handler :: proc() { +_setup_signal_handler :: proc() { // Do nothing. } -should_abort :: proc() -> bool { +_setup_task_signal_handler :: proc(test_index: int) { + // Do nothing. +} + +_should_stop_runner :: proc() -> bool { return false } + +_should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool) { + return 0, {}, false +} From 433ca538bfccdfca9704942c4575b582b36db9ad Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Thu, 30 May 2024 18:01:22 -0400 Subject: [PATCH 097/270] Be specific about platforms not implementing test runner signal handler --- core/testing/signal_handler_other.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/testing/signal_handler_other.odin b/core/testing/signal_handler_other.odin index 8621349f0..04981f5af 100644 --- a/core/testing/signal_handler_other.odin +++ b/core/testing/signal_handler_other.odin @@ -1,5 +1,5 @@ //+private -//+build js, wasi, freestanding +//+build !windows !linux !darwin !freebsd !openbsd !netbsd !haiku package testing _setup_signal_handler :: proc() { From 6a5d51f0d6c82d350e9082c1132fd1f48720d4c0 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Fri, 31 May 2024 15:45:06 -0400 Subject: [PATCH 098/270] Use more concise way of satisfying `-vet` --- core/testing/runner.odin | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 63b3864dd..64c1ad760 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -5,23 +5,18 @@ import "base:intrinsics" import "base:runtime" import "core:bytes" import "core:encoding/ansi" -import "core:encoding/base64" +@require import "core:encoding/base64" import "core:fmt" import "core:io" -import pkg_log "core:log" +@require import pkg_log "core:log" import "core:mem" import "core:os" import "core:slice" -import "core:strings" +@require import "core:strings" import "core:sync/chan" import "core:thread" import "core:time" -// Keep `-vet` happy. -base64_encode :: base64.encode -_ :: pkg_log -_ :: strings - // Specify how many threads to use when running tests. TEST_THREADS : int : #config(ODIN_TEST_THREADS, 0) // Track the memory used by each test. @@ -728,7 +723,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { fmt.wprintf(clipboard_writer, "%s.%s,", it.pkg, it.name) } - encoded_names := base64_encode(bytes.buffer_to_bytes(&clipboard_buffer), allocator = context.temp_allocator) + encoded_names := base64.encode(bytes.buffer_to_bytes(&clipboard_buffer), allocator = context.temp_allocator) fmt.wprintf(batch_writer, ansi.OSC + ansi.CLIPBOARD + ";c;%s" + ansi.ST + From cb8faf5b74cd0863e226908d2bebd4829b71cbc8 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:04:20 -0400 Subject: [PATCH 099/270] Remove `-test-name` in favor of test runner option `-define:ODIN_TEST_NAMES=...` is capable of selecting test by package and name or name only, with the ability to access packages included by `-all-packages`. --- src/build_settings.cpp | 1 - src/checker.cpp | 29 ----------------------------- src/main.cpp | 24 ------------------------ 3 files changed, 54 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 376e56a8e..7bdec376b 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -893,7 +893,6 @@ struct BuildContext { u32 cmd_doc_flags; Array extra_packages; - StringSet test_names; bool test_all_packages; gbAffinity affinity; diff --git a/src/checker.cpp b/src/checker.cpp index 2fd274975..97e685d33 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -5852,35 +5852,6 @@ gb_internal void remove_neighbouring_duplicate_entires_from_sorted_array(Arrayinfo.testing_procedures, init_procedures_cmp); remove_neighbouring_duplicate_entires_from_sorted_array(&c->info.testing_procedures); - - if (build_context.test_names.entries.count == 0) { - return; - } - - AstPackage *pkg = c->info.init_package; - Scope *s = pkg->scope; - - for (String const &name : build_context.test_names) { - Entity *e = scope_lookup(s, name); - if (e == nullptr) { - Token tok = {}; - if (pkg->files.count != 0) { - tok = pkg->files[0]->tokens[0]; - } - error(tok, "Unable to find the test '%.*s' in 'package %.*s' ", LIT(name), LIT(pkg->name)); - } - } - - for (isize i = 0; i < c->info.testing_procedures.count; /**/) { - Entity *e = c->info.testing_procedures[i]; - String name = e->token.string; - if (!string_set_exists(&build_context.test_names, name)) { - array_ordered_remove(&c->info.testing_procedures, i); - } else { - i += 1; - } - } - } diff --git a/src/main.cpp b/src/main.cpp index 4df6f97d5..e7f076c7c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -276,8 +276,6 @@ enum BuildFlagKind { BuildFlag_RelocMode, BuildFlag_DisableRedZone, - BuildFlag_TestName, - BuildFlag_DisallowDo, BuildFlag_DefaultToNilAllocator, BuildFlag_DefaultToPanicAllocator, @@ -471,8 +469,6 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_RelocMode, str_lit("reloc-mode"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_DisableRedZone, str_lit("disable-red-zone"), BuildFlagParam_None, Command__does_build); - add_flag(&build_flags, BuildFlag_TestName, str_lit("test-name"), BuildFlagParam_String, Command_test); - add_flag(&build_flags, BuildFlag_DisallowDo, str_lit("disallow-do"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_DefaultToNilAllocator, str_lit("default-to-nil-allocator"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_DefaultToPanicAllocator, str_lit("default-to-panic-allocator"),BuildFlagParam_None, Command__does_check); @@ -1119,21 +1115,6 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_DisableRedZone: build_context.disable_red_zone = true; break; - case BuildFlag_TestName: { - GB_ASSERT(value.kind == ExactValue_String); - { - String name = value.value_string; - if (!string_is_valid_identifier(name)) { - gb_printf_err("Test name '%.*s' must be a valid identifier\n", LIT(name)); - bad_flags = true; - break; - } - string_set_add(&build_context.test_names, name); - - // NOTE(bill): Allow for multiple -test-name - continue; - } - } case BuildFlag_DisallowDo: build_context.disallow_do = true; break; @@ -1962,10 +1943,6 @@ gb_internal void print_show_help(String const arg0, String const &command) { } if (test_only) { - print_usage_line(1, "-test-name:"); - print_usage_line(2, "Runs specific test only by name."); - print_usage_line(0, ""); - print_usage_line(1, "-all-packages"); print_usage_line(2, "Tests all packages imported into the given initial package."); print_usage_line(0, ""); @@ -2489,7 +2466,6 @@ int main(int arg_count, char const **arg_ptr) { TIME_SECTION("init args"); map_init(&build_context.defined_values); build_context.extra_packages.allocator = heap_allocator(); - string_set_init(&build_context.test_names); Array args = setup_args(arg_count, arg_ptr); From 45fa9d81487e89b88e4b5cd22803be45ea341f85 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:12:51 -0400 Subject: [PATCH 100/270] Expand documentation comment for `ODIN_TEST_NAMES` --- core/testing/runner.odin | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 64c1ad760..9027750da 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -26,6 +26,9 @@ ALWAYS_REPORT_MEMORY : bool : #config(ODIN_TEST_ALWAYS_REPORT_MEMORY, false) // Specify how much memory each thread allocator starts with. PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE) // Select a specific set of tests to run by name. +// Each test is separated by a comma and may optionally include the package name. +// This may be useful when running tests on multiple packages with `-all-packages`. +// The format is: `package.test_name,test_name_only,...` TEST_NAMES : string : #config(ODIN_TEST_NAMES, "") // Show the fancy animated progress report. FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true) From 21064fbb60ac9b3a838b100e4718e1e66dfdf80f Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:15:27 -0400 Subject: [PATCH 101/270] Clear thread pool task data on restart --- core/thread/thread_pool.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 53f89ea0c..6d80cb619 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -180,6 +180,7 @@ pool_stop_task :: proc(pool: ^Pool, user_index: int, exit_code: int = 1) -> bool replacement := create(pool_thread_runner) replacement.user_index = t.user_index replacement.data = data + data.task = {} pool.threads[i] = replacement start(replacement) @@ -211,6 +212,7 @@ pool_stop_all_tasks :: proc(pool: ^Pool, exit_code: int = 1) { replacement := create(pool_thread_runner) replacement.user_index = t.user_index replacement.data = data + data.task = {} pool.threads[i] = replacement start(replacement) From 9dcf3457952c32e34baa9136c5c4163cb4a55ceb Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:16:14 -0400 Subject: [PATCH 102/270] Set thread pool `is_running` to false on shutdown --- core/thread/thread_pool.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 6d80cb619..da5e116ff 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -225,6 +225,7 @@ pool_stop_all_tasks :: proc(pool: ^Pool, exit_code: int = 1) { // // The pool must still be destroyed after this. pool_shutdown :: proc(pool: ^Pool, exit_code: int = 1) { + intrinsics.atomic_store(&pool.is_running, false) sync.guard(&pool.mutex) for t in pool.threads { From ccdbd4b6cecfb5712edac97d9b22cc37c141a542 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:30:02 -0400 Subject: [PATCH 103/270] Simplify casts in `mem.Rollback_Stack` procs --- core/mem/rollback_stack_allocator.odin | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/mem/rollback_stack_allocator.odin b/core/mem/rollback_stack_allocator.odin index d159897cd..f5e428d87 100644 --- a/core/mem/rollback_stack_allocator.odin +++ b/core/mem/rollback_stack_allocator.odin @@ -63,9 +63,9 @@ Rollback_Stack :: struct { @(private="file", require_results) rb_ptr_in_bounds :: proc(block: ^Rollback_Stack_Block, ptr: rawptr) -> bool { - start := cast(uintptr)raw_data(block.buffer) - end := cast(uintptr)raw_data(block.buffer) + block.offset - return start < cast(uintptr)ptr && cast(uintptr)ptr <= end + start := raw_data(block.buffer) + end := start[block.offset:] + return start < ptr && ptr <= end } @(private="file", require_results) @@ -105,8 +105,8 @@ rb_rollback_block :: proc(block: ^Rollback_Stack_Block, header: ^Rollback_Stack_ header := header for block.offset > 0 && header.is_free { block.offset = header.prev_offset - block.last_alloc = cast(rawptr)(cast(uintptr)raw_data(block.buffer) + cast(uintptr)header.prev_ptr) - header = cast(^Rollback_Stack_Header)(cast(uintptr)raw_data(block.buffer) + cast(uintptr)header.prev_ptr - size_of(Rollback_Stack_Header)) + block.last_alloc = raw_data(block.buffer)[header.prev_ptr:] + header = cast(^Rollback_Stack_Header)(raw_data(block.buffer)[header.prev_ptr - size_of(Rollback_Stack_Header):]) } } @@ -189,8 +189,8 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt } } - start := cast(uintptr)raw_data(block.buffer) + block.offset - padding := cast(uintptr)calc_padding_with_header(start, cast(uintptr)alignment, size_of(Rollback_Stack_Header)) + start := raw_data(block.buffer)[block.offset:] + padding := cast(uintptr)calc_padding_with_header(cast(uintptr)start, cast(uintptr)alignment, size_of(Rollback_Stack_Header)) if block.offset + padding + cast(uintptr)size > cast(uintptr)len(block.buffer) { when !ODIN_DISABLE_ASSERT { @@ -202,8 +202,8 @@ rb_alloc :: proc(stack: ^Rollback_Stack, size, alignment: int) -> (result: []byt continue } - header := cast(^Rollback_Stack_Header)(start + cast(uintptr)padding - size_of(Rollback_Stack_Header)) - ptr := (cast([^]byte)(start + cast(uintptr)padding)) + header := cast(^Rollback_Stack_Header)(start[padding - size_of(Rollback_Stack_Header):]) + ptr := start[padding:] header^ = { prev_offset = block.offset, From 4875f745c832119e634197a7eb6403a30f82a95b Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:42:23 -0400 Subject: [PATCH 104/270] Remove Windows test runner in favor of `libc` implementation --- core/testing/runner.odin | 2 +- core/testing/runner_other.odin | 8 - core/testing/runner_windows.odin | 242 ------------------------------- 3 files changed, 1 insertion(+), 251 deletions(-) delete mode 100644 core/testing/runner_other.odin delete mode 100644 core/testing/runner_windows.odin diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 9027750da..8b156eb22 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -88,7 +88,7 @@ run_test_task :: proc(task: thread.Task) { free_all(context.temp_allocator) - run_internal_test(&data.t, data.it) + data.it.p(&data.t) end_t(&data.t) diff --git a/core/testing/runner_other.odin b/core/testing/runner_other.odin deleted file mode 100644 index 29f338828..000000000 --- a/core/testing/runner_other.odin +++ /dev/null @@ -1,8 +0,0 @@ -//+private -//+build !windows -package testing - -run_internal_test :: proc(t: ^T, it: Internal_Test) { - // TODO(bill): Catch panics on other platforms - it.p(t) -} diff --git a/core/testing/runner_windows.odin b/core/testing/runner_windows.odin deleted file mode 100644 index 23dab4f31..000000000 --- a/core/testing/runner_windows.odin +++ /dev/null @@ -1,242 +0,0 @@ -//+private -//+build windows -package testing - -run_internal_test :: proc(t: ^T, it: Internal_Test) { - it.p(t) -} - -// Temporarily disabled during multi-threaded test runner refactor. -/* -import win32 "core:sys/windows" -import "base:runtime" -import "base:intrinsics" -import "core:time" - -Sema :: struct { - count: i32, -} - -sema_reset :: proc "contextless" (s: ^Sema) { - intrinsics.atomic_store(&s.count, 0) -} -sema_wait :: proc "contextless" (s: ^Sema) { - for { - original_count := s.count - for original_count == 0 { - win32.WaitOnAddress(&s.count, &original_count, size_of(original_count), win32.INFINITE) - original_count = s.count - } - if original_count == intrinsics.atomic_compare_exchange_strong(&s.count, original_count-1, original_count) { - return - } - } -} -sema_wait_with_timeout :: proc "contextless" (s: ^Sema, duration: time.Duration) -> bool { - if duration <= 0 { - return false - } - for { - - original_count := intrinsics.atomic_load(&s.count) - for start := time.tick_now(); original_count == 0; /**/ { - if intrinsics.atomic_load(&s.count) != original_count { - remaining := duration - time.tick_since(start) - if remaining < 0 { - return false - } - ms := u32(remaining/time.Millisecond) - if !win32.WaitOnAddress(&s.count, &original_count, size_of(original_count), ms) { - return false - } - } - original_count = s.count - } - if original_count == intrinsics.atomic_compare_exchange_strong(&s.count, original_count-1, original_count) { - return true - } - } -} - -sema_post :: proc "contextless" (s: ^Sema, count := 1) { - intrinsics.atomic_add(&s.count, i32(count)) - if count == 1 { - win32.WakeByAddressSingle(&s.count) - } else { - win32.WakeByAddressAll(&s.count) - } -} - - - -Thread_Proc :: #type proc(^Thread) - -MAX_USER_ARGUMENTS :: 8 - -Thread :: struct { - using specific: Thread_Os_Specific, - procedure: Thread_Proc, - - t: ^T, - it: Internal_Test, - success: bool, - - init_context: Maybe(runtime.Context), - - creation_allocator: runtime.Allocator, - - internal_fail_timeout: time.Duration, - internal_fail_timeout_loc: runtime.Source_Code_Location, -} - -Thread_Os_Specific :: struct { - win32_thread: win32.HANDLE, - win32_thread_id: win32.DWORD, - done: bool, // see note in `is_done` -} - -thread_create :: proc(procedure: Thread_Proc) -> ^Thread { - __windows_thread_entry_proc :: proc "system" (t_: rawptr) -> win32.DWORD { - t := (^Thread)(t_) - context = t.init_context.? or_else runtime.default_context() - - t.procedure(t) - - if t.init_context == nil { - if context.temp_allocator.data == &runtime.global_default_temp_allocator_data { - runtime.default_temp_allocator_destroy(auto_cast context.temp_allocator.data) - } - } - - intrinsics.atomic_store(&t.done, true) - return 0 - } - - - thread := new(Thread) - if thread == nil { - return nil - } - thread.creation_allocator = context.allocator - - win32_thread_id: win32.DWORD - win32_thread := win32.CreateThread(nil, 0, __windows_thread_entry_proc, thread, win32.CREATE_SUSPENDED, &win32_thread_id) - if win32_thread == nil { - free(thread, thread.creation_allocator) - return nil - } - thread.procedure = procedure - thread.win32_thread = win32_thread - thread.win32_thread_id = win32_thread_id - thread.init_context = context - - return thread -} - -thread_start :: proc "contextless" (thread: ^Thread) { - win32.ResumeThread(thread.win32_thread) -} - -thread_join_and_destroy :: proc(thread: ^Thread) { - if thread.win32_thread != win32.INVALID_HANDLE { - win32.WaitForSingleObject(thread.win32_thread, win32.INFINITE) - win32.CloseHandle(thread.win32_thread) - thread.win32_thread = win32.INVALID_HANDLE - } - free(thread, thread.creation_allocator) -} - -thread_terminate :: proc "contextless" (thread: ^Thread, exit_code: int) { - win32.TerminateThread(thread.win32_thread, u32(exit_code)) -} - - -_fail_timeout :: proc(t: ^T, duration: time.Duration, loc := #caller_location) { - assert(global_fail_timeout_thread == nil, "set_fail_timeout previously called", loc) - - thread := thread_create(proc(thread: ^Thread) { - t := thread.t - timeout := thread.internal_fail_timeout - if !sema_wait_with_timeout(&global_fail_timeout_semaphore, timeout) { - fail_now(t, "TIMEOUT", thread.internal_fail_timeout_loc) - } - }) - thread.internal_fail_timeout = duration - thread.internal_fail_timeout_loc = loc - thread.t = t - global_fail_timeout_thread = thread - thread_start(thread) -} - -global_fail_timeout_thread: ^Thread -global_fail_timeout_semaphore: Sema - -global_threaded_runner_semaphore: Sema -global_exception_handler: rawptr -global_current_thread: ^Thread -global_current_t: ^T - -run_internal_test :: proc(t: ^T, it: Internal_Test) { - thread := thread_create(proc(thread: ^Thread) { - exception_handler_proc :: proc "system" (ExceptionInfo: ^win32.EXCEPTION_POINTERS) -> win32.LONG { - switch ExceptionInfo.ExceptionRecord.ExceptionCode { - case - win32.EXCEPTION_DATATYPE_MISALIGNMENT, - win32.EXCEPTION_BREAKPOINT, - win32.EXCEPTION_ACCESS_VIOLATION, - win32.EXCEPTION_ILLEGAL_INSTRUCTION, - win32.EXCEPTION_ARRAY_BOUNDS_EXCEEDED, - win32.EXCEPTION_STACK_OVERFLOW: - - sema_post(&global_threaded_runner_semaphore) - return win32.EXCEPTION_EXECUTE_HANDLER - } - - return win32.EXCEPTION_CONTINUE_SEARCH - } - global_exception_handler = win32.AddVectoredExceptionHandler(0, exception_handler_proc) - - context.assertion_failure_proc = proc(prefix, message: string, loc: runtime.Source_Code_Location) -> ! { - errorf(global_current_t, "%s %s", prefix, message, loc=loc) - intrinsics.trap() - } - - t := thread.t - - global_fail_timeout_thread = nil - sema_reset(&global_fail_timeout_semaphore) - - thread.it.p(t) - - sema_post(&global_fail_timeout_semaphore) - if global_fail_timeout_thread != nil do thread_join_and_destroy(global_fail_timeout_thread) - - thread.success = true - sema_post(&global_threaded_runner_semaphore) - }) - - sema_reset(&global_threaded_runner_semaphore) - global_current_t = t - - t._fail_now = proc() -> ! { - intrinsics.trap() - } - - thread.t = t - thread.it = it - thread.success = false - thread_start(thread) - - sema_wait(&global_threaded_runner_semaphore) - thread_terminate(thread, int(!thread.success)) - thread_join_and_destroy(thread) - - win32.RemoveVectoredExceptionHandler(global_exception_handler) - - if !thread.success && t.error_count == 0 { - t.error_count += 1 - } - - return -} -*/ From cb00b8022b5041b93a4b066e4b27c2a0368a377c Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 07:56:28 -0400 Subject: [PATCH 105/270] Add note about `SIGSEGV` edge case on UNIX-likes --- core/testing/runner.odin | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 8b156eb22..24b902005 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -659,6 +659,13 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { // -- All tests are complete, or the runner has been interrupted. + // NOTE(Feoramund): If you've arrived here after receiving signal 11 or + // SIGSEGV on the main runner thread, while using a UNIX-like platform, + // there is the possibility that you may have encountered a rare edge case + // involving the joining of threads. + // + // At the time of writing, the thread library is undergoing a rewrite that + // should solve this problem; it is not an issue with the test runner itself. thread.pool_join(&pool) finished_in := time.since(start_time) From 5db65aa796ef5dc2bfecf3a74d718e43ef5b40d0 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 08:16:40 -0400 Subject: [PATCH 106/270] Make it easier to learn about `ODIN_TEST_CLIPBOARD` --- core/testing/runner.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 24b902005..ddc01c919 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -748,6 +748,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { #no_bounds_check it := internal_tests[test_index] fmt.wprintf(batch_writer, "%s.%s,", it.pkg, it.name) } + fmt.wprint(batch_writer, "\n\nIf your terminal supports OSC 52, you may use -define:ODIN_TEST_CLIPBOARD to have this copied directly to your clipboard.") } fmt.wprintln(batch_writer) From 6a9203328bfd34c59abeb46d61f1cb5d238b24a7 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 14:03:36 -0400 Subject: [PATCH 107/270] Log thread count at test run start Provides a helpful info message about the option to change how many threads are used per run. --- core/testing/runner.odin | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index ddc01c919..4a45dabd1 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -397,6 +397,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count) } + when TEST_THREADS == 0 { + pkg_log.infof("Starting test runner with %i thread%s. Set with -define:ODIN_TEST_THREADS=n.", + thread_count, + "" if thread_count == 1 else "s") + } else { + pkg_log.infof("Starting test runner with %i thread%s.", + thread_count, + "" if thread_count == 1 else "s") + } + pkg_log.infof("The random seed sent to every test is: %v", shared_random_seed) when TRACKING_MEMORY { From 5e3e958574a7654a70382fb04cea2212f8652ff0 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 14:04:49 -0400 Subject: [PATCH 108/270] Add `-define:ODIN_TEST_LOG_LEVEL` to set lowest log level --- 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 4a45dabd1..dcc7c10ce 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -39,8 +39,23 @@ PROGRESS_WIDTH : int : #config(ODIN_TEST_PROGRESS_WIDTH, 24) // This is the random seed that will be sent to each test. // If it is unspecified, it will be set to the system cycle counter at startup. SHARED_RANDOM_SEED : u64 : #config(ODIN_TEST_RANDOM_SEED, 0) +// Set the lowest log level for this test run. +LOG_LEVEL : string : #config(ODIN_TEST_LOG_LEVEL, "info") +get_log_level :: #force_inline proc() -> runtime.Logger_Level { + when ODIN_DEBUG { + // Always use .Debug in `-debug` mode. + return .Debug + } else { + when LOG_LEVEL == "debug" { return .Debug } + else when LOG_LEVEL == "info" { return .Info } + else when LOG_LEVEL == "warning" { return .Warning } + else when LOG_LEVEL == "error" { return .Error } + else when LOG_LEVEL == "fatal" { return .Fatal } + } +} + end_t :: proc(t: ^T) { for i := len(t.cleanups)-1; i >= 0; i -= 1 { #no_bounds_check c := t.cleanups[i] @@ -82,7 +97,7 @@ run_test_task :: proc(task: thread.Task) { context.logger = { procedure = test_logger_proc, data = &data.t, - lowest_level = .Debug if ODIN_DEBUG else .Info, + lowest_level = get_log_level(), options = Default_Test_Logger_Opts, } @@ -355,7 +370,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { context.logger = { procedure = runner_logger_proc, data = &log_messages, - lowest_level = .Debug if ODIN_DEBUG else .Info, + lowest_level = get_log_level(), options = Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}, } From d581dbbec52a7ddcb8dbc986240a5d5092659450 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 14:28:02 -0400 Subject: [PATCH 109/270] Keep test runner main thread from using 100% of a CPU core --- core/testing/runner.odin | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index dcc7c10ce..fbe413bb2 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -439,6 +439,23 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { thread.pool_start(&pool) main_loop: for !thread.pool_is_empty(&pool) { + { + events_pending := thread.pool_num_done(&pool) > 0 + + if !events_pending { + poll_tasks: for &task_channel in task_channels { + if chan.len(task_channel.channel) > 0 { + events_pending = true + break poll_tasks + } + } + } + + if !events_pending { + // Keep the main thread from pegging a core at 100% usage. + time.sleep(1 * time.Microsecond) + } + } cycle_pool: for task in thread.pool_pop_done(&pool) { data := cast(^Task_Data)(task.data) From 890fe07c6e7e1e52407e458ea125d4d29b171c60 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 1 Jun 2024 19:59:20 -0400 Subject: [PATCH 110/270] Disable `FANCY_OUTPUT` in Odin test scripts This should tidy up the CI output logs a bit. --- tests/benchmark/Makefile | 2 +- tests/benchmark/build.bat | 4 ++-- tests/core/Makefile | 2 +- tests/core/build.bat | 2 +- tests/core/math/big/build.bat | 2 +- tests/issues/run.bat | 2 +- tests/issues/run.sh | 2 +- tests/vendor/build.bat | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/benchmark/Makefile b/tests/benchmark/Makefile index 8a8e08b24..840174dbb 100644 --- a/tests/benchmark/Makefile +++ b/tests/benchmark/Makefile @@ -1,5 +1,5 @@ ODIN=../../odin -COMMON=-no-bounds-check -vet -strict-style +COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false all: crypto_bench \ hash_bench diff --git a/tests/benchmark/build.bat b/tests/benchmark/build.bat index e9051855b..30b030066 100644 --- a/tests/benchmark/build.bat +++ b/tests/benchmark/build.bat @@ -1,5 +1,5 @@ @echo off -set COMMON=-no-bounds-check -vet -strict-style +set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false set PATH_TO_ODIN==..\..\odin echo --- @@ -10,4 +10,4 @@ echo --- echo --- echo Running core:hash benchmarks echo --- -%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:bench_hash.exe || exit /b \ No newline at end of file +%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:bench_hash.exe || exit /b diff --git a/tests/core/Makefile b/tests/core/Makefile index 9b12f5d76..85f3783b4 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -1,6 +1,6 @@ ODIN=../../odin PYTHON=$(shell which python3) -COMMON=-no-bounds-check -vet -strict-style +COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false all: all_bsd \ net_test diff --git a/tests/core/build.bat b/tests/core/build.bat index 3e67b6e13..67ac10f86 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -1,5 +1,5 @@ @echo off -set COMMON=-no-bounds-check -vet -strict-style +set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false set PATH_TO_ODIN==..\..\odin python3 download_assets.py echo --- diff --git a/tests/core/math/big/build.bat b/tests/core/math/big/build.bat index ad199d775..54b715a4f 100644 --- a/tests/core/math/big/build.bat +++ b/tests/core/math/big/build.bat @@ -5,7 +5,7 @@ set TEST_ARGS=-fast-tests set TEST_ARGS=-no-random set TEST_ARGS= set OUT_NAME=math_big_test_library.dll -set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style +set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style -define:ODIN_TEST_FANCY=false echo --- echo Running core:math/big tests echo --- diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 41c52c02f..cd23faefb 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -3,7 +3,7 @@ if not exist "build\" mkdir build pushd build -set COMMON=-collection:tests=..\.. +set COMMON=-collection:tests=..\.. -define:ODIN_TEST_FANCY=false @echo on diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 6d53388a7..a7eee8514 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -4,7 +4,7 @@ set -eu mkdir -p build pushd build ODIN=../../../odin -COMMON="-collection:tests=../.." +COMMON="-collection:tests=../.. -define:ODIN_TEST_FANCY=false" NO_NIL_ERR="Error: " diff --git a/tests/vendor/build.bat b/tests/vendor/build.bat index 693d344f4..1ba53615a 100644 --- a/tests/vendor/build.bat +++ b/tests/vendor/build.bat @@ -1,5 +1,5 @@ @echo off -set COMMON=-show-timings -no-bounds-check -vet -strict-style +set COMMON=-show-timings -no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false set PATH_TO_ODIN==..\..\odin echo --- From 21a1ddfbae92fb78bf536f4047016d0001b76cc2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 Jun 2024 21:03:22 +0200 Subject: [PATCH 111/270] Disable NetBSD tests until 'undefined reference to stdout' is solved. --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c5493775f..36914b41b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,10 +31,6 @@ jobs: ./odin version ./odin report ./odin check examples/all -vet -strict-style -target:netbsd_amd64 - (cd tests/core; gmake all_bsd) - (cd tests/internal; gmake all_bsd) - (cd tests/issues; ./run.sh) - (cd tests/benchmark; gmake all) build_linux: name: Ubuntu Build, Check, and Test runs-on: ubuntu-latest From 9d8d864400f49e84076fbea843ebf0435d61721a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 Jun 2024 21:12:24 +0200 Subject: [PATCH 112/270] Plug leak in AES tests. --- tests/core/crypto/test_core_crypto_aes.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/core/crypto/test_core_crypto_aes.odin b/tests/core/crypto/test_core_crypto_aes.odin index 8b96b0ae9..4d4c06bdc 100644 --- a/tests/core/crypto/test_core_crypto_aes.odin +++ b/tests/core/crypto/test_core_crypto_aes.odin @@ -15,6 +15,7 @@ test_aes :: proc(t: ^testing.T) { log.info("Testing AES") impls := make([dynamic]aes.Implementation, 0, 2) + defer delete(impls) append(&impls, aes.Implementation.Portable) if aes.is_hardware_accelerated() { append(&impls, aes.Implementation.Hardware) From 60d0c03134dbb65692a0e6208068cf1fb64960d8 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 2 Jun 2024 21:15:25 +0200 Subject: [PATCH 113/270] Strip old test runner back out of `internal`, `issues` and `vendor` --- tests/internal/Makefile | 17 +- tests/internal/build.bat | 15 +- tests/internal/test_128.odin | 50 +--- tests/internal/test_asan.odin | 45 +--- tests/internal/test_map.odin | 162 ++++--------- tests/internal/test_pow.odin | 48 +--- tests/internal/test_rtti.odin | 69 +----- tests/internal/test_string_compare.odin | 60 +---- tests/issues/run.bat | 20 +- tests/issues/run.sh | 22 +- tests/issues/test_issue_1592.odin | 293 ++++++++++++------------ tests/issues/test_issue_2056.odin | 5 +- tests/issues/test_issue_2466.odin | 5 +- tests/issues/test_issue_829.odin | 7 +- tests/vendor/build.bat | 2 +- tests/vendor/glfw/test_vendor_glfw.odin | 44 +--- 16 files changed, 282 insertions(+), 582 deletions(-) diff --git a/tests/internal/Makefile b/tests/internal/Makefile index 09182cd23..fb22767d2 100644 --- a/tests/internal/Makefile +++ b/tests/internal/Makefile @@ -1,23 +1,22 @@ ODIN=../../odin +COMMON=-file -vet -strict-style -o:minimal -all: all_bsd asan_test - -all_bsd: rtti_test map_test pow_test 128_test string_compare_test +all: asan_test rtti_test map_test pow_test 128_test string_compare_test rtti_test: - $(ODIN) run test_rtti.odin -file -vet -strict-style -o:minimal + $(ODIN) test test_rtti.odin $(COMMON) map_test: - $(ODIN) run test_map.odin -file -vet -strict-style -o:minimal + $(ODIN) test test_map.odin $(COMMON) pow_test: - $(ODIN) run test_pow.odin -file -vet -strict-style -o:minimal + $(ODIN) test test_pow.odin $(COMMON) 128_test: - $(ODIN) run test_128.odin -file -vet -strict-style -o:minimal + $(ODIN) test test_128.odin $(COMMON) asan_test: - $(ODIN) run test_asan.odin -file -sanitize:address -debug + $(ODIN) test test_asan.odin $(COMMON) -sanitize:address -debug string_compare_test: - $(ODIN) run test_string_compare.odin -file -vet -strict-style -o:minimal + $(ODIN) test test_string_compare.odin $(COMMON) \ No newline at end of file diff --git a/tests/internal/build.bat b/tests/internal/build.bat index 29b3e36c3..edaa2684b 100644 --- a/tests/internal/build.bat +++ b/tests/internal/build.bat @@ -1,10 +1,9 @@ @echo off set PATH_TO_ODIN==..\..\odin -rem %PATH_TO_ODIN% run test_rtti.odin -file -vet -strict-style -o:minimal || exit /b -%PATH_TO_ODIN% run test_map.odin -file -vet -strict-style -o:minimal || exit /b -rem -define:SEED=42 -%PATH_TO_ODIN% run test_pow.odin -file -vet -strict-style -o:minimal || exit /b - -%PATH_TO_ODIN% run test_128.odin -file -vet -strict-style -o:minimal || exit /b - -%PATH_TO_ODIN% run test_string_compare.odin -file -vet -strict-style -o:minimal || exit /b \ No newline at end of file +set COMMON=-file -vet -strict-style -o:minimal +%PATH_TO_ODIN% test test_rtti.odin %COMMON% || exit /b +%PATH_TO_ODIN% test test_map.odin %COMMON% || exit /b +%PATH_TO_ODIN% test test_pow.odin %COMMON% || exit /b +%PATH_TO_ODIN% test test_asan.odin %COMMON% || exit /b +%PATH_TO_ODIN% test test_128.odin %COMMON% || exit /b +%PATH_TO_ODIN% test test_string_compare.odin %COMMON% || exit /b \ No newline at end of file diff --git a/tests/internal/test_128.odin b/tests/internal/test_128.odin index 11ef068ed..1aa7487b6 100644 --- a/tests/internal/test_128.odin +++ b/tests/internal/test_128.odin @@ -1,41 +1,7 @@ package test_128 -import "core:fmt" -import "core:os" import "core:testing" -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - test_128_align(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - @test test_128_align :: proc(t: ^testing.T) { Danger_Struct :: struct { @@ -45,15 +11,15 @@ test_128_align :: proc(t: ^testing.T) { list := [?]Danger_Struct{{0, 0}, {1, 0}, {2, 0}, {3, 0}} - expect(t, list[0].x == 0, fmt.tprintf("[0].x (%v) != 0", list[0].x)) - expect(t, list[0].y == 0, fmt.tprintf("[0].y (%v) != 0", list[0].y)) + testing.expectf(t, list[0].x == 0, "[0].x (%v) != 0", list[0].x) + testing.expectf(t, list[0].y == 0, "[0].y (%v) != 0", list[0].y) - expect(t, list[1].x == 1, fmt.tprintf("[1].x (%v) != 1", list[1].x)) - expect(t, list[1].y == 0, fmt.tprintf("[1].y (%v) != 0", list[1].y)) + testing.expectf(t, list[1].x == 1, "[1].x (%v) != 1", list[1].x) + testing.expectf(t, list[1].y == 0, "[1].y (%v) != 0", list[1].y) - expect(t, list[2].x == 2, fmt.tprintf("[2].x (%v) != 2", list[2].x)) - expect(t, list[2].y == 0, fmt.tprintf("[2].y (%v) != 0", list[2].y)) + testing.expectf(t, list[2].x == 2, "[2].x (%v) != 2", list[2].x) + testing.expectf(t, list[2].y == 0, "[2].y (%v) != 0", list[2].y) - expect(t, list[3].x == 3, fmt.tprintf("[3].x (%v) != 3", list[3].x)) - expect(t, list[3].y == 0, fmt.tprintf("[3].y (%v) != 0", list[3].y)) + testing.expectf(t, list[3].x == 3, "[3].x (%v) != 3", list[3].x) + testing.expectf(t, list[3].y == 0, "[3].y (%v) != 0", list[3].y) } diff --git a/tests/internal/test_asan.odin b/tests/internal/test_asan.odin index 2384ada76..66f3c6547 100644 --- a/tests/internal/test_asan.odin +++ b/tests/internal/test_asan.odin @@ -1,42 +1,7 @@ // Intended to contain code that would trigger asan easily if the abi was set up badly. package test_asan -import "core:fmt" import "core:testing" -import "core:os" - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - - test_12_bytes(&t) - test_12_bytes_two(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} @(test) test_12_bytes :: proc(t: ^testing.T) { @@ -45,9 +10,9 @@ test_12_bytes :: proc(t: ^testing.T) { } a, b, ok := internal() - expect(t, a == max(f32), fmt.tprintf("a (%v) != max(f32)", a)) - expect(t, b == 0, fmt.tprintf("b (%v) != 0", b)) - expect(t, ok, fmt.tprintf("ok (%v) != true", ok)) + testing.expectf(t, a == max(f32), "a (%v) != max(f32)", a) + testing.expectf(t, b == 0, "b (%v) != 0", b) + testing.expectf(t, ok, "ok (%v) != true", ok) } @(test) @@ -57,6 +22,6 @@ test_12_bytes_two :: proc(t: ^testing.T) { } a, b := internal() - expect(t, a == 100., fmt.tprintf("a (%v) != 100.", a)) - expect(t, b == max(int), fmt.tprintf("b (%v) != max(int)", b)) + testing.expectf(t, a == 100., "a (%v) != 100.", a) + testing.expectf(t, b == max(int), "b (%v) != max(int)", b) } diff --git a/tests/internal/test_map.odin b/tests/internal/test_map.odin index 7d1dbf470..fa13244ac 100644 --- a/tests/internal/test_map.odin +++ b/tests/internal/test_map.odin @@ -1,26 +1,22 @@ package test_internal_map -import "core:fmt" +import "core:log" import "base:intrinsics" import "core:math/rand" -import "core:mem" -import "core:os" import "core:testing" -seed: u64 - ENTRY_COUNTS := []int{11, 101, 1_001, 10_001, 100_001, 1_000_001} @test map_insert_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) for entries in ENTRY_COUNTS { - fmt.printf("[map_insert_random_key_value] Testing %v entries.\n", entries) + log.infof("Testing %v entries", entries) m: map[i64]i64 defer delete(m) unique_keys := 0 - r := rand.create(seed + seed_incr) + r := rand.create(t.seed + seed_incr) for _ in 0.. 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k])) + testing.expectf(t, false, "Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k]) } } seed_incr += 1 @@ -65,12 +61,12 @@ map_insert_random_key_value :: proc(t: ^testing.T) { map_update_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) for entries in ENTRY_COUNTS { - fmt.printf("[map_update_random_key_value] Testing %v entries.\n", entries) + log.infof("Testing %v entries", entries) m: map[i64]i64 defer delete(m) unique_keys := 0 - r := rand.create(seed + seed_incr) + r := rand.create(t.seed + seed_incr) for _ in 0.. 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k])) + testing.expectf(t, false, "Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k]) } } seed_incr += 1 @@ -127,12 +123,12 @@ map_update_random_key_value :: proc(t: ^testing.T) { map_delete_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) for entries in ENTRY_COUNTS { - fmt.printf("[map_delete_random_key_value] Testing %v entries.\n", entries) + log.infof("Testing %v entries", entries) m: map[i64]i64 defer delete(m) unique_keys := 0 - r := rand.create(seed + seed_incr) + r := rand.create(t.seed + seed_incr) for _ in 0.. 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected key present. Expected m[%v] to have been deleted, got %v", k, m[k])) + testing.expectf(t, false, "Unexpected key present. Expected m[%v] to have been deleted, got %v", k, m[k]) } } else { if k not_in m { num_fails += 1 if num_fails > 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Expected key not present. Expected m[%v] = %v", k, v)) + testing.expectf(t, false, "Expected key not present. Expected m[%v] = %v", k, v) } else if m[k] != v { num_fails += 1 if num_fails > 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k])) + testing.expectf(t, false, "Unexpected value. Expected m[%v] = %v, got %v", k, v, m[k]) } } } @@ -205,12 +201,12 @@ map_delete_random_key_value :: proc(t: ^testing.T) { set_insert_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) for entries in ENTRY_COUNTS { - fmt.printf("[set_insert_random_key_value] Testing %v entries.\n", entries) + log.infof("Testing %v entries", entries) m: map[i64]struct{} defer delete(m) unique_keys := 0 - r := rand.create(seed + seed_incr) + r := rand.create(t.seed + seed_incr) for _ in 0.. 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected value. Expected m[%v] to exist", k)) + testing.expectf(t, false, "Unexpected value. Expected m[%v] to exist", k) } } seed_incr += 1 @@ -252,12 +248,12 @@ set_insert_random_key_value :: proc(t: ^testing.T) { set_delete_random_key_value :: proc(t: ^testing.T) { seed_incr := u64(0) for entries in ENTRY_COUNTS { - fmt.printf("[set_delete_random_key_value] Testing %v entries.\n", entries) + log.infof("Testing %v entries", entries) m: map[i64]struct{} defer delete(m) unique_keys := 0 - r := rand.create(seed + seed_incr) + r := rand.create(t.seed + seed_incr) for _ in 0.. 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Unexpected key present. Expected m[%v] to have been deleted", k)) + testing.expectf(t, false, "Unexpected key present. Expected m[%v] to have been deleted", k) } } else { if k not_in m { num_fails += 1 if num_fails > 5 { - fmt.println("... and more") + log.info("... and more") break } - expect(t, false, fmt.tprintf("Expected key not present. Expected m[%v] to exist", k)) + testing.expectf(t, false, "Expected key not present. Expected m[%v] to exist", k) } } } seed_incr += 1 } -} - -// -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- - -main :: proc() { - t := testing.T{} - - // Allow tests to be repeatable - SEED :: #config(SEED, -1) - when SEED > 0 { - seed = u64(SEED) - } else { - seed = u64(intrinsics.read_cycle_counter()) - } - fmt.println("Initialized seed to", seed) - - mem_track_test(&t, map_insert_random_key_value) - mem_track_test(&t, map_update_random_key_value) - mem_track_test(&t, map_delete_random_key_value) - - mem_track_test(&t, set_insert_random_key_value) - mem_track_test(&t, set_delete_random_key_value) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - -mem_track_test :: proc(t: ^testing.T, test: proc(t: ^testing.T)) { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - test(t) - - expect(t, len(track.allocation_map) == 0, "Expected no leaks.") - expect(t, len(track.bad_free_array) == 0, "Expected no leaks.") - - for _, leak in track.allocation_map { - fmt.printf("%v leaked %v bytes\n", leak.location, leak.size) - } - for bad_free in track.bad_free_array { - fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) - } -} - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} +} \ No newline at end of file diff --git a/tests/internal/test_pow.odin b/tests/internal/test_pow.odin index 70b81258d..609087fb2 100644 --- a/tests/internal/test_pow.odin +++ b/tests/internal/test_pow.odin @@ -1,8 +1,7 @@ package test_internal_math_pow -import "core:fmt" +@(require) import "core:log" import "core:math" -import "core:os" import "core:testing" @test @@ -19,14 +18,14 @@ pow_test :: proc(t: ^testing.T) { // pow2_f64 returns the same float on all platforms because it isn't this stupid _v1 = 0h00000000_00000000 } - expect(t, _v1 == _v2, fmt.tprintf("Expected math.pow2_f64(%d) == math.pow(2, %d) (= %16x), got %16x", exp, exp, _v1, _v2)) + testing.expectf(t, _v1 == _v2, "Expected math.pow2_f64(%d) == math.pow(2, %d) (= %16x), got %16x", exp, exp, _v1, _v2) } { v1 := math.pow(2, f32(exp)) v2 := math.pow2_f32(exp) _v1 := transmute(u32)v1 _v2 := transmute(u32)v2 - expect(t, _v1 == _v2, fmt.tprintf("Expected math.pow2_f32(%d) == math.pow(2, %d) (= %08x), got %08x", exp, exp, _v1, _v2)) + testing.expectf(t, _v1 == _v2, "Expected math.pow2_f32(%d) == math.pow(2, %d) (= %08x), got %08x", exp, exp, _v1, _v2) } { v1 := math.pow(2, f16(exp)) @@ -36,46 +35,11 @@ pow_test :: proc(t: ^testing.T) { when ODIN_OS == .Darwin && ODIN_ARCH == .arm64 { if exp == -25 { - testing.logf(t, "skipping known test failure on darwin+arm64, Expected math.pow2_f16(-25) == math.pow(2, -25) (= 0000), got 0001") + log.info("skipping known test failure on darwin+arm64, Expected math.pow2_f16(-25) == math.pow(2, -25) (= 0000), got 0001") _v2 = 0 } } - - expect(t, _v1 == _v2, fmt.tprintf("Expected math.pow2_f16(%d) == math.pow(2, %d) (= %04x), got %04x", exp, exp, _v1, _v2)) + testing.expectf(t, _v1 == _v2, "Expected math.pow2_f16(%d) == math.pow(2, %d) (= %04x), got %04x", exp, exp, _v1, _v2) } } -} - -// -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- - -main :: proc() { - t := testing.T{} - - pow_test(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} +} \ No newline at end of file diff --git a/tests/internal/test_rtti.odin b/tests/internal/test_rtti.odin index 12f64462b..ec5508208 100644 --- a/tests/internal/test_rtti.odin +++ b/tests/internal/test_rtti.odin @@ -1,11 +1,8 @@ package test_internal_rtti import "core:fmt" -import "core:mem" -import "core:os" import "core:testing" - Buggy_Struct :: struct { a: int, b: bool, @@ -28,74 +25,22 @@ rtti_test :: proc(t: ^testing.T) { for v, i in g_b { checksum += (i+1) * int(v) } - expect(t, checksum == 0, fmt.tprintf("Expected g_b to be zero-initialized, got %v", g_b)) + testing.expectf(t, checksum == 0, "Expected g_b to be zero-initialized, got %v", g_b) } { checksum := 0 for v, i in l_b { checksum += (i+1) * int(v) } - expect(t, checksum == 0, fmt.tprintf("Expected l_b to be zero-initialized, got %v", l_b)) + testing.expectf(t, checksum == 0, "Expected l_b to be zero-initialized, got %v", l_b) } - expect(t, size_of(Buggy_Struct) == 40, fmt.tprintf("Expected size_of(Buggy_Struct) == 40, got %v", size_of(Buggy_Struct))) - expect(t, size_of(g_buggy) == 40, fmt.tprintf("Expected size_of(g_buggy) == 40, got %v", size_of(g_buggy))) - expect(t, size_of(l_buggy) == 40, fmt.tprintf("Expected size_of(l_buggy) == 40, got %v", size_of(l_buggy))) + testing.expectf(t, size_of(Buggy_Struct) == 40, "Expected size_of(Buggy_Struct) == 40, got %v", size_of(Buggy_Struct)) + testing.expectf(t, size_of(g_buggy) == 40, "Expected size_of(g_buggy) == 40, got %v", size_of(g_buggy)) + testing.expectf(t, size_of(l_buggy) == 40, "Expected size_of(l_buggy) == 40, got %v", size_of(l_buggy)) g_s := fmt.tprintf("%s", g_buggy) l_s := fmt.tprintf("%s", l_buggy) - expect(t, g_s == EXPECTED_REPR, fmt.tprintf("Expected fmt.tprintf(\"%%s\", g_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, g_s)) - expect(t, l_s == EXPECTED_REPR, fmt.tprintf("Expected fmt.tprintf(\"%%s\", l_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, l_s)) -} - -// -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- - -main :: proc() { - t := testing.T{} - - mem_track_test(&t, rtti_test) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - -mem_track_test :: proc(t: ^testing.T, test: proc(t: ^testing.T)) { - track: mem.Tracking_Allocator - mem.tracking_allocator_init(&track, context.allocator) - context.allocator = mem.tracking_allocator(&track) - - test(t) - - expect(t, len(track.allocation_map) == 0, "Expected no leaks.") - expect(t, len(track.bad_free_array) == 0, "Expected no leaks.") - - for _, leak in track.allocation_map { - fmt.printf("%v leaked %v bytes\n", leak.location, leak.size) - } - for bad_free in track.bad_free_array { - fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory) - } -} - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } + testing.expectf(t, g_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", g_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, g_s) + testing.expectf(t, l_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", l_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, l_s) } \ No newline at end of file diff --git a/tests/internal/test_string_compare.odin b/tests/internal/test_string_compare.odin index ff65b41c2..63b62cc96 100644 --- a/tests/internal/test_string_compare.odin +++ b/tests/internal/test_string_compare.odin @@ -1,7 +1,5 @@ package test_internal_string_compare -import "core:fmt" -import "core:os" import "core:testing" Op :: enum { Eq, Lt, Gt } @@ -29,65 +27,31 @@ string_compare :: proc(t: ^testing.T) { for res, op in v.res { switch op { case .Eq: - expect(t, (v.a == v.b) == res, fmt.tprintf("Expected cstring(\"%v\") == cstring(\"%v\") to be %v", v.a, v.b, res)) - expect(t, (s_a == s_b) == res, fmt.tprintf("Expected string(\"%v\") == string(\"%v\") to be %v", v.a, v.b, res)) + testing.expectf(t, (v.a == v.b) == res, "Expected cstring(\"%v\") == cstring(\"%v\") to be %v", v.a, v.b, res) + testing.expectf(t, (s_a == s_b) == res, "Expected string(\"%v\") == string(\"%v\") to be %v", v.a, v.b, res) // If a == b then a != b - expect(t, (v.a != v.b) == !res, fmt.tprintf("Expected cstring(\"%v\") != cstring(\"%v\") to be %v", v.a, v.b, !res)) - expect(t, (s_a != s_b) == !res, fmt.tprintf("Expected string(\"%v\") != string(\"%v\") to be %v", v.a, v.b, !res)) + testing.expectf(t, (v.a != v.b) == !res, "Expected cstring(\"%v\") != cstring(\"%v\") to be %v", v.a, v.b, !res) + testing.expectf(t, (s_a != s_b) == !res, "Expected string(\"%v\") != string(\"%v\") to be %v", v.a, v.b, !res) case .Lt: - expect(t, (v.a < v.b) == res, fmt.tprintf("Expected cstring(\"%v\") < cstring(\"%v\") to be %v", v.a, v.b, res)) - expect(t, (s_a < s_b) == res, fmt.tprintf("Expected string(\"%v\") < string(\"%v\") to be %v", v.a, v.b, res)) + testing.expectf(t, (v.a < v.b) == res, "Expected cstring(\"%v\") < cstring(\"%v\") to be %v", v.a, v.b, res) + testing.expectf(t, (s_a < s_b) == res, "Expected string(\"%v\") < string(\"%v\") to be %v", v.a, v.b, res) // .Lt | .Eq == .LtEq lteq := v.res[.Eq] | res - expect(t, (v.a <= v.b) == lteq, fmt.tprintf("Expected cstring(\"%v\") <= cstring(\"%v\") to be %v", v.a, v.b, lteq)) - expect(t, (s_a <= s_b) == lteq, fmt.tprintf("Expected string(\"%v\") <= string(\"%v\") to be %v", v.a, v.b, lteq)) + testing.expectf(t, (v.a <= v.b) == lteq, "Expected cstring(\"%v\") <= cstring(\"%v\") to be %v", v.a, v.b, lteq) + testing.expectf(t, (s_a <= s_b) == lteq, "Expected string(\"%v\") <= string(\"%v\") to be %v", v.a, v.b, lteq) case .Gt: - expect(t, (v.a > v.b) == res, fmt.tprintf("Expected cstring(\"%v\") > cstring(\"%v\") to be %v", v.a, v.b, res)) - expect(t, (s_a > s_b) == res, fmt.tprintf("Expected string(\"%v\") > string(\"%v\") to be %v", v.a, v.b, res)) + testing.expectf(t, (v.a > v.b) == res, "Expected cstring(\"%v\") > cstring(\"%v\") to be %v", v.a, v.b, res) + testing.expectf(t, (s_a > s_b) == res, "Expected string(\"%v\") > string(\"%v\") to be %v", v.a, v.b, res) // .Gt | .Eq == .GtEq gteq := v.res[.Eq] | res - expect(t, (v.a >= v.b) == gteq, fmt.tprintf("Expected cstring(\"%v\") >= cstring(\"%v\") to be %v", v.a, v.b, gteq)) - expect(t, (s_a >= s_b) == gteq, fmt.tprintf("Expected string(\"%v\") >= string(\"%v\") to be %v", v.a, v.b, gteq)) + testing.expectf(t, (v.a >= v.b) == gteq, "Expected cstring(\"%v\") >= cstring(\"%v\") to be %v", v.a, v.b, gteq) + testing.expectf(t, (s_a >= s_b) == gteq, "Expected string(\"%v\") >= string(\"%v\") to be %v", v.a, v.b, gteq) } } } -} - -// -------- -------- -------- -------- -------- -------- -------- -------- -------- -------- - -main :: proc() { - t := testing.T{} - - string_compare(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.printf("[%v] %v\n", loc, message) - return - } - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } } \ No newline at end of file diff --git a/tests/issues/run.bat b/tests/issues/run.bat index cd23faefb..f3e3daba9 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -3,19 +3,19 @@ if not exist "build\" mkdir build pushd build -set COMMON=-collection:tests=..\.. -define:ODIN_TEST_FANCY=false +set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style @echo on -..\..\..\odin test ..\test_issue_829.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_1592.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_2056.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_2087.odin %COMMON% -file || exit /b -..\..\..\odin build ..\test_issue_2113.odin %COMMON% -file -debug || exit /b -..\..\..\odin test ..\test_issue_2466.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_2615.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_2637.odin %COMMON% -file || exit /b -..\..\..\odin test ..\test_issue_2666.odin %COMMON% -file || exit /b +..\..\..\odin test ..\test_issue_829.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_1592.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_2056.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_2087.odin %COMMON% || exit /b +..\..\..\odin build ..\test_issue_2113.odin %COMMON% -debug || exit /b +..\..\..\odin test ..\test_issue_2466.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_2615.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_2637.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_2666.odin %COMMON% || exit /b @echo off diff --git a/tests/issues/run.sh b/tests/issues/run.sh index a7eee8514..24b388b07 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -4,22 +4,22 @@ set -eu mkdir -p build pushd build ODIN=../../../odin -COMMON="-collection:tests=../.. -define:ODIN_TEST_FANCY=false" +COMMON="-define:ODIN_TEST_FANCY=false -file -vet -strict-style" NO_NIL_ERR="Error: " set -x -$ODIN test ../test_issue_829.odin $COMMON -file -$ODIN test ../test_issue_1592.odin $COMMON -file -$ODIN test ../test_issue_2056.odin $COMMON -file -$ODIN test ../test_issue_2087.odin $COMMON -file -$ODIN build ../test_issue_2113.odin $COMMON -file -debug -$ODIN test ../test_issue_2466.odin $COMMON -file -$ODIN test ../test_issue_2615.odin $COMMON -file -$ODIN test ../test_issue_2637.odin $COMMON -file -$ODIN test ../test_issue_2666.odin $COMMON -file -if [[ $($ODIN build ../test_issue_2395.odin $COMMON -file 2>&1 >/dev/null | grep -c "$NO_NIL_ERR") -eq 2 ]] ; then +$ODIN test ../test_issue_829.odin $COMMON +$ODIN test ../test_issue_1592.odin $COMMON +$ODIN test ../test_issue_2056.odin $COMMON +$ODIN test ../test_issue_2087.odin $COMMON +$ODIN build ../test_issue_2113.odin $COMMON -debug +$ODIN test ../test_issue_2466.odin $COMMON +$ODIN test ../test_issue_2615.odin $COMMON +$ODIN test ../test_issue_2637.odin $COMMON +$ODIN test ../test_issue_2666.odin $COMMON +if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "$NO_NIL_ERR") -eq 2 ]] ; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" diff --git a/tests/issues/test_issue_1592.odin b/tests/issues/test_issue_1592.odin index 800314a93..79eff33df 100644 --- a/tests/issues/test_issue_1592.odin +++ b/tests/issues/test_issue_1592.odin @@ -1,7 +1,6 @@ // Tests issue #1592 https://github.com/odin-lang/Odin/issues/1592 package test_issues -import "core:fmt" import "core:testing" /* Original issue #1592 example */ @@ -31,428 +30,428 @@ true_result :: proc() -> bool { @test test_simple_const_false :: proc(t: ^testing.T) { if CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if (CONSTANT_FALSE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if (!CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !!CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_FALSE == true { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_FALSE == false { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(CONSTANT_FALSE == true) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(CONSTANT_FALSE == false) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } } @test test_simple_const_true :: proc(t: ^testing.T) { if CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if (CONSTANT_TRUE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if (!CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if (!CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !!CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE == true { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE == false { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE == true) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE == false) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_simple_proc_false :: proc(t: ^testing.T) { if false_result() { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !false_result() { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_simple_proc_true :: proc(t: ^testing.T) { if true_result() { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !true_result() { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } } @test test_const_false_const_false :: proc(t: ^testing.T) { if CONSTANT_FALSE || CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_FALSE && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !CONSTANT_FALSE || CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !CONSTANT_FALSE && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_FALSE || !CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_FALSE && !CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_FALSE || CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(CONSTANT_FALSE && CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_const_false_const_true :: proc(t: ^testing.T) { if CONSTANT_FALSE || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_FALSE && CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !CONSTANT_FALSE || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !CONSTANT_FALSE && CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_FALSE || !CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_FALSE && !CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_FALSE || CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_FALSE && CONSTANT_TRUE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_const_true_const_false :: proc(t: ^testing.T) { if CONSTANT_TRUE || CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !CONSTANT_TRUE || CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !CONSTANT_TRUE && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_TRUE || !CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE && !CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(CONSTANT_TRUE || CONSTANT_FALSE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE && CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_const_true_const_true :: proc(t: ^testing.T) { if CONSTANT_TRUE || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE && CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !CONSTANT_TRUE || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !CONSTANT_TRUE && CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if CONSTANT_TRUE || !CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if CONSTANT_TRUE && !CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE || CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(CONSTANT_TRUE && CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } } @test test_proc_false_const_false :: proc(t: ^testing.T) { if false_result() || CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if false_result() && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(false_result() || CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(false_result() && CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_proc_false_const_true :: proc(t: ^testing.T) { if false_result() || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if false_result() && CONSTANT_TRUE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(false_result() || CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(false_result() && CONSTANT_TRUE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_proc_true_const_false :: proc(t: ^testing.T) { if true_result() || CONSTANT_FALSE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if true_result() && CONSTANT_FALSE { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(true_result() || CONSTANT_FALSE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(true_result() && CONSTANT_FALSE) { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } } @test test_proc_true_const_true :: proc(t: ^testing.T) { if true_result() || CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if true_result() && CONSTANT_TRUE { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } else { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } if !(true_result() || CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } if !(true_result() && CONSTANT_TRUE) { - testing.expect(t, false, fmt.tprintf("%s: !false\n", #procedure)) + testing.expect(t, false, "!false") } else { - testing.expect(t, true, fmt.tprintf("%s: !true\n", #procedure)) + testing.expect(t, true, "!true") } } diff --git a/tests/issues/test_issue_2056.odin b/tests/issues/test_issue_2056.odin index 4869b557e..06bc11fba 100644 --- a/tests/issues/test_issue_2056.odin +++ b/tests/issues/test_issue_2056.odin @@ -1,7 +1,6 @@ // Tests issue #2056 https://github.com/odin-lang/Odin/issues/2056 package test_issues -import "core:fmt" import "core:testing" @test @@ -12,9 +11,9 @@ test_scalar_matrix_conversion :: proc(t: ^testing.T) { for i in 0..<4 { for j in 0..<4 { if i == j { - testing.expect(t, m[i,j] == 1, fmt.tprintf("expected 1 at m[%d,%d], found %f\n", i, j, m[i,j])) + testing.expectf(t, m[i,j] == 1, "expected 1 at m[%d,%d], found %f\n", i, j, m[i,j]) } else { - testing.expect(t, m[i,j] == 0, fmt.tprintf("expected 0 at m[%d,%d], found %f\n", i, j, m[i,j])) + testing.expectf(t, m[i,j] == 0, "expected 0 at m[%d,%d], found %f\n", i, j, m[i,j]) } } } diff --git a/tests/issues/test_issue_2466.odin b/tests/issues/test_issue_2466.odin index 4810cfea9..f5987903a 100644 --- a/tests/issues/test_issue_2466.odin +++ b/tests/issues/test_issue_2466.odin @@ -1,7 +1,6 @@ // Tests issue #2466 https://github.com/odin-lang/Odin/issues/2466 package test_issues -import "core:fmt" import "core:testing" Bug :: struct { @@ -16,7 +15,7 @@ test_compound_literal_local_reuse :: proc(t: ^testing.T) { val = v, arr = {42}, } - testing.expect(t, bug.val == 123, fmt.tprintf("expected 123, found %d", bug.val)) - testing.expect(t, bug.arr[0] == 42, fmt.tprintf("expected 42, found %d", bug.arr[0])) + testing.expectf(t, bug.val == 123, "expected 123, found %d", bug.val) + testing.expectf(t, bug.arr[0] == 42, "expected 42, found %d", bug.arr[0]) } diff --git a/tests/issues/test_issue_829.odin b/tests/issues/test_issue_829.odin index 273b3b3b5..229d8e9b4 100644 --- a/tests/issues/test_issue_829.odin +++ b/tests/issues/test_issue_829.odin @@ -1,7 +1,6 @@ // Tests issue #829 https://github.com/odin-lang/Odin/issues/829 package test_issues -import "core:fmt" import "core:testing" /* Original issue #829 example */ @@ -13,6 +12,6 @@ env : map[string]proc(a, b : int) -> int = { @(test) test_orig_ret :: proc(t: ^testing.T) { - r := fmt.tprint(env["+"](1, 2)) - testing.expect(t, r == "3", fmt.tprintf("%s: \"%s\" != \"3\"\n", #procedure, r)) -} + r := env["+"](1, 2) + testing.expectf(t, r == 3, "%q != 3", r) +} \ No newline at end of file diff --git a/tests/vendor/build.bat b/tests/vendor/build.bat index 1ba53615a..84ab2f1ef 100644 --- a/tests/vendor/build.bat +++ b/tests/vendor/build.bat @@ -5,4 +5,4 @@ set PATH_TO_ODIN==..\..\odin echo --- echo Running vendor:glfw tests echo --- -%PATH_TO_ODIN% run glfw %COMMON% -out:vendor_glfw.exe || exit /b \ No newline at end of file +%PATH_TO_ODIN% test glfw %COMMON% -out:vendor_glfw.exe || exit /b \ No newline at end of file diff --git a/tests/vendor/glfw/test_vendor_glfw.odin b/tests/vendor/glfw/test_vendor_glfw.odin index ce55ad7ef..4254ebb09 100644 --- a/tests/vendor/glfw/test_vendor_glfw.odin +++ b/tests/vendor/glfw/test_vendor_glfw.odin @@ -1,49 +1,21 @@ package test_vendor_glfw import "core:testing" -import "core:fmt" import "vendor:glfw" -import "core:os" GLFW_MAJOR :: 3 GLFW_MINOR :: 4 GLFW_PATCH :: 0 -TEST_count := 0 -TEST_fail := 0 - -when ODIN_TEST { - expect :: testing.expect - log :: testing.log -} else { - expect :: proc(t: ^testing.T, condition: bool, message: string, loc := #caller_location) { - fmt.printf("[%v] ", loc) - TEST_count += 1 - if !condition { - TEST_fail += 1 - fmt.println(message) - return - } - fmt.println(" PASS") - } - log :: proc(t: ^testing.T, v: any, loc := #caller_location) { - fmt.printf("[%v] ", loc) - fmt.printf("log: %v\n", v) - } -} - -main :: proc() { - t := testing.T{} - test_glfw(&t) - - fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count) - if TEST_fail > 0 { - os.exit(1) - } -} - @(test) test_glfw :: proc(t: ^testing.T) { major, minor, patch := glfw.GetVersion() - expect(t, major == GLFW_MAJOR && minor == GLFW_MINOR, fmt.tprintf("Expected GLFW.GetVersion: %v.%v.%v, got %v.%v.%v instead", GLFW_MAJOR, GLFW_MINOR, GLFW_PATCH, major, minor, patch)) + testing.expectf( + t, + major == GLFW_MAJOR && \ + minor == GLFW_MINOR, + "Expected GLFW.GetVersion: %v.%v.%v, got %v.%v.%v instead", + GLFW_MAJOR, GLFW_MINOR, GLFW_PATCH, + major, minor, patch, + ) } From 8d8c42e9625b234e181dd8b7d16875ad5140d2ad Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 15:30:23 -0400 Subject: [PATCH 114/270] Use `T.seed` in tests where applicable --- tests/core/container/test_core_avl.odin | 4 ++-- tests/core/container/test_core_rbtree.odin | 12 +++--------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/tests/core/container/test_core_avl.odin b/tests/core/container/test_core_avl.odin index 37b21a6be..99dbba8b2 100644 --- a/tests/core/container/test_core_avl.odin +++ b/tests/core/container/test_core_avl.odin @@ -8,7 +8,7 @@ import "core:log" @(test) test_avl :: proc(t: ^testing.T) { - log.infof("Testing avl, using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", random_seed, random_seed) + log.infof("Testing avl using random seed %v.", t.seed) // Initialization. tree: avl.Tree(int) @@ -21,7 +21,7 @@ test_avl :: proc(t: ^testing.T) { testing.expect(t, avl.iterator_get(&iter) == nil, "empty/iterator: first node should be nil") r: rand.Rand - rand.init(&r, random_seed) + rand.init(&r, t.seed) // Test insertion. NR_INSERTS :: 32 + 1 // Ensure at least 1 collision. diff --git a/tests/core/container/test_core_rbtree.odin b/tests/core/container/test_core_rbtree.odin index a4151d120..8def8edb6 100644 --- a/tests/core/container/test_core_rbtree.odin +++ b/tests/core/container/test_core_rbtree.odin @@ -8,12 +8,6 @@ import "core:mem" import "core:slice" import "core:log" -@(private) -_RANDOM_SEED :: #config(RANDOM_SEED, u64(0)) - -// Exported -random_seed := u64(intrinsics.read_cycle_counter()) when _RANDOM_SEED == 0 else u64(_RANDOM_SEED) - test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { track: mem.Tracking_Allocator mem.tracking_allocator_init(&track, context.allocator) @@ -21,9 +15,9 @@ test_rbtree_integer :: proc(t: ^testing.T, $Key: typeid, $Value: typeid) { context.allocator = mem.tracking_allocator(&track) r: rand.Rand - rand.init(&r, random_seed) + rand.init(&r, t.seed) - log.infof("Testing Red-Black Tree($Key=%v,$Value=%v), using random seed %v, add -define:RANDOM_SEED=%v to reuse it.", type_info_of(Key), type_info_of(Value), random_seed, random_seed) + log.infof("Testing Red-Black Tree($Key=%v,$Value=%v) using random seed %v.", type_info_of(Key), type_info_of(Value), t.seed) tree: rb.Tree(Key, Value) rb.init(&tree) @@ -243,4 +237,4 @@ verify_rbtree_propery_5_helper :: proc(t: ^testing.T, n: ^$N/rb.Node($Key, $Valu verify_rbtree_propery_5_helper(t, n._right, black_count, path_black_count) } // Properties 4 and 5 together guarantee that no path in the tree is more than about twice as long as any other path, -// which guarantees that it has O(log n) height. \ No newline at end of file +// which guarantees that it has O(log n) height. From 3f1249c27e6364896d801cbbfe7edaf12f69cf37 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 15:34:13 -0400 Subject: [PATCH 115/270] Tell user about `ODIN_TEST_RANDOM_SEED` option --- core/testing/runner.odin | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index fbe413bb2..5b80d0cf8 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -422,7 +422,11 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { "" if thread_count == 1 else "s") } - pkg_log.infof("The random seed sent to every test is: %v", shared_random_seed) + when SHARED_RANDOM_SEED == 0 { + pkg_log.infof("The random seed sent to every test is: %v. Set with -define:ODIN_TEST_RANDOM_SEED=n.", shared_random_seed) + } else { + pkg_log.infof("The random seed sent to every test is: %v.", shared_random_seed) + } when TRACKING_MEMORY { when ALWAYS_REPORT_MEMORY { From f77ce359cec2cc50c5a98e4b4257e63ed702995e Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 15:40:09 -0400 Subject: [PATCH 116/270] Be pedantic about not overwriting Odin errors I was encountering bounds-check error messages being overwritten during a test, if the test failed for another reason and sent a log message. The original intent of having this check inside of the above `if` block was that if a test sent an error message, then it was assumed an overwrite would be safe, but it's completely possible for a test to fail for a legitimate reason, then do an unrelated bounds check somewhere else that would be buried under the animation. This change will make sure that, no matter what, the progress display will not trigger a clear if a signal was raised. There's still no guarantee that bounds-check messages will be printed properly, and it's best to redirect STDERR. The only way that can be fixed is if they get a similar hook to `context.assertion_failure_proc`. --- core/testing/runner.odin | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 5b80d0cf8..190e10744 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -648,10 +648,11 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { failed_test_reason_map[test_index] = fmt.aprintf("Signal caught: %v", reason, allocator = shared_log_allocator) pkg_log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason) - when FANCY_OUTPUT { - signals_were_raised = true - bypass_progress_overwrite = true - } + } + + when FANCY_OUTPUT { + bypass_progress_overwrite = true + signals_were_raised = true } total_failure_count += 1 From f030603f0dc9c487221a8f65f5a1ccb7b345a237 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 16:47:46 -0400 Subject: [PATCH 117/270] Fix #3660 This also prevents a segfault if you do `odin build .odin -file` --- src/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 4df6f97d5..80fc995cf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2657,6 +2657,10 @@ int main(int arg_count, char const **arg_ptr) { gb_printf_err("Expected either a directory or a .odin file, got '%.*s'\n", LIT(init_filename)); return 1; } + if (!gb_file_exists(cast(const char*)init_filename.text)) { + gb_printf_err("The file '%.*s' was not found.\n", LIT(init_filename)); + return 1; + } } } } From ac9484206b8fb79bd289049ab8c2f4b6488e9cfa Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 19:15:28 -0400 Subject: [PATCH 118/270] Fix `STDIN`, `STDOUT`, `STDERR` handles for BSDs Tested on FreeBSD 14.0 and NetBSD 10.0 OpenBSD is untested, but link names were sourced from: https://github.com/openbsd/src/blob/master/include/stdio.h According to this, OpenBSD shares the same layout as NetBSD. FreeBSD has the same as Darwin in this regard. --- core/c/libc/stdio.odin | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index f17d3bd06..3e1d0f5a2 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -102,10 +102,12 @@ when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD { SEEK_END :: 2 foreign libc { - stderr: ^FILE - stdin: ^FILE - stdout: ^FILE + __sF: [3]FILE } + + stdin: ^FILE = &__sF[0] + stdout: ^FILE = &__sF[1] + stderr: ^FILE = &__sF[2] } when ODIN_OS == .FreeBSD { @@ -127,9 +129,9 @@ when ODIN_OS == .FreeBSD { SEEK_END :: 2 foreign libc { - stderr: ^FILE - stdin: ^FILE - stdout: ^FILE + @(link_name="__stderrp") stderr: ^FILE + @(link_name="__stdinp") stdin: ^FILE + @(link_name="__stdoutp") stdout: ^FILE } } From c8539fe4115b733583849705bac14e396a3a7dd7 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 19:28:07 -0400 Subject: [PATCH 119/270] Revert "Disable NetBSD tests until 'undefined reference to stdout' is solved." This reverts commit 21a1ddfbae92fb78bf536f4047016d0001b76cc2. --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36914b41b..c5493775f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,10 @@ jobs: ./odin version ./odin report ./odin check examples/all -vet -strict-style -target:netbsd_amd64 + (cd tests/core; gmake all_bsd) + (cd tests/internal; gmake all_bsd) + (cd tests/issues; ./run.sh) + (cd tests/benchmark; gmake all) build_linux: name: Ubuntu Build, Check, and Test runs-on: ubuntu-latest From 6a5633df2d3c7a398d6aa20e1024f6f380c9d987 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 20:16:23 -0400 Subject: [PATCH 120/270] Fix wrong `PTHREAD_CANCEL_ASYNCHRONOUS` on FreeBSD and OpenBSD The test runner was deadlocking when a test raised a signal on FreeBSD. This is untested on OpenBSD, but I have referenced this file: https://github.com/openbsd/src/blob/master/include/pthread.h --- core/sys/unix/pthread_freebsd.odin | 4 ++-- core/sys/unix/pthread_openbsd.odin | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/sys/unix/pthread_freebsd.odin b/core/sys/unix/pthread_freebsd.odin index 3417d3943..5f4dac289 100644 --- a/core/sys/unix/pthread_freebsd.odin +++ b/core/sys/unix/pthread_freebsd.odin @@ -95,7 +95,7 @@ sem_t :: struct { PTHREAD_CANCEL_ENABLE :: 0 PTHREAD_CANCEL_DISABLE :: 1 PTHREAD_CANCEL_DEFERRED :: 0 -PTHREAD_CANCEL_ASYNCHRONOUS :: 1 +PTHREAD_CANCEL_ASYNCHRONOUS :: 2 foreign import "system:pthread" @@ -119,4 +119,4 @@ foreign pthread { pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- pthread_cancel :: proc (thread: pthread_t) -> c.int --- -} \ No newline at end of file +} diff --git a/core/sys/unix/pthread_openbsd.odin b/core/sys/unix/pthread_openbsd.odin index 7ae82e662..855e7d99c 100644 --- a/core/sys/unix/pthread_openbsd.odin +++ b/core/sys/unix/pthread_openbsd.odin @@ -49,7 +49,7 @@ sem_t :: distinct rawptr PTHREAD_CANCEL_ENABLE :: 0 PTHREAD_CANCEL_DISABLE :: 1 PTHREAD_CANCEL_DEFERRED :: 0 -PTHREAD_CANCEL_ASYNCHRONOUS :: 1 +PTHREAD_CANCEL_ASYNCHRONOUS :: 2 foreign import libc "system:c" @@ -71,4 +71,4 @@ foreign libc { pthread_setcancelstate :: proc (state: c.int, old_state: ^c.int) -> c.int --- pthread_setcanceltype :: proc (type: c.int, old_type: ^c.int) -> c.int --- pthread_cancel :: proc (thread: pthread_t) -> c.int --- -} \ No newline at end of file +} From 7764ab2ab0a45786d3c27c2a5bfad264e181b7b7 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 21:25:04 -0400 Subject: [PATCH 121/270] Prevent test runner deadlock on NetBSD Add `pthread_testcancel` to `core:sys/unix` --- core/sys/unix/pthread_unix.odin | 1 + core/testing/signal_handler_libc.odin | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin index 5760560ee..c876a214a 100644 --- a/core/sys/unix/pthread_unix.odin +++ b/core/sys/unix/pthread_unix.odin @@ -116,4 +116,5 @@ foreign pthread { pthread_mutexattr_setpshared :: proc(attrs: ^pthread_mutexattr_t, value: c.int) -> c.int --- pthread_mutexattr_getpshared :: proc(attrs: ^pthread_mutexattr_t, result: ^c.int) -> c.int --- + pthread_testcancel :: proc () --- } diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index ff3dbe135..f60cf2540 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -6,6 +6,7 @@ import "base:intrinsics" import "core:c/libc" import "core:encoding/ansi" import "core:sync" +@require import "core:sys/unix" @(private="file") stop_runner_flag: libc.sig_atomic_t @@ -75,6 +76,18 @@ This is a dire bug and should be reported to the Odin developers. // Idle until this thread is terminated by the runner, // otherwise we may continue to generate signals. intrinsics.cpu_relax() + + when ODIN_OS != .Windows { + // NOTE(Feoramund): Some UNIX-like platforms may require this. + // + // During testing, I found that NetBSD 10.0 refused to + // terminate a task thread, even when its thread had been + // properly set to PTHREAD_CANCEL_ASYNCHRONOUS. + // + // The runner would stall after returning from `pthread_cancel`. + + unix.pthread_testcancel() + } } } } From fa29974dabc03479848935168eb4e26becbe304d Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 23:21:44 -0400 Subject: [PATCH 122/270] Use `Warning` log level for reporting memory leaks Works well with `-define:ODIN_TEST_LOG_LEVEL=warning`. --- core/testing/runner.odin | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index 190e10744..c82aa1fda 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -467,16 +467,18 @@ runner :: proc(internal_tests: []Internal_Test) -> bool { when TRACKING_MEMORY { #no_bounds_check tracker := &task_memory_trackers[data.allocator_index] + memory_is_in_bad_state := len(tracker.allocation_map) + len(tracker.bad_free_array) > 0 + when ALWAYS_REPORT_MEMORY { should_report := true } else { - should_report := len(tracker.allocation_map) + len(tracker.bad_free_array) > 0 + should_report := memory_is_in_bad_state } if should_report { write_memory_report(batch_writer, tracker, data.it.pkg, data.it.name) - pkg_log.info(bytes.buffer_to_string(&batch_buffer)) + pkg_log.log(.Warning if memory_is_in_bad_state else .Info, bytes.buffer_to_string(&batch_buffer)) bytes.buffer_reset(&batch_buffer) } From 0ff130d82bf058a41f77c08c8c0c63fb9e205a13 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 2 Jun 2024 23:36:04 -0400 Subject: [PATCH 123/270] Fix ad hoc `printf` in test runner signal handler --- core/testing/signal_handler_libc.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index f60cf2540..d76fdd66b 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -51,7 +51,7 @@ stop_test_callback :: proc "c" (sig: libc.int) { sigbuf[i] = cast(u8)('0' + m) i -= 1 } - sigstr = cast(string)sigbuf[i:] + sigstr = cast(string)sigbuf[1 + i:len(sigbuf) - 1] } advisory_a := ` From 1617060f46d2467dff185c6c7c43ff8d7ed6e36e Mon Sep 17 00:00:00 2001 From: Laurent Dufresne Date: Mon, 3 Jun 2024 19:43:39 +0200 Subject: [PATCH 124/270] Fix type of two Win32 functions --- core/sys/windows/kernel32.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index eba275522..16b6fa244 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -453,9 +453,9 @@ foreign kernel32 { // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setfilecompletionnotificationmodes) SetFileCompletionNotificationModes :: proc(FileHandle: HANDLE, Flags: u8) -> BOOL --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-createiocompletionport) - CreateIoCompletionPort :: proc(FileHandle: HANDLE, ExistingCompletionPort: HANDLE, CompletionKey: ^uintptr, NumberOfConcurrentThreads: DWORD) -> HANDLE --- + CreateIoCompletionPort :: proc(FileHandle: HANDLE, ExistingCompletionPort: HANDLE, CompletionKey: ULONG_PTR, NumberOfConcurrentThreads: DWORD) -> HANDLE --- //[MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatus) - GetQueuedCompletionStatus :: proc(CompletionPort: HANDLE, lpNumberOfBytesTransferred: ^DWORD, lpCompletionKey: uintptr, lpOverlapped: ^^OVERLAPPED, dwMilliseconds: DWORD) -> BOOL --- + GetQueuedCompletionStatus :: proc(CompletionPort: HANDLE, lpNumberOfBytesTransferred: ^DWORD, lpCompletionKey: PULONG_PTR, lpOverlapped: ^^OVERLAPPED, dwMilliseconds: DWORD) -> BOOL --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-getqueuedcompletionstatusex) GetQueuedCompletionStatusEx :: proc(CompletionPort: HANDLE, lpCompletionPortEntries: ^OVERLAPPED_ENTRY, ulCount: c_ulong, ulNumEntriesRemoved: ^c_ulong, dwMilliseconds: DWORD, fAlertable: BOOL) -> BOOL --- // [MS-Docs](https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-postqueuedcompletionstatus) From 4e1dd4ced270ec374c9f45edec03dbc2e2e0b050 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:40:28 -0400 Subject: [PATCH 125/270] Move `Raw_Complex/Quaternion` types to `base:runtime` --- base/runtime/core.odin | 6 ++++++ core/mem/raw.odin | 7 ------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/base/runtime/core.odin b/base/runtime/core.odin index 4b6a1949e..9b7a3f613 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -470,6 +470,12 @@ Raw_Soa_Pointer :: struct { index: int, } +Raw_Complex64 :: struct {real, imag: f32} +Raw_Complex128 :: struct {real, imag: f64} +Raw_Quaternion128 :: struct {imag, jmag, kmag: f32, real: f32} +Raw_Quaternion256 :: struct {imag, jmag, kmag: f64, real: f64} +Raw_Quaternion128_Vector_Scalar :: struct {vector: [3]f32, scalar: f32} +Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64} /* diff --git a/core/mem/raw.odin b/core/mem/raw.odin index 56790e959..4f37ce5ab 100644 --- a/core/mem/raw.odin +++ b/core/mem/raw.odin @@ -11,13 +11,6 @@ Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array Raw_Map :: runtime.Raw_Map Raw_Soa_Pointer :: runtime.Raw_Soa_Pointer -Raw_Complex64 :: struct {real, imag: f32} -Raw_Complex128 :: struct {real, imag: f64} -Raw_Quaternion128 :: struct {imag, jmag, kmag: f32, real: f32} -Raw_Quaternion256 :: struct {imag, jmag, kmag: f64, real: f64} -Raw_Quaternion128_Vector_Scalar :: struct {vector: [3]f32, scalar: f32} -Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64} - make_any :: proc "contextless" (data: rawptr, id: typeid) -> any { return transmute(any)Raw_Any{data, id} } From 97f1d12e042b1400a4af84447959ef17e1b57f4a Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:41:14 -0400 Subject: [PATCH 126/270] Add missing `Raw_*` types for complex and quaternion --- base/runtime/core.odin | 3 +++ 1 file changed, 3 insertions(+) diff --git a/base/runtime/core.odin b/base/runtime/core.odin index 9b7a3f613..47b9a690c 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -470,10 +470,13 @@ Raw_Soa_Pointer :: struct { index: int, } +Raw_Complex32 :: struct {real, imag: f16} Raw_Complex64 :: struct {real, imag: f32} Raw_Complex128 :: struct {real, imag: f64} +Raw_Quaternion64 :: struct {imag, jmag, kmag: f16, real: f16} Raw_Quaternion128 :: struct {imag, jmag, kmag: f32, real: f32} Raw_Quaternion256 :: struct {imag, jmag, kmag: f64, real: f64} +Raw_Quaternion64_Vector_Scalar :: struct {vector: [3]f16, scalar: f16} Raw_Quaternion128_Vector_Scalar :: struct {vector: [3]f32, scalar: f32} Raw_Quaternion256_Vector_Scalar :: struct {vector: [3]f64, scalar: f64} From 88598c2c6483cdaa9d574d9883a65543d0d31676 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:45:32 -0400 Subject: [PATCH 127/270] Make use of `runtime.Raw_*` types in `core:math/linalg` --- core/math/linalg/general.odin | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/core/math/linalg/general.odin b/core/math/linalg/general.odin index 51dfd2360..37c0447cb 100644 --- a/core/math/linalg/general.odin +++ b/core/math/linalg/general.odin @@ -3,6 +3,7 @@ package linalg import "core:math" import "base:builtin" import "base:intrinsics" +import "base:runtime" // Generic @@ -223,33 +224,27 @@ quaternion_mul_quaternion :: proc "contextless" (q1, q2: $Q) -> Q where IS_QUATE @(require_results) quaternion64_mul_vector3 :: proc "contextless" (q: $Q/quaternion64, v: $V/[3]$F/f16) -> V { - Raw_Quaternion :: struct {xyz: [3]f16, r: f16} - - q := transmute(Raw_Quaternion)q + q := transmute(runtime.Raw_Quaternion64_Vector_Scalar)q v := v - t := cross(2*q.xyz, v) - return V(v + q.r*t + cross(q.xyz, t)) + t := cross(2*q.vector, v) + return V(v + q.scalar*t + cross(q.vector, t)) } @(require_results) quaternion128_mul_vector3 :: proc "contextless" (q: $Q/quaternion128, v: $V/[3]$F/f32) -> V { - Raw_Quaternion :: struct {xyz: [3]f32, r: f32} - - q := transmute(Raw_Quaternion)q + q := transmute(runtime.Raw_Quaternion128_Vector_Scalar)q v := v - t := cross(2*q.xyz, v) - return V(v + q.r*t + cross(q.xyz, t)) + t := cross(2*q.vector, v) + return V(v + q.scalar*t + cross(q.vector, t)) } @(require_results) quaternion256_mul_vector3 :: proc "contextless" (q: $Q/quaternion256, v: $V/[3]$F/f64) -> V { - Raw_Quaternion :: struct {xyz: [3]f64, r: f64} - - q := transmute(Raw_Quaternion)q + q := transmute(runtime.Raw_Quaternion256_Vector_Scalar)q v := v - t := cross(2*q.xyz, v) - return V(v + q.r*t + cross(q.xyz, t)) + t := cross(2*q.vector, v) + return V(v + q.scalar*t + cross(q.vector, t)) } quaternion_mul_vector3 :: proc{quaternion64_mul_vector3, quaternion128_mul_vector3, quaternion256_mul_vector3} From 50b4a63fe141d8f2c8a17f61bfde8ce078daae14 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 22:02:35 +0100 Subject: [PATCH 128/270] Add `ast.Foreign_Impot_Decl.fullpaths` to walk.odin --- core/odin/ast/walk.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/odin/ast/walk.odin b/core/odin/ast/walk.odin index 63107a2e2..7304f237c 100644 --- a/core/odin/ast/walk.odin +++ b/core/odin/ast/walk.odin @@ -320,6 +320,7 @@ walk :: proc(v: ^Visitor, node: ^Node) { if n.comment != nil { walk(v, n.comment) } + walk_expr_list(v, n.fullpaths) case ^Proc_Group: walk_expr_list(v, n.args) From d7f6def8adf14c774b4ece317e510a321af23c64 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 3 Jun 2024 17:18:27 -0400 Subject: [PATCH 129/270] Add aliases for `Raw_*` complex/quaternion types into `core:mem` --- core/mem/raw.odin | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/mem/raw.odin b/core/mem/raw.odin index 4f37ce5ab..f56206957 100644 --- a/core/mem/raw.odin +++ b/core/mem/raw.odin @@ -11,6 +11,16 @@ Raw_Dynamic_Array :: runtime.Raw_Dynamic_Array Raw_Map :: runtime.Raw_Map Raw_Soa_Pointer :: runtime.Raw_Soa_Pointer +Raw_Complex32 :: runtime.Raw_Complex32 +Raw_Complex64 :: runtime.Raw_Complex64 +Raw_Complex128 :: runtime.Raw_Complex128 +Raw_Quaternion64 :: runtime.Raw_Quaternion64 +Raw_Quaternion128 :: runtime.Raw_Quaternion128 +Raw_Quaternion256 :: runtime.Raw_Quaternion256 +Raw_Quaternion64_Vector_Scalar :: runtime.Raw_Quaternion64_Vector_Scalar +Raw_Quaternion128_Vector_Scalar :: runtime.Raw_Quaternion128_Vector_Scalar +Raw_Quaternion256_Vector_Scalar :: runtime.Raw_Quaternion256_Vector_Scalar + make_any :: proc "contextless" (data: rawptr, id: typeid) -> any { return transmute(any)Raw_Any{data, id} } From 8e9716ea2f1628297b89e6d097b01bd574b1686c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 22:20:39 +0100 Subject: [PATCH 130/270] Add `ast.Foreign_Import_Decl` to `ast.clone` --- core/odin/ast/clone.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/odin/ast/clone.odin b/core/odin/ast/clone.odin index bca740dd4..406add2e9 100644 --- a/core/odin/ast/clone.odin +++ b/core/odin/ast/clone.odin @@ -279,6 +279,7 @@ clone_node :: proc(node: ^Node) -> ^Node { r.body = clone(r.body) case ^Foreign_Import_Decl: r.name = auto_cast clone(r.name) + r.fullpaths = auto_cast clone_array(r.fullpaths) case ^Proc_Group: r.args = clone(r.args) case ^Attribute: From 4dac577caaafcee45695baf057795666ace7f431 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 22:21:54 +0100 Subject: [PATCH 131/270] Add attributes to Foreign_Import_Decl in `clone` --- core/odin/ast/clone.odin | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/odin/ast/clone.odin b/core/odin/ast/clone.odin index 406add2e9..b0a1673b2 100644 --- a/core/odin/ast/clone.odin +++ b/core/odin/ast/clone.odin @@ -278,8 +278,9 @@ clone_node :: proc(node: ^Node) -> ^Node { r.foreign_library = clone(r.foreign_library) r.body = clone(r.body) case ^Foreign_Import_Decl: + r.attributes = clone_dynamic_array(r.attributes) r.name = auto_cast clone(r.name) - r.fullpaths = auto_cast clone_array(r.fullpaths) + r.fullpaths = clone_array(r.fullpaths) case ^Proc_Group: r.args = clone(r.args) case ^Attribute: From 5b5106baee1ff76f8dfca28bc77ed0dfafeb4e53 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 22:36:54 +0100 Subject: [PATCH 132/270] Try updating nightly stuff --- .github/workflows/nightly.yml | 2 +- ci/upload_create_nightly.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index c9a2c821b..cfd780380 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -189,7 +189,7 @@ jobs: DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }} run: | echo Authorizing B2 account - b2 authorize-account "$APPID" "$APPKEY" + b2 acount authorize "$APPID" "$APPKEY" echo Uploading artifcates to B2 chmod +x ./ci/upload_create_nightly.sh diff --git a/ci/upload_create_nightly.sh b/ci/upload_create_nightly.sh index 065cb13bf..a4122f1da 100755 --- a/ci/upload_create_nightly.sh +++ b/ci/upload_create_nightly.sh @@ -22,4 +22,4 @@ else 7z a -bd "output/$filename" -r "$artifact" fi -b2 upload-file --noProgress "$bucket" "output/$filename" "nightly/$filename" +b2 upload-file "$bucket" "output/$filename" "nightly/$filename" From dc6a8e5ffbf86cd41b526d443be7a7f0a042b802 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 22:44:12 +0100 Subject: [PATCH 133/270] Fix typo --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index cfd780380..221ab1cdb 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -189,7 +189,7 @@ jobs: DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }} run: | echo Authorizing B2 account - b2 acount authorize "$APPID" "$APPKEY" + b2 account authorize "$APPID" "$APPKEY" echo Uploading artifcates to B2 chmod +x ./ci/upload_create_nightly.sh From 0ef0894213f9b44e748bcdc965eedaaeb13dcc7f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 22:55:24 +0100 Subject: [PATCH 134/270] Fix to `bl.tok = path` --- core/odin/parser/parser.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 813585ba4..ad5ee9087 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -1204,7 +1204,7 @@ parse_foreign_decl :: proc(p: ^Parser) -> ^ast.Decl { path := expect_token(p, .String) reserve(&fullpaths, 1) bl := ast.new(ast.Basic_Lit, path.pos, end_pos(path)) - bl.tok = tok + bl.tok = path append(&fullpaths, bl) } From c8f9af64db1600b4efb61561215c7c9fc46fd940 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 23:01:39 +0100 Subject: [PATCH 135/270] Add `print` to `delete_old_binaries.py` --- ci/delete_old_binaries.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ci/delete_old_binaries.py b/ci/delete_old_binaries.py index 206d849f5..2a87bc731 100644 --- a/ci/delete_old_binaries.py +++ b/ci/delete_old_binaries.py @@ -12,6 +12,7 @@ def main(): print(f"Looking for binaries to delete older than {days_to_keep} days") files_lines = execute_cli(f"b2 ls --long --versions {bucket} nightly").split("\n") + print(files_lines) for x in files_lines: parts = [y for y in x.split(' ') if y] From 17225131f729de1e233220fabb7e1ea3257f02c3 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 3 Jun 2024 23:12:24 +0100 Subject: [PATCH 136/270] Try `/` rather than ` ` --- ci/delete_old_binaries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/delete_old_binaries.py b/ci/delete_old_binaries.py index 2a87bc731..294c3c4be 100644 --- a/ci/delete_old_binaries.py +++ b/ci/delete_old_binaries.py @@ -11,7 +11,7 @@ def main(): days_to_keep = int(sys.argv[2]) print(f"Looking for binaries to delete older than {days_to_keep} days") - files_lines = execute_cli(f"b2 ls --long --versions {bucket} nightly").split("\n") + files_lines = execute_cli(f"b2 ls --long --versions {bucket}/nightly").split("\n") print(files_lines) for x in files_lines: parts = [y for y in x.split(' ') if y] From f745a1c4709b6d38a7bc99da1ccf97a4790b2e73 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 4 Jun 2024 00:22:49 +0200 Subject: [PATCH 137/270] b2 uri --- ci/create_nightly_json.py | 2 +- ci/delete_old_binaries.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/create_nightly_json.py b/ci/create_nightly_json.py index 2ad086a82..d7029399a 100644 --- a/ci/create_nightly_json.py +++ b/ci/create_nightly_json.py @@ -9,7 +9,7 @@ def main(): files_by_date = {} bucket = sys.argv[1] - files_lines = execute_cli(f"b2 ls --long {bucket} nightly").split("\n") + files_lines = execute_cli(f"b2 ls --long b2://{bucket}/nightly/").split("\n") for x in files_lines: parts = x.split(" ", 1) if parts[0]: diff --git a/ci/delete_old_binaries.py b/ci/delete_old_binaries.py index 294c3c4be..39e8ff2ac 100644 --- a/ci/delete_old_binaries.py +++ b/ci/delete_old_binaries.py @@ -11,7 +11,7 @@ def main(): days_to_keep = int(sys.argv[2]) print(f"Looking for binaries to delete older than {days_to_keep} days") - files_lines = execute_cli(f"b2 ls --long --versions {bucket}/nightly").split("\n") + files_lines = execute_cli(f"b2 ls --long --versions b2://{bucket}/nightly/").split("\n") print(files_lines) for x in files_lines: parts = [y for y in x.split(' ') if y] From d2a9122176c792f0445736639dba05b1817eb1f7 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 3 Jun 2024 19:48:09 -0400 Subject: [PATCH 138/270] Add `fmt` tests for printing complex and quaternion types --- tests/core/fmt/test_core_fmt.odin | 66 +++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tests/core/fmt/test_core_fmt.odin b/tests/core/fmt/test_core_fmt.odin index 82d009ac6..84b273f90 100644 --- a/tests/core/fmt/test_core_fmt.odin +++ b/tests/core/fmt/test_core_fmt.odin @@ -1,8 +1,10 @@ package test_core_fmt +import "base:runtime" import "core:fmt" import "core:os" import "core:testing" +import "core:math" import "core:mem" TEST_count := 0 @@ -61,6 +63,70 @@ test_fmt_memory :: proc(t: ^testing.T) { check(t, "0b", "%m", u8(0)) } +@(test) +test_fmt_complex_quaternion :: proc(t: ^testing.T) { + neg_inf := math.inf_f64(-1) + pos_inf := math.inf_f64(+1) + neg_zero := f64(0h80000000_00000000) + nan := math.nan_f64() + + // NOTE(Feoramund): Doing it this way, because complex construction is broken. + // Reported in issue #3665. + c: complex128 + cptr := cast(^runtime.Raw_Complex128)&c + + cptr^ = {0, 0} + check(t, "0+0i", "%v", c) + cptr^ = {1, 1} + check(t, "1+1i", "%v", c) + cptr^ = {1, 0} + check(t, "1+0i", "%v", c) + cptr^ = {-1, -1} + check(t, "-1-1i", "%v", c) + cptr^ = {0, neg_zero} + check(t, "0-0i", "%v", c) + cptr^ = {nan, nan} + check(t, "NaNNaNi", "%v", c) + cptr^ = {pos_inf, pos_inf} + check(t, "+Inf+Infi", "%v", c) + cptr^ = {neg_inf, neg_inf} + check(t, "-Inf-Infi", "%v", c) + + // Check forced plus signs. + cptr^ = {0, neg_zero} + check(t, "+0-0i", "%+v", c) + cptr^ = {1, 1} + check(t, "+1+1i", "%+v", c) + cptr^ = {nan, nan} + check(t, "NaNNaNi", "%+v", c) + cptr^ = {pos_inf, pos_inf} + check(t, "+Inf+Infi", "%+v", c) + cptr^ = {neg_inf, neg_inf} + check(t, "-Inf-Infi", "%+v", c) + + // Remember that the real number is the last in a quaternion's data layout, + // opposed to a complex, where it is the first. + q: quaternion256 + qptr := cast(^runtime.Raw_Quaternion256)&q + + qptr^ = {0, 0, 0, 0} + check(t, "0+0i+0j+0k", "%v", q) + qptr^ = {1, 1, 1, 1} + check(t, "1+1i+1j+1k", "%v", q) + qptr^ = {2, 3, 4, 1} + check(t, "1+2i+3j+4k", "%v", q) + qptr^ = {-1, -1, -1, -1} + check(t, "-1-1i-1j-1k", "%v", q) + qptr^ = {2, neg_zero, neg_zero, 1} + check(t, "1+2i-0j-0k", "%v", q) + qptr^ = {neg_inf, neg_inf, neg_inf, -1} + check(t, "-1-Infi-Infj-Infk", "%v", q) + qptr^ = {pos_inf, pos_inf, pos_inf, -1} + check(t, "-1+Infi+Infj+Infk", "%v", q) + qptr^ = {nan, nan, nan, -1} + check(t, "-1NaNiNaNjNaNk", "%v", q) +} + @(test) test_fmt_doc_examples :: proc(t: ^testing.T) { // C-like syntax From eb93779f630870b66103c559b07b855288dc8aa4 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 3 Jun 2024 19:49:38 -0400 Subject: [PATCH 139/270] Fix duplicate sign printing of complex and quaternion types Negative zero wasn't being detected (so it would appear as `+-0`), and `+Inf` was appearing as `++Inf` when imaginary. --- core/fmt/fmt.odin | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 62cd95968..f43683d11 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -2,6 +2,7 @@ package fmt import "base:intrinsics" import "base:runtime" +import "core:math" import "core:math/bits" import "core:mem" import "core:io" @@ -2968,6 +2969,21 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { fmt_bit_field(fi, v, verb, info, "") } } +// This proc helps keep some of the code around whether or not to print an +// intermediate plus sign in complexes and quaternions more readable. +@(private) +_cq_should_print_intermediate_plus :: proc "contextless" (fi: ^Info, f: f64) -> bool { + if !fi.plus && f >= 0 { + #partial switch math.classify(f) { + case .Neg_Zero, .Inf: + // These two classes print their own signs. + return false + case: + return true + } + } + return false +} // Formats a complex number based on the given formatting verb // // Inputs: @@ -2981,7 +2997,7 @@ fmt_complex :: proc(fi: ^Info, c: complex128, bits: int, verb: rune) { case 'f', 'F', 'v', 'h', 'H', 'w': r, i := real(c), imag(c) fmt_float(fi, r, bits/2, verb) - if !fi.plus && i >= 0 { + if _cq_should_print_intermediate_plus(fi, i) { io.write_rune(fi.writer, '+', &fi.n) } fmt_float(fi, i, bits/2, verb) @@ -3007,19 +3023,19 @@ fmt_quaternion :: proc(fi: ^Info, q: quaternion256, bits: int, verb: rune) { fmt_float(fi, r, bits/4, verb) - if !fi.plus && i >= 0 { + if _cq_should_print_intermediate_plus(fi, i) { io.write_rune(fi.writer, '+', &fi.n) } fmt_float(fi, i, bits/4, verb) io.write_rune(fi.writer, 'i', &fi.n) - if !fi.plus && j >= 0 { + if _cq_should_print_intermediate_plus(fi, j) { io.write_rune(fi.writer, '+', &fi.n) } fmt_float(fi, j, bits/4, verb) io.write_rune(fi.writer, 'j', &fi.n) - if !fi.plus && k >= 0 { + if _cq_should_print_intermediate_plus(fi, k) { io.write_rune(fi.writer, '+', &fi.n) } fmt_float(fi, k, bits/4, verb) From e46e22e21ba61e0bceb2e96084c1e1772bb7ed11 Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Tue, 4 Jun 2024 01:12:10 -0700 Subject: [PATCH 140/270] add a few more x11 funcs, fix egl bug --- vendor/egl/egl.odin | 2 +- vendor/x11/xlib/xlib_procs.odin | 2566 ++++++++++++++++--------------- 2 files changed, 1288 insertions(+), 1280 deletions(-) diff --git a/vendor/egl/egl.odin b/vendor/egl/egl.odin index 3174fa60b..82181b1c5 100644 --- a/vendor/egl/egl.odin +++ b/vendor/egl/egl.odin @@ -47,7 +47,7 @@ foreign egl { GetDisplay :: proc(display: NativeDisplayType) -> Display --- Initialize :: proc(display: Display, major: ^i32, minor: ^i32) -> i32 --- BindAPI :: proc(api: u32) -> i32 --- - ChooseConfig :: proc(display: Display, attrib_list: ^i32, configs: ^Context, config_size: i32, num_config: ^i32) -> i32 --- + ChooseConfig :: proc(display: Display, attrib_list: ^i32, configs: ^Config, config_size: i32, num_config: ^i32) -> i32 --- CreateWindowSurface :: proc(display: Display, config: Config, native_window: NativeWindowType, attrib_list: ^i32) -> Surface --- CreateContext :: proc(display: Display, config: Config, share_context: Context, attrib_list: ^i32) -> Context --- MakeCurrent :: proc(display: Display, draw: Surface, read: Surface, ctx: Context) -> i32 --- diff --git a/vendor/x11/xlib/xlib_procs.odin b/vendor/x11/xlib/xlib_procs.odin index 5e999519b..5465cd71b 100644 --- a/vendor/x11/xlib/xlib_procs.odin +++ b/vendor/x11/xlib/xlib_procs.odin @@ -8,108 +8,108 @@ foreign xlib { /* ---- X11/Xlib.h ---------------------------------------------------------*/ -@(default_calling_convention="c") +@(default_calling_convention="c", link_prefix="X") foreign xlib { // Free data allocated by Xlib - XFree :: proc(ptr: rawptr) --- + Free :: proc(ptr: rawptr) --- // Opening/closing a display - XOpenDisplay :: proc(name: cstring) -> ^Display --- - XCloseDisplay :: proc(display: ^Display) --- - XSetCloseDownMode :: proc(display: ^Display, mode: CloseMode) --- + OpenDisplay :: proc(name: cstring) -> ^Display --- + CloseDisplay :: proc(display: ^Display) --- + SetCloseDownMode :: proc(display: ^Display, mode: CloseMode) --- // Generate a no-op request - XNoOp :: proc(display: ^Display) --- + NoOp :: proc(display: ^Display) --- // Display macros (connection) - XConnectionNumber :: proc(display: ^Display) -> i32 --- - XExtendedMaxRequestSize :: + ConnectionNumber :: proc(display: ^Display) -> i32 --- + ExtendedMaxRequestSize :: proc(display: ^Display) -> int --- - XMaxRequestSize :: proc(display: ^Display) -> int --- - XLastKnownRequestProcessed :: + MaxRequestSize :: proc(display: ^Display) -> int --- + LastKnownRequestProcessed :: proc(display: ^Display) -> uint --- - XNextRequest :: proc(display: ^Display) -> uint --- - XProtocolVersion :: proc(display: ^Display) -> i32 --- - XProtocolRevision :: proc(display: ^Display) -> i32 --- - XQLength :: proc(display: ^Display) -> i32 --- - XServerVendor :: proc(display: ^Display) -> cstring --- - XVendorRelease :: proc(display: ^Display) -> i32 --- + NextRequest :: proc(display: ^Display) -> uint --- + ProtocolVersion :: proc(display: ^Display) -> i32 --- + ProtocolRevision :: proc(display: ^Display) -> i32 --- + QLength :: proc(display: ^Display) -> i32 --- + ServerVendor :: proc(display: ^Display) -> cstring --- + VendorRelease :: proc(display: ^Display) -> i32 --- // Display macros (display properties) - XBlackPixel :: proc(display: ^Display, screen_no: i32) -> uint --- - XWhitePixel :: proc(display: ^Display, screen_no: i32) -> uint --- - XListDepths :: proc(display: ^Display, screen_no: i32, count: ^i32) -> [^]i32 --- - XDisplayCells :: proc(display: ^Display, screen_no: i32) -> i32 --- - XDisplayPlanes :: proc(display: ^Display, screen_no: i32) -> i32 --- - XScreenOfDisplay :: proc(display: ^Display, screen_no: i32) -> ^Screen --- - XDisplayString :: proc(display: ^Display) -> cstring --- + BlackPixel :: proc(display: ^Display, screen_no: i32) -> uint --- + WhitePixel :: proc(display: ^Display, screen_no: i32) -> uint --- + ListDepths :: proc(display: ^Display, screen_no: i32, count: ^i32) -> [^]i32 --- + DisplayCells :: proc(display: ^Display, screen_no: i32) -> i32 --- + DisplayPlanes :: proc(display: ^Display, screen_no: i32) -> i32 --- + ScreenOfDisplay :: proc(display: ^Display, screen_no: i32) -> ^Screen --- + DisplayString :: proc(display: ^Display) -> cstring --- // Display macros (defaults) - XDefaultColormap :: proc(display: ^Display, screen_no: i32) -> Colormap --- - XDefaultDepth :: proc(display: ^Display) -> i32 --- - XDefaultGC :: proc(display: ^Display, screen_no: i32) -> GC --- - XDefaultRootWindow :: proc(display: ^Display) -> Window --- - XDefaultScreen :: proc(display: ^Display) -> i32 --- - XDefaultVisual :: proc(display: ^Display, screen_no: i32) -> ^Visual --- - XDefaultScreenOfDisplay :: + DefaultColormap :: proc(display: ^Display, screen_no: i32) -> Colormap --- + DefaultDepth :: proc(display: ^Display) -> i32 --- + DefaultGC :: proc(display: ^Display, screen_no: i32) -> GC --- + DefaultRootWindow :: proc(display: ^Display) -> Window --- + DefaultScreen :: proc(display: ^Display) -> i32 --- + DefaultVisual :: proc(display: ^Display, screen_no: i32) -> ^Visual --- + DefaultScreenOfDisplay :: proc(display: ^Display) -> ^Screen --- // Display macros (other) - XRootWindow :: proc(display: ^Display, screen_no: i32) -> Window --- - XScreenCount :: proc(display: ^Display) -> i32 --- + RootWindow :: proc(display: ^Display, screen_no: i32) -> Window --- + ScreenCount :: proc(display: ^Display) -> i32 --- // Display image format macros - XListPixmapFormats :: proc(display: ^Display, count: ^i32) -> [^]XPixmapFormatValues --- - XImageByteOrder :: proc(display: ^Display) -> ByteOrder --- - XBitmapUnit :: proc(display: ^Display) -> i32 --- - XBitmapBitOrder :: proc(display: ^Display) -> ByteOrder --- - XBitmapPad :: proc(display: ^Display) -> i32 --- - XDisplayHeight :: proc(display: ^Display, screen_no: i32) -> i32 --- - XDisplayHeightMM :: proc(display: ^Display, screen_no: i32) -> i32 --- - XDisplayWidth :: proc(display: ^Display, screen_no: i32) -> i32 --- - XDisplayWidthMM :: proc(display: ^Display, screen_no: i32) -> i32 --- + ListPixmapFormats :: proc(display: ^Display, count: ^i32) -> [^]XPixmapFormatValues --- + ImageByteOrder :: proc(display: ^Display) -> ByteOrder --- + BitmapUnit :: proc(display: ^Display) -> i32 --- + BitmapBitOrder :: proc(display: ^Display) -> ByteOrder --- + BitmapPad :: proc(display: ^Display) -> i32 --- + DisplayHeight :: proc(display: ^Display, screen_no: i32) -> i32 --- + DisplayHeightMM :: proc(display: ^Display, screen_no: i32) -> i32 --- + DisplayWidth :: proc(display: ^Display, screen_no: i32) -> i32 --- + DisplayWidthMM :: proc(display: ^Display, screen_no: i32) -> i32 --- // Screen macros - XBlackPixelsOfScreen :: proc(screen: ^Screen) -> uint --- - XWhitePixelsOfScreen :: proc(screen: ^Screen) -> uint --- - XCellsOfScreen :: proc(screen: ^Screen) -> i32 --- - XDefaultColormapOfScreen :: proc(screen: ^Screen) -> Colormap --- - XDefaultDepthOfScreen :: proc(screen: ^Screen) -> i32 --- - XDefaultGCOfScreen :: proc(screen: ^Screen) -> GC --- - XDefaultVisualOfScreen :: proc(screen: ^Screen) -> ^Visual --- - XDoesBackingStore :: proc(screen: ^Screen) -> BackingStore --- - XDoesSaveUnders :: proc(screen: ^Screen) -> b32 --- - XDisplayOfScreen :: proc(screen: ^Screen) -> ^Display --- - XScreenNumberOfScreens :: proc(screen: ^Screen) -> i32 --- - XEventMaskOfScreen :: proc(screen: ^Screen) -> EventMask --- - XWidthOfScreen :: proc(screen: ^Screen) -> i32 --- - XHeightOfScreen :: proc(screen: ^Screen) -> i32 --- - XWidthMMOfScreen :: proc(screen: ^Screen) -> i32 --- - XHeightMMOfScreen :: proc(screen: ^Screen) -> i32 --- - XMaxCmapsOfScreen :: proc(screen: ^Screen) -> i32 --- - XMinCmapsOfScreen :: proc(screen: ^Screen) -> i32 --- - XPlanesOfScreen :: proc(screen: ^Screen) -> i32 --- - XRootWindowOfScreen :: proc(screen: ^Screen) -> Window --- + BlackPixelsOfScreen :: proc(screen: ^Screen) -> uint --- + WhitePixelsOfScreen :: proc(screen: ^Screen) -> uint --- + CellsOfScreen :: proc(screen: ^Screen) -> i32 --- + DefaultColormapOfScreen :: proc(screen: ^Screen) -> Colormap --- + DefaultDepthOfScreen :: proc(screen: ^Screen) -> i32 --- + DefaultGCOfScreen :: proc(screen: ^Screen) -> GC --- + DefaultVisualOfScreen :: proc(screen: ^Screen) -> ^Visual --- + DoesBackingStore :: proc(screen: ^Screen) -> BackingStore --- + DoesSaveUnders :: proc(screen: ^Screen) -> b32 --- + DisplayOfScreen :: proc(screen: ^Screen) -> ^Display --- + ScreenNumberOfScreens :: proc(screen: ^Screen) -> i32 --- + EventMaskOfScreen :: proc(screen: ^Screen) -> EventMask --- + WidthOfScreen :: proc(screen: ^Screen) -> i32 --- + HeightOfScreen :: proc(screen: ^Screen) -> i32 --- + WidthMMOfScreen :: proc(screen: ^Screen) -> i32 --- + HeightMMOfScreen :: proc(screen: ^Screen) -> i32 --- + MaxCmapsOfScreen :: proc(screen: ^Screen) -> i32 --- + MinCmapsOfScreen :: proc(screen: ^Screen) -> i32 --- + PlanesOfScreen :: proc(screen: ^Screen) -> i32 --- + RootWindowOfScreen :: proc(screen: ^Screen) -> Window --- // Threading functions - XInitThreads :: proc() -> Status --- - XLockDisplay :: proc(display: ^Display) --- - XUnlockDisplay :: proc(display: ^Display) --- + InitThreads :: proc() -> Status --- + LockDisplay :: proc(display: ^Display) --- + UnlockDisplay :: proc(display: ^Display) --- // Internal connections - XAddConnectionWatch :: proc( + AddConnectionWatch :: proc( display: ^Display, procedure: XConnectionWatchProc, data: rawptr, ) -> Status --- - XRemoveConnectionWatch :: proc( + RemoveConnectionWatch :: proc( display: ^Display, procedure: XConnectionWatchProc, data: rawptr, ) -> Status --- - XProcessInternalConnections :: proc( + ProcessInternalConnections :: proc( display: ^Display, fd: i32, ) --- - XInternalConnectionNumbers :: proc( + InternalConnectionNumbers :: proc( display: ^Display, fds: ^[^]i32, count: ^i32, ) -> Status --- // Windows functions - XVisualIDFromVisual :: proc(visual: ^Visual) -> VisualID --- + VisualIDFromVisual :: proc(visual: ^Visual) -> VisualID --- // Windows: creation/destruction - XCreateWindow :: proc( + CreateWindow :: proc( display: ^Display, parent: Window, x: i32, @@ -123,7 +123,7 @@ foreign xlib { attr_mask: WindowAttributeMask, attr: ^XSetWindowAttributes, ) -> Window --- - XCreateSimpleWindow :: proc( + CreateSimpleWindow :: proc( display: ^Display, parent: Window, x: i32, @@ -134,34 +134,34 @@ foreign xlib { border: int, bg: int, ) -> Window --- - XDestroyWindow :: proc(display: ^Display, window: Window) --- - XDestroySubwindows :: proc(display: ^Display, window: Window) --- + DestroyWindow :: proc(display: ^Display, window: Window) --- + DestroySubwindows :: proc(display: ^Display, window: Window) --- // Windows: mapping/unmapping - XMapWindow :: proc(display: ^Display, window: Window) --- - XMapRaised :: proc(display: ^Display, window: Window) --- - XMapSubwindows :: proc(display: ^Display, window: Window) --- - XUnmapWindow :: proc(display: ^Display, window: Window) --- - XUnmapSubwindows :: proc(display: ^Display, window: Window) --- + MapWindow :: proc(display: ^Display, window: Window) --- + MapRaised :: proc(display: ^Display, window: Window) --- + MapSubwindows :: proc(display: ^Display, window: Window) --- + UnmapWindow :: proc(display: ^Display, window: Window) --- + UnmapSubwindows :: proc(display: ^Display, window: Window) --- // Windows: configuring - XConfigureWindow :: proc( + ConfigureWindow :: proc( display: ^Display, window: Window, mask: WindowChangesMask, values: XWindowChanges, ) --- - XMoveWindow :: proc( + MoveWindow :: proc( display: ^Display, window: Window, x: i32, y: i32, ) --- - XResizeWindow :: proc( + ResizeWindow :: proc( display: ^Display, window: Window, width: u32, height: u32, ) --- - XMoveResizeWindow :: proc( + MoveResizeWindow :: proc( display: ^Display, window: Window, x: i32, @@ -169,51 +169,51 @@ foreign xlib { width: u32, height: u32, ) --- - XSetWindowBorderWidth :: proc( + SetWindowBorderWidth :: proc( display: ^Display, window: Window, width: u32, ) --- // Window: changing stacking order - XRaiseWindow :: proc(display: ^Display, window: Window) --- - XLowerWindow :: proc(display: ^Display, window: Window) --- - XCirculateSubwindows :: proc(display: ^Display, window: Window, direction: CirculationDirection) --- - XCirculateSubwindowsUp :: proc(display: ^Display, window: Window) --- - XCirculateSubwindowsDown :: proc(display: ^Display, window: Window) --- - XRestackWindows :: proc(display: ^Display, windows: [^]Window, nwindows: i32) --- + RaiseWindow :: proc(display: ^Display, window: Window) --- + LowerWindow :: proc(display: ^Display, window: Window) --- + CirculateSubwindows :: proc(display: ^Display, window: Window, direction: CirculationDirection) --- + CirculateSubwindowsUp :: proc(display: ^Display, window: Window) --- + CirculateSubwindowsDown :: proc(display: ^Display, window: Window) --- + RestackWindows :: proc(display: ^Display, windows: [^]Window, nwindows: i32) --- // Window: changing attributes - XChangeWindowAttributes :: proc( + ChangeWindowAttributes :: proc( display: ^Display, window: Window, attr_mask: WindowAttributeMask, attr: XWindowAttributes, ) --- - XSetWindowBackground :: proc( + SetWindowBackground :: proc( display: ^Display, window: Window, pixel: uint, ) --- - XSetWindowBackgroundMap :: proc( + SetWindowBackgroundMap :: proc( display: ^Display, window: Window, pixmap: Pixmap, ) --- - XSetWindowColormap :: proc( + SetWindowColormap :: proc( display: ^Display, window: Window, colormap: Colormap, ) --- - XDefineCursor :: proc( + DefineCursor :: proc( display: ^Display, window: Window, cursor: Cursor, ) --- - XUndefineCursor :: proc( + UndefineCursor :: proc( display: ^Display, window: Window, ) --- // Windows: querying information - XQueryTree :: proc( + QueryTree :: proc( display: ^Display, window: Window, root: ^Window, @@ -221,12 +221,12 @@ foreign xlib { children: ^[^]Window, nchildren: ^u32, ) -> Status --- - XGetWindowAttributes :: proc( + GetWindowAttributes :: proc( display: ^Display, window: Window, attr: ^XWindowAttributes, ) --- - XGetGeometry :: proc( + GetGeometry :: proc( display: ^Display, drawable: Drawable, root: ^Window, @@ -238,7 +238,7 @@ foreign xlib { depth: ^u32, ) -> Status --- // Windows: translating screen coordinates - XTranslateCoordinates :: proc( + TranslateCoordinates :: proc( display: ^Display, src_window: Window, dst_window: Window, @@ -247,7 +247,7 @@ foreign xlib { dst_x: ^i32, dst_y: ^i32, ) -> b32 --- - XQueryPointer :: proc( + QueryPointer :: proc( display: ^Display, window: Window, root: ^Window, @@ -259,29 +259,29 @@ foreign xlib { mask: ^KeyMask, ) -> b32 --- // Atoms - XInternAtom :: proc( + InternAtom :: proc( display: ^Display, name: cstring, existing: b32, ) -> Atom --- - XInternAtoms :: proc( + InternAtoms :: proc( display: ^Display, names: [^]cstring, count: i32, atoms: [^]Atom, ) -> Status --- - XGetAtomName :: proc( + GetAtomName :: proc( display: ^Display, atom: Atom, ) -> cstring --- - XGetAtomNames :: proc( + GetAtomNames :: proc( display: ^Display, atoms: [^]Atom, count: i32, names: [^]cstring, ) -> Status --- // Windows: Obtaining and changing properties - XGetWindowProperty :: proc( + GetWindowProperty :: proc( display: ^Display, window: Window, property: Atom, @@ -295,12 +295,12 @@ foreign xlib { bytes_after: [^]uint, props: ^rawptr, ) -> i32 --- - XListProperties :: proc( + ListProperties :: proc( display: ^Display, window: Window, num: ^i32, ) -> [^]Atom --- - XChangeProperty :: proc( + ChangeProperty :: proc( display: ^Display, window: Window, property: Atom, @@ -310,30 +310,30 @@ foreign xlib { data: rawptr, count: i32, ) --- - XRotateWindowProperties :: proc( + RotateWindowProperties :: proc( display: ^Display, window: Window, props: [^]Atom, nprops: i32, npos: i32, ) --- - XDeleteProperty :: proc( + DeleteProperty :: proc( display: ^Display, window: Window, prop: Atom, ) --- // Selections - XSetSelectionOwner :: proc( + SetSelectionOwner :: proc( display: ^Display, selection: Atom, owber: Window, time: Time, ) --- - XGetSelectionOwner :: proc( + GetSelectionOwner :: proc( display: ^Display, selection: Atom, ) -> Window --- - XConvertSelection :: proc( + ConvertSelection :: proc( display: ^Display, selection: Atom, target: Atom, @@ -342,23 +342,23 @@ foreign xlib { time: Time, ) --- // Creating and freeing pixmaps - XCreatePixmap :: proc( + CreatePixmap :: proc( display: ^Display, drawable: Drawable, width: u32, height: u32, depth: u32, ) -> Pixmap --- - XFreePixmap :: proc( + FreePixmap :: proc( display: ^Display, pixmap: Pixmap, ) --- // Creating recoloring and freeing cursors - XCreateFontCursor :: proc( + CreateFontCursor :: proc( display: ^Display, shape: CursorShape, ) -> Cursor --- - XCreateGlyphCursor :: proc( + CreateGlyphCursor :: proc( display: ^Display, src_font: Font, mask_font: Font, @@ -367,7 +367,7 @@ foreign xlib { fg: ^XColor, bg: ^XColor, ) -> Cursor --- - XCreatePixmapCursor :: proc( + CreatePixmapCursor :: proc( display: ^Display, source: Pixmap, mask: Pixmap, @@ -376,7 +376,7 @@ foreign xlib { x: u32, y: u32, ) -> Cursor --- - XQueryBestCursor :: proc( + QueryBestCursor :: proc( display: ^Display, drawable: Drawable, width: u32, @@ -384,72 +384,50 @@ foreign xlib { out_width: ^u32, out_height: ^u32, ) -> Status --- - XRecolorCursor :: proc( + RecolorCursor :: proc( display: ^Display, cursor: Cursor, fg: ^XColor, bg: ^XColor, ) --- - XFreeCursor :: proc(display: ^Display, cursor: Cursor) --- + FreeCursor :: proc(display: ^Display, cursor: Cursor) --- // Creation/destruction of colormaps - XCreateColormap :: proc( + CreateColormap :: proc( display: ^Display, window: Window, visual: ^Visual, alloc: ColormapAlloc, ) -> Colormap --- - XCopyColormapAndFree :: proc( + CopyColormapAndFree :: proc( display: ^Display, colormap: Colormap, ) -> Colormap --- - XFreeColormap :: proc( + FreeColormap :: proc( display: ^Display, colormap: Colormap, ) --- // Mapping color names to values - XLookupColor :: proc( + LookupColor :: proc( display: ^Display, colomap: Colormap, name: cstring, exact: ^XColor, screen: ^XColor, ) -> Status --- - XcmsLookupColor :: proc( - display: ^Display, - colormap: Colormap, - name: cstring, - exact: XcmsColor, - screen: XcmsColor, - format: XcmsColorFormat, - ) -> Status --- // Allocating and freeing color cells - XAllocColor :: proc( + AllocColor :: proc( display: ^Display, colormap: Colormap, screen: ^XColor, ) -> Status --- - XcmsAllocColor :: proc( - display: ^Display, - colormap: Colormap, - color: ^XcmsColor, - format: XcmsColorFormat, - ) -> Status --- - XAllocNamedColor :: proc( + AllocNamedColor :: proc( display: ^Display, colormap: Colormap, name: cstring, screen: ^XColor, exact: ^XColor, ) -> Status --- - XcmsAllocNamedColor :: proc( - display: ^Display, - colormap: Colormap, - name: cstring, - screen: ^XcmsColor, - exact: ^XcmsColor, - format: XcmsColorFormat, - ) -> Status --- - XAllocColorCells :: proc( + AllocColorCells :: proc( display: ^Display, colormap: Colormap, contig: b32, @@ -458,7 +436,7 @@ foreign xlib { pixels: [^]uint, npixels: u32, ) -> Status --- - XAllocColorPlanes :: proc( + AllocColorPlanes :: proc( display: ^Display, colormap: Colormap, contig: b32, @@ -471,7 +449,7 @@ foreign xlib { gmask: [^]uint, bmask: [^]uint, ) -> Status --- - XFreeColors :: proc( + FreeColors :: proc( display: ^Display, colormap: Colormap, pixels: [^]uint, @@ -479,17 +457,1155 @@ foreign xlib { planes: uint, ) --- // Modifying and querying colormap cells - XStoreColor :: proc( + StoreColor :: proc( display: ^Display, colormap: Colormap, color: ^XColor, ) --- - XStoreColors :: proc( + StoreColors :: proc( display: ^Display, colormap: Colormap, color: [^]XColor, ncolors: i32, ) --- + // Graphics context functions + CreateGC :: proc( + display: ^Display, + drawable: Drawable, + mask: GCAttributeMask, + attr: ^XGCValues, + ) -> GC --- + CopyGC :: proc( + display: ^Display, + src: GC, + dst: GC, + mask: GCAttributeMask, + ) --- + ChangeGC :: proc( + display: ^Display, + gc: GC, + mask: GCAttributeMask, + values: ^XGCValues, + ) --- + GetGCValues :: proc( + display: ^Display, + gc: GC, + mask: GCAttributeMask, + values: ^XGCValues, + ) -> Status --- + FreeGC :: proc(display: ^Display, gc: GC) --- + GCContextFromGC :: proc(gc: GC) -> GContext --- + FlushGC :: proc(display: ^Display, gc: GC) --- + // Convenience routines for GC + SetState :: proc( + display: ^Display, + gc: GC, + fg: uint, + bg: uint, + fn: GCFunction, + pmask: uint, + ) --- + SetForeground :: proc( + display: ^Display, + gc: GC, + fg: uint, + ) --- + SetBackground :: proc( + display: ^Display, + gc: GC, + bg: uint, + ) --- + SetFunction :: proc( + display: ^Display, + gc: GC, + fn: GCFunction, + ) --- + SetPlaneMask :: proc( + display: ^Display, + gc: GC, + pmask: uint, + ) --- + SetLineAttributes :: proc( + display: ^Display, + gc: GC, + width: u32, + line_style: LineStyle, + cap_style: CapStyle, + join_style: JoinStyle, + ) --- + SetDashes :: proc( + display: ^Display, + gc: GC, + dash_offs: i32, + dash_list: [^]i8, + n: i32, + ) --- + SetFillStyle :: proc( + display: ^Display, + gc: GC, + style: FillStyle, + ) --- + SetFillRule :: proc( + display: ^Display, + gc: GC, + rule: FillRule, + ) --- + QueryBestSize :: proc( + display: ^Display, + class: i32, + which: Drawable, + width: u32, + height: u32, + out_width: ^u32, + out_height: ^u32, + ) -> Status --- + QueryBestTile :: proc( + display: ^Display, + which: Drawable, + width: u32, + height: u32, + out_width: ^u32, + out_height: ^u32, + ) -> Status --- + QueryBestStripple :: proc( + display: ^Display, + which: Drawable, + width: u32, + height: u32, + out_width: u32, + out_height: u32, + ) -> Status --- + SetTile :: proc(display: ^Display, gc: GC, tile: Pixmap) --- + SetStripple :: proc(display: ^Display, gc: GC, stripple: Pixmap) --- + SetTSOrigin :: proc(display: ^Display, gc: GC, x: i32, y: i32) --- + SetFont :: proc(display: ^Display, gc: GC, font: Font) --- + SetClipOrigin :: proc(display: ^Display, gc: GC, x: i32, y: i32) --- + SetClipMask :: proc(display: ^Display, gc: GC, pixmap: Pixmap) --- + SetClipRectangles :: proc( + display: ^Display, + gc: GC, + x: i32, + y: i32, + rects: [^]XRectangle, + n: i32, + ordering: i32, + ) --- + SetArcMode :: proc(display: ^Display, gc: GC, mode: ArcMode) --- + SetSubwindowMode :: proc(display: ^Display, gc: GC, mode: SubwindowMode) --- + SetGraphicsExposures :: proc(display: ^Display, gc: GC, exp: b32) --- + // Graphics functions + ClearArea :: proc( + display: ^Display, + window: Window, + x: i32, + y: i32, + width: u32, + height: u32, + exp: b32, + ) --- + ClearWindow :: proc( + display: ^Display, + window: Window, + ) --- + CopyArea :: proc( + display: ^Display, + src: Drawable, + dst: Drawable, + gc: GC, + src_x: i32, + src_y: i32, + width: u32, + height: u32, + dst_x: i32, + dst_y: i32, + ) --- + CopyPlane :: proc( + display: ^Display, + src: Drawable, + dst: Drawable, + gc: GC, + src_x: i32, + src_y: i32, + width: u32, + height: u32, + dst_x: i32, + dst_y: i32, + plane: uint, + ) --- + // Drawing lines, points, rectangles and arc + DrawPoint :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + ) --- + DrawPoints :: proc( + display: Display, + drawable: Drawable, + gc: GC, + point: [^]XPoint, + npoints: i32, + mode: CoordMode, + ) --- + DrawLine :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x1: i32, + y1: i32, + x2: i32, + y2: i32, + ) --- + DrawLines :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + points: [^]XPoint, + npoints: i32, + ) --- + DrawSegments :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + segs: [^]XSegment, + nsegs: i32, + ) --- + DrawRectangle :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + width: u32, + height: u32, + ) --- + DrawRectangles :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + rects: [^]XRectangle, + nrects: i32, + ) --- + DrawArc :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + width: u32, + height: u32, + angle1: i32, + angle2: i32, + ) --- + DrawArcs :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + arcs: [^]XArc, + narcs: i32, + ) --- + // Filling areas + FillRectangle :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + width: u32, + height: u32, + ) --- + FillRectangles :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + rects: [^]XRectangle, + nrects: i32, + ) --- + FillPolygon :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + points: [^]XPoint, + npoints: i32, + shape: Shape, + mode: CoordMode, + ) --- + FillArc :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + width: u32, + height: u32, + angle1: i32, + angle2: i32, + ) --- + FillArcs :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + arcs: [^]XArc, + narcs: i32, + ) --- + // Font metrics + LoadFont :: proc(display: ^Display, name: cstring) -> Font --- + QueryFont :: proc(display: ^Display, id: XID) -> ^XFontStruct --- + LoadQueryFont :: proc(display: ^Display, name: cstring) -> ^XFontStruct --- + FreeFont :: proc(display: ^Display, font_struct: ^XFontStruct) --- + GetFontProperty :: proc(font_struct: ^XFontStruct, atom: Atom, ret: ^uint) -> b32 --- + UnloadFont :: proc(display: ^Display, font: Font) --- + ListFonts :: proc(display: ^Display, pat: cstring, max: i32, count: ^i32) -> [^]cstring --- + FreeFontNames :: proc(display: ^Display, list: [^]cstring) --- + ListFontsWithInfo :: proc( + display: ^Display, + pat: cstring, + max: i32, + count: ^i32, + info: ^[^]XFontStruct, + ) -> [^]cstring --- + FreeFontInfo :: proc(names: [^]cstring, info: [^]XFontStruct, count: i32) --- + // Computing character string sizes + TextWidth :: proc(font_struct: ^XFontStruct, string: [^]u8, count: i32) -> i32 --- + TextWidth16 :: proc(font_struct: ^XFontStruct, string: [^]XChar2b, count: i32) -> i32 --- + TextExtents :: proc( + font_struct: ^XFontStruct, + string: [^]u8, + nchars: i32, + direction: ^FontDirection, + ascent: ^i32, + descent: ^i32, + ret: ^XCharStruct, + ) --- + TextExtents16 :: proc( + font_struct: ^XFontStruct, + string: [^]XChar2b, + nchars: i32, + direction: ^FontDirection, + ascent: ^i32, + descent: ^i32, + ret: ^XCharStruct, + ) --- + QueryTextExtents :: proc( + display: ^Display, + font_id: XID, + string: [^]u8, + nchars: i32, + direction: ^FontDirection, + ascent: ^i32, + descent: ^i32, + ret: ^XCharStruct, + ) --- + QueryTextExtents16 :: proc( + display: ^Display, + font_id: XID, + string: [^]XChar2b, + nchars: i32, + direction: ^FontDirection, + ascent: ^i32, + descent: ^i32, + ret: ^XCharStruct, + ) --- + // Drawing complex text + DrawText :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + items: XTextItem, + nitems: i32, + ) --- + DrawText16 :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + items: XTextItem16, + nitems: i32, + ) --- + // Drawing text characters + DrawString :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + string: [^]u8, + length: i32, + ) --- + DrawString16 :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + string: [^]XChar2b, + length: i32, + ) --- + DrawImageString :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + string: [^]u8, + length: i32, + ) --- + DrawImageString16 :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + x: i32, + y: i32, + string: [^]XChar2b, + length: i32, + ) --- + // Transferring images between client and server + InitImage :: proc(image: ^XImage) -> Status --- + PutImage :: proc( + display: ^Display, + drawable: Drawable, + gc: GC, + image: ^XImage, + src_x: i32, + src_y: i32, + dst_x: i32, + dst_y: i32, + width: u32, + height: u32, + ) --- + GetImage :: proc( + display: ^Display, + drawable: Drawable, + x: i32, + y: i32, + width: u32, + height: u32, + mask: uint, + format: ImageFormat, + ) -> ^XImage --- + GetSubImage :: proc( + display: ^Display, + drawable: Drawable, + src_x: i32, + src_y: i32, + width: u32, + height: u32, + mask: uint, + format: ImageFormat, + dst: ^XImage, + dst_x: i32, + dst_y: i32, + ) -> ^XImage --- + // Window and session manager functions + ReparentWindow :: proc( + display: ^Display, + window: Window, + parent: Window, + x: i32, + y: i32, + ) --- + ChangeSaveSet :: proc( + display: ^Display, + window: Window, + mode: SaveSetChangeMode, + ) --- + AddToSaveSet :: proc( + display: ^Display, + window: Window, + ) --- + RemoveFromSaveSet :: proc( + display: ^Display, + window: Window, + ) --- + // Managing installed colormaps + InstallColormap :: proc(display: ^Display, colormap: Colormap) --- + UninstallColormap :: proc(display: ^Display, colormap: Colormap) --- + ListInstalledColormaps :: proc(display: ^Display, window: Window, n: ^i32) -> [^]Colormap --- + // Setting and retrieving font search paths + SetFontPath :: proc(display: ^Display, dirs: [^]cstring, ndirs: i32) --- + GetFontPath :: proc(display: ^Display, npaths: ^i32) -> [^]cstring --- + FreeFontPath :: proc(list: [^]cstring) --- + // Grabbing the server + GrabServer :: proc(display: ^Display) --- + UngrabServer :: proc(display: ^Display) --- + // Killing clients + KillClient :: proc(display: ^Display, resource: XID) --- + // Controlling the screen saver + SetScreenSaver :: proc( + display: ^Display, + timeout: i32, + interval: i32, + blanking: ScreenSaverBlanking, + exposures: ScreenSavingExposures, + ) --- + ForceScreenSaver :: proc(display: ^Display, mode: ScreenSaverForceMode) --- + ActivateScreenSaver :: proc(display: ^Display) --- + ResetScreenSaver :: proc(display: ^Display) --- + GetScreenSaver :: proc( + display: ^Display, + timeout: ^i32, + interval: ^i32, + blanking: ^ScreenSaverBlanking, + exposures: ^ScreenSavingExposures, + ) --- + // Controlling host address + AddHost :: proc(display: ^Display, addr: ^XHostAddress) --- + AddHosts :: proc(display: ^Display, hosts: [^]XHostAddress, nhosts: i32) --- + ListHosts :: proc(display: ^Display, nhosts: ^i32, state: [^]b32) -> [^]XHostAddress --- + RemoveHost :: proc(display: ^Display, host: XHostAddress) --- + RemoveHosts :: proc(display: ^Display, hosts: [^]XHostAddress, nhosts: i32) --- + // Access control list + SetAccessControl :: proc(display: ^Display, mode: AccessControlMode) --- + EnableAccessControl :: proc(display: ^Display) --- + DisableAccessControl :: proc(display: ^Display) --- + // Events + SelectInput :: proc(display: ^Display, window: Window, mask: EventMask) --- + Flush :: proc(display: ^Display) --- + Sync :: proc(display: ^Display) --- + EventsQueued :: proc(display: ^Display, mode: EventQueueMode) -> i32 --- + Pending :: proc(display: ^Display) -> i32 --- + NextEvent :: proc(display: ^Display, event: ^XEvent) --- + PeekEvent :: proc(display: ^Display, event: ^XEvent) --- + GetEventData :: proc(display: ^Display, cookie: ^XGenericEventCookie) -> b32 --- + FreeEventData :: proc(display: ^Display, cookie: ^XGenericEventCookie) --- + // Selecting events using a predicate procedure + IfEvent :: proc( + display: ^Display, + event: ^XEvent, + predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, + ctx: rawptr, + ) --- + CheckIfEvent :: proc( + display: ^Display, + event: ^XEvent, + predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, + arg: rawptr, + ) -> b32 --- + PeekIfEvent :: proc( + display: ^Display, + event: ^XEvent, + predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, + ctx: rawptr, + ) --- + // Selecting events using a window or event mask + WindowEvent :: proc( + display: ^Display, + window: Window, + mask: EventMask, + event: ^XEvent, + ) --- + CheckWindowEvent :: proc( + display: ^Display, + window: Window, + mask: EventMask, + event: ^XEvent, + ) -> b32 --- + MaskEvent :: proc( + display: ^Display, + mask: EventMask, + event: ^XEvent, + ) --- + CheckMaskEvent :: proc( + display: ^Display, + mask: EventMask, + event: ^XEvent, + ) -> b32 --- + CheckTypedEvent :: proc( + display: ^Display, + type: EventType, + event: ^XEvent, + ) -> b32 --- + CheckTypedWindowEvent :: proc( + display: ^Display, + window: Window, + type: EventType, + event: ^XEvent, + ) -> b32 --- + // Putting events back + PutBackEvent :: proc( + display: ^Display, + event: ^XEvent, + ) --- + // Sending events to other applications + SendEvent :: proc( + display: ^Display, + window: Window, + propagate: b32, + mask: EventMask, + event: ^XEvent, + ) -> Status --- + // Getting the history of pointer motion + DisplayMotionBufferSize :: proc(display: ^Display) -> uint --- + GetMotionEvents :: proc( + display: ^Display, + window: Window, + start: Time, + stop: Time, + nevents: ^i32, + ) -> [^]XTimeCoord --- + // Enabling or disabling synchronization + SetAfterFunction :: proc( + display: ^Display, + procedure: #type proc "c" (display: ^Display) -> i32, + ) -> i32 --- + Synchronize :: proc( + display: ^Display, + onoff: b32, + ) -> i32 --- + // Error handling + SetErrorHandler :: proc( + handler: #type proc "c" (display: ^Display, event: ^XErrorEvent) -> i32, + ) -> i32 --- + GetErrorText :: proc( + display: ^Display, + code: i32, + buffer: [^]u8, + size: i32, + ) --- + GetErrorDatabaseText :: proc( + display: ^Display, + name: cstring, + message: cstring, + default_string: cstring, + buffer: [^]u8, + size: i32, + ) --- + DisplayName :: proc(string: cstring) -> cstring --- + SetIOErrorHandler :: proc( + handler: #type proc "c" (display: ^Display) -> i32, + ) -> i32 --- + // Pointer grabbing + GrabPointer :: proc( + display: ^Display, + grab_window: Window, + owner_events: b32, + mask: EventMask, + pointer_mode: GrabMode, + keyboard_mode: GrabMode, + confine_to: Window, + cursor: Cursor, + time: Time, + ) -> i32 --- + UngrabPointer :: proc( + display: ^Display, + time: Time, + ) -> i32 --- + ChangeActivePointerGrab :: proc( + display: ^Display, + event_mask: EventMask, + cursor: Cursor, + time: Time, + ) --- + GrabButton :: proc( + display: ^Display, + button: u32, + modifiers: InputMask, + grab_window: Window, + owner_events: b32, + event_mask: EventMask, + pointer_mode: GrabMode, + keyboard_mode: GrabMode, + confine_to: Window, + cursor: Cursor, + ) --- + UngrabButton :: proc( + display: ^Display, + button: u32, + modifiers: InputMask, + grab_window: Window, + ) --- + GrabKeyboard :: proc( + display: ^Display, + grab_window: Window, + owner_events: b32, + pointer_mode: GrabMode, + keyboard_mode: GrabMode, + time: Time, + ) -> i32 --- + UngrabKeyboard :: proc( + display: ^Display, + time: Time, + ) --- + GrabKey :: proc( + display: ^Display, + keycode: i32, + modifiers: InputMask, + grab_window: Window, + owner_events: b32, + pointer_mode: GrabMode, + keyboard_mode: GrabMode, + ) --- + UngrabKey :: proc( + display: ^Display, + keycode: i32, + modifiers: InputMask, + grab_window: Window, + ) --- + // Resuming event processing + AllowEvents :: proc(display: ^Display, evend_mode: AllowEventsMode, time: Time) --- + // Moving the pointer + WarpPointer :: proc( + display: ^Display, + src_window: Window, + dst_window: Window, + src_x: i32, + src_y: i32, + src_width: u32, + src_height: u32, + dst_x: i32, + dst_y: i32, + ) --- + // Controlling input focus + SetInputFocus :: proc( + display: ^Display, + focus: Window, + revert_to: FocusRevert, + time: Time, + ) --- + GetInputFocus :: proc( + display: ^Display, + focus: ^Window, + revert_to: ^FocusRevert, + ) --- + // Manipulating the keyboard and pointer settings + ChangeKeyboardControl :: proc( + display: ^Display, + mask: KeyboardControlMask, + values: ^XKeyboardControl, + ) --- + GetKeyboardControl :: proc( + display: ^Display, + values: ^XKeyboardState, + ) --- + AutoRepeatOn :: proc(display: ^Display) --- + AutoRepeatOff :: proc(display: ^Display) --- + Bell :: proc(display: ^Display, percent: i32) --- + QueryKeymap :: proc(display: ^Display, keys: [^]u32) --- + SetPointerMapping :: proc(display: ^Display, map_should_not_be_a_keyword: [^]u8, nmap: i32) -> i32 --- + GetPointerMapping :: proc(display: ^Display, map_should_not_be_a_keyword: [^]u8, nmap: i32) -> i32 --- + ChangePointerControl :: proc( + display: ^Display, + do_accel: b32, + do_threshold: b32, + accel_numerator: i32, + accel_denominator: i32, + threshold: i32, + ) --- + GetPointerControl :: proc( + display: ^Display, + accel_numerator: ^i32, + accel_denominator: ^i32, + threshold: ^i32, + ) --- + // Manipulating the keyboard encoding + DisplayKeycodes :: proc( + display: ^Display, + min_keycodes: ^i32, + max_keycodes: ^i32, + ) --- + GetKeyboardMapping :: proc( + display: ^Display, + first: KeyCode, + count: i32, + keysyms_per: ^i32, + ) -> ^KeySym --- + ChangeKeyboardMapping :: proc( + display: ^Display, + first: KeyCode, + keysyms_per: i32, + keysyms: [^]KeySym, + num_codes: i32, + ) --- + NewModifiermap :: proc(max_keys_per_mode: i32) -> ^XModifierKeymap --- + InsertModifiermapEntry :: proc( + modmap: ^XModifierKeymap, + keycode_entry: KeyCode, + modifier: i32, + ) -> ^XModifierKeymap --- + DeleteModifiermapEntry :: proc( + modmap: ^XModifierKeymap, + keycode_entry: KeyCode, + modifier: i32, + ) -> ^XModifierKeymap --- + FreeModifiermap :: proc(modmap: ^XModifierKeymap) --- + SetModifierMapping :: proc(display: ^Display, modmap: ^XModifierKeymap) -> i32 --- + GetModifierMapping :: proc(display: ^Display) -> ^XModifierKeymap --- + // Manipulating top-level windows + IconifyWindow :: proc( + dipslay: ^Display, + window: Window, + screen_no: i32, + ) -> Status --- + WithdrawWindow :: proc( + dipslay: ^Display, + window: Window, + screen_no: i32, + ) -> Status --- + ReconfigureWMWindow :: proc( + dipslay: ^Display, + window: Window, + screen_no: i32, + mask: WindowChangesMask, + changes: ^XWindowChanges, + ) -> Status --- + // Getting and setting the WM_NAME property + SetWMName :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) --- + GetWMName :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) -> Status --- + StoreName :: proc( + display: ^Display, + window: Window, + name: cstring, + ) --- + FetchName :: proc( + display: ^Display, + window: Window, + name: ^cstring, + ) -> Status --- + SetWMIconName :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) --- + GetWMIconName :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) -> Status --- + SetIconName :: proc( + display: ^Display, + window: Window, + name: cstring, + ) --- + GetIconName :: proc( + display: ^Display, + window: Window, + prop: ^cstring, + ) -> Status --- + // Setting and reading WM_HINTS property + AllocWMHints :: proc() -> ^XWMHints --- + SetWMHints :: proc( + display: ^Display, + window: Window, + hints: ^XWMHints, + ) --- + GetWMHints :: proc( + display: ^Display, + window: Window, + ) -> ^XWMHints --- + // Setting and reading MW_NORMAL_HINTS property + AllocSizeHints :: proc() -> ^XSizeHints --- + SetWMNormalHints :: proc( + display: ^Display, + window: Window, + hints: ^XSizeHints, + ) --- + GetWMNormalHints :: proc( + display: ^Display, + window: Window, + hints: ^XSizeHints, + flags: ^SizeHints, + ) -> Status --- + SetWMSizeHints :: proc( + display: ^Display, + window: Window, + hints: ^XSizeHints, + prop: Atom, + ) --- + GetWMSizeHints :: proc( + display: ^Display, + window: Window, + hints: ^XSizeHints, + masks: ^SizeHints, + prop: Atom, + ) -> Status --- + // Setting and reading the WM_CLASS property + AllocClassHint :: proc() -> ^XClassHint --- + SetClassHint :: proc( + display: ^Display, + window: Window, + hint: ^XClassHint, + ) --- + GetClassHint :: proc( + display: ^Display, + window: Window, + hint: ^XClassHint, + ) -> Status --- + // Setting and reading WM_TRANSIENT_FOR property + SetTransientForHint :: proc( + display: ^Display, + window: Window, + prop_window: Window, + ) --- + GetTransientForHint :: proc( + display: ^Display, + window: Window, + prop_window: ^Window, + ) -> Status --- + // Setting and reading the WM_PROTOCOLS property + SetWMProtocols :: proc( + display: ^Display, + window: Window, + protocols: [^]Atom, + count: i32, + ) -> Status --- + GetWMProtocols :: proc( + display: ^Display, + window: Window, + protocols: ^[^]Atom, + count: ^i32, + ) -> Status --- + // Setting and reading the WM_COLORMAP_WINDOWS property + SetWMColormapWindows :: proc( + display: ^Display, + window: Window, + colormap_windows: [^]Window, + count: i32, + ) -> Status --- + GetWMColormapWindows :: proc( + display: ^Display, + window: Window, + colormap_windows: ^[^]Window, + count: ^i32, + ) -> Status --- + // Setting and reading the WM_ICON_SIZE_PROPERTY + AllocIconSize :: proc() -> ^XIconSize --- + SetIconSizes :: proc( + display: ^Display, + window: Window, + size_list: [^]XIconSize, + count: i32, + ) --- + GetIconSizes :: proc( + display: ^Display, + window: Window, + size_list: ^[^]XIconSize, + count: ^i32, + ) -> Status --- + // Using window manager convenience functions + mbSetWMProperties :: proc( + display: ^Display, + window: Window, + window_name: cstring, + icon_name: cstring, + argv: [^]cstring, + argc: i32, + normal_hints: ^XSizeHints, + wm_hints: ^XWMHints, + class_hints: ^XClassHint, + ) --- + SetWMProperties :: proc( + display: ^Display, + window: Window, + window_name: ^XTextProperty, + argv: [^]cstring, + argc: i32, + normal_hints: ^XSizeHints, + wm_hints: ^XWMHints, + class_hints: ^XWMHints, + ) --- + // Client to session manager communication + SetCommand :: proc( + display: ^Display, + window: Window, + argv: [^]cstring, + argc: i32, + ) --- + GetCommand :: proc( + display: ^Display, + window: Window, + argv: ^[^]cstring, + argc: ^i32, + ) -> Status --- + SetWMClientMachine :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) --- + GetWMClientMachine :: proc( + display: ^Display, + window: Window, + prop: ^XTextProperty, + ) -> Status --- + SetRGBColormaps :: proc( + display: ^Display, + window: Window, + colormap: ^XStandardColormap, + prop: Atom, + ) --- + GetRGBColormaps :: proc( + display: ^Display, + window: Window, + colormap: ^[^]XStandardColormap, + count: ^i32, + prop: Atom, + ) -> Status --- + // Keyboard utility functions + LookupKeysym :: proc( + event: ^XKeyEvent, + index: i32, + ) -> KeySym --- + KeycodeToKeysym :: proc( + display: ^Display, + keycode: KeyCode, + index: i32, + ) -> KeySym --- + KeysymToKeycode :: proc( + display: ^Display, + keysym: KeySym, + ) -> KeyCode --- + RefreshKeyboardMapping :: proc(event_map: ^XMappingEvent) --- + ConvertCase :: proc( + keysym: KeySym, + lower: ^KeySym, + upper: ^KeySym, + ) --- + StringToKeysym :: proc(str: cstring) -> KeySym --- + KeysymToString :: proc(keysym: KeySym) -> cstring --- + LookupString :: proc( + event: ^XKeyEvent, + buffer: [^]u8, + count: i32, + keysym: ^KeySym, + status: ^XComposeStatus, + ) -> i32 --- + RebindKeysym :: proc( + display: ^Display, + keysym: KeySym, + list: [^]KeySym, + mod_count: i32, + string: [^]u8, + num_bytes: i32, + ) --- + // Allocating permanent storage + Permalloc :: proc(size: u32) -> rawptr --- + // Parsing the window geometry + ParseGeometry :: proc( + parsestring: cstring, + x_ret: ^i32, + y_ret: ^i32, + width: ^u32, + height: ^u32, + ) -> i32 --- + WMGeometry :: proc( + display: ^Display, + screen_no: i32, + user_geom: cstring, + def_geom: cstring, + bwidth: u32, + hints: ^XSizeHints, + x_ret: ^i32, + y_ret: ^i32, + w_ret: ^u32, + h_ret: ^u32, + grav: ^Gravity, + ) -> i32 --- + // Creating, copying and destroying regions + CreateRegion :: proc() -> Region --- + PolygonRegion :: proc( + points: [^]XPoint, + n: i32, + fill: FillRule, + ) -> Region --- + SetRegion :: proc( + display: ^Display, + gc: GC, + region: Region, + ) --- + DestroyRegion :: proc(r: Region) --- + // Moving or shrinking regions + OffsetRegion :: proc(region: Region, dx, dy: i32) --- + ShrinkRegion :: proc(region: Region, dx, dy: i32) --- + // Computing with regions + ClipBox :: proc(region: Region, rect: ^XRectangle) --- + IntersectRegion :: proc(sra, srb, ret: Region) --- + UnionRegion :: proc(sra, srb, ret: Region) --- + UnionRectWithRegion :: proc(rect: ^XRectangle, src, dst: Region) --- + SubtractRegion :: proc(sra, srb, ret: Region) --- + XorRegion :: proc(sra, srb, ret: Region) --- + EmptyRegion :: proc(reg: Region) -> b32 --- + EqualRegion :: proc(a,b: Region) -> b32 --- + PointInRegion :: proc(reg: Region, x,y: i32) -> b32 --- + RectInRegion :: proc(reg: Region, x,y: i32, w,h: u32) -> b32 --- + // Using cut buffers + StoreBytes :: proc(display: ^Display, bytes: [^]u8, nbytes: i32) --- + StoreBuffer :: proc(display: ^Display, bytes: [^]u8, nbytes: i32, buffer: i32) --- + FetchBytes :: proc(display: ^Display, nbytes: ^i32) -> [^]u8 --- + FetchBuffer :: proc(display: ^Display, nbytes: ^i32, buffer: i32) -> [^]u8 --- + // Determining the appropriate visual types + GetVisualInfo :: proc( + display: ^Display, + mask: VisualInfoMask, + info: ^XVisualInfo, + nret: ^i32, + ) -> [^]XVisualInfo --- + MatchVisualInfo :: proc( + display: ^Display, + screen_no: i32, + depth: i32, + class: i32, + ret: ^XVisualInfo, + ) -> Status --- + // Manipulating images + CreateImage :: proc( + display: ^Display, + visual: ^Visual, + depth: u32, + format: ImageFormat, + offset: i32, + data: rawptr, + width: u32, + height: u32, + pad: i32, + stride: i32, + ) -> ^XImage --- + GetPixel :: proc( + image: ^XImage, + x: i32, + y: i32, + ) -> uint --- + PutPixel :: proc( + image: ^XImage, + x: i32, + y: i32, + pixel: uint, + ) --- + SubImage :: proc( + image: ^XImage, + x: i32, + y: i32, + w: u32, + h: u32, + ) -> ^XImage --- + AddPixel :: proc( + image: ^XImage, + value: int, + ) --- + DestroyImage :: proc(image: ^XImage) --- + ResourceManagerString :: proc(display: ^Display) -> cstring --- +} + +@(default_calling_convention="c") +foreign xlib { + XcmsLookupColor :: proc( + display: ^Display, + colormap: Colormap, + name: cstring, + exact: XcmsColor, + screen: XcmsColor, + format: XcmsColorFormat, + ) -> Status --- XcmsStoreColor :: proc( display: ^Display, colormap: Colormap, @@ -502,25 +1618,25 @@ foreign xlib { ncolors: XcmsColor, cflags: [^]b32, ) -> Status --- - XStoreNamedColor :: proc( + StoreNamedColor :: proc( display: ^Display, colormap: Colormap, name: cstring, pixel: uint, flags: ColorFlags, ) --- - XQueryColor :: proc( + QueryColor :: proc( display: ^Display, colormap: Colormap, color: ^XColor, ) --- - XQueryColors :: proc( + QueryColors :: proc( display: ^Display, colormap: Colormap, colors: [^]XColor, ncolors: i32, ) --- - XQueryExtension :: proc( + QueryExtension :: proc( display: ^Display, name: cstring, major_opcode_return: ^i32, @@ -790,1129 +1906,21 @@ foreign xlib { chroma: XcmsFloat, color: ^XcmsColor, ) -> Status --- - // Graphics context functions - XCreateGC :: proc( + XcmsAllocNamedColor :: proc( display: ^Display, - drawable: Drawable, - mask: GCAttributeMask, - attr: ^XGCValues, - ) -> GC --- - XCopyGC :: proc( - display: ^Display, - src: GC, - dst: GC, - mask: GCAttributeMask, - ) --- - XChangeGC :: proc( - display: ^Display, - gc: GC, - mask: GCAttributeMask, - values: ^XGCValues, - ) --- - XGetGCValues :: proc( - display: ^Display, - gc: GC, - mask: GCAttributeMask, - values: ^XGCValues, + colormap: Colormap, + name: cstring, + screen: ^XcmsColor, + exact: ^XcmsColor, + format: XcmsColorFormat, ) -> Status --- - XFreeGC :: proc(display: ^Display, gc: GC) --- - XGCContextFromGC :: proc(gc: GC) -> GContext --- - XFlushGC :: proc(display: ^Display, gc: GC) --- - // Convenience routines for GC - XSetState :: proc( - display: ^Display, - gc: GC, - fg: uint, - bg: uint, - fn: GCFunction, - pmask: uint, - ) --- - XSetForeground :: proc( - display: ^Display, - gc: GC, - fg: uint, - ) --- - XSetBackground :: proc( - display: ^Display, - gc: GC, - bg: uint, - ) --- - XSetFunction :: proc( - display: ^Display, - gc: GC, - fn: GCFunction, - ) --- - XSetPlaneMask :: proc( - display: ^Display, - gc: GC, - pmask: uint, - ) --- - XSetLineAttributes :: proc( - display: ^Display, - gc: GC, - width: u32, - line_style: LineStyle, - cap_style: CapStyle, - join_style: JoinStyle, - ) --- - XSetDashes :: proc( - display: ^Display, - gc: GC, - dash_offs: i32, - dash_list: [^]i8, - n: i32, - ) --- - XSetFillStyle :: proc( - display: ^Display, - gc: GC, - style: FillStyle, - ) --- - XSetFillRule :: proc( - display: ^Display, - gc: GC, - rule: FillRule, - ) --- - XQueryBestSize :: proc( - display: ^Display, - class: i32, - which: Drawable, - width: u32, - height: u32, - out_width: ^u32, - out_height: ^u32, + XcmsAllocColor :: proc( + display: ^Display, + colormap: Colormap, + color: ^XcmsColor, + format: XcmsColorFormat, ) -> Status --- - XQueryBestTile :: proc( - display: ^Display, - which: Drawable, - width: u32, - height: u32, - out_width: ^u32, - out_height: ^u32, - ) -> Status --- - XQueryBestStripple :: proc( - display: ^Display, - which: Drawable, - width: u32, - height: u32, - out_width: u32, - out_height: u32, - ) -> Status --- - XSetTile :: proc(display: ^Display, gc: GC, tile: Pixmap) --- - XSetStripple :: proc(display: ^Display, gc: GC, stripple: Pixmap) --- - XSetTSOrigin :: proc(display: ^Display, gc: GC, x: i32, y: i32) --- - XSetFont :: proc(display: ^Display, gc: GC, font: Font) --- - XSetClipOrigin :: proc(display: ^Display, gc: GC, x: i32, y: i32) --- - XSetClipMask :: proc(display: ^Display, gc: GC, pixmap: Pixmap) --- - XSetClipRectangles :: proc( - display: ^Display, - gc: GC, - x: i32, - y: i32, - rects: [^]XRectangle, - n: i32, - ordering: i32, - ) --- - XSetArcMode :: proc(display: ^Display, gc: GC, mode: ArcMode) --- - XSetSubwindowMode :: proc(display: ^Display, gc: GC, mode: SubwindowMode) --- - XSetGraphicsExposures :: proc(display: ^Display, gc: GC, exp: b32) --- - // Graphics functions - XClearArea :: proc( - display: ^Display, - window: Window, - x: i32, - y: i32, - width: u32, - height: u32, - exp: b32, - ) --- - XClearWindow :: proc( - display: ^Display, - window: Window, - ) --- - XCopyArea :: proc( - display: ^Display, - src: Drawable, - dst: Drawable, - gc: GC, - src_x: i32, - src_y: i32, - width: u32, - height: u32, - dst_x: i32, - dst_y: i32, - ) --- - XCopyPlane :: proc( - display: ^Display, - src: Drawable, - dst: Drawable, - gc: GC, - src_x: i32, - src_y: i32, - width: u32, - height: u32, - dst_x: i32, - dst_y: i32, - plane: uint, - ) --- - // Drawing lines, points, rectangles and arc - XDrawPoint :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - ) --- - XDrawPoints :: proc( - display: Display, - drawable: Drawable, - gc: GC, - point: [^]XPoint, - npoints: i32, - mode: CoordMode, - ) --- - XDrawLine :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x1: i32, - y1: i32, - x2: i32, - y2: i32, - ) --- - XDrawLines :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - points: [^]XPoint, - npoints: i32, - ) --- - XDrawSegments :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - segs: [^]XSegment, - nsegs: i32, - ) --- - XDrawRectangle :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - width: u32, - height: u32, - ) --- - XDrawRectangles :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - rects: [^]XRectangle, - nrects: i32, - ) --- - XDrawArc :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - width: u32, - height: u32, - angle1: i32, - angle2: i32, - ) --- - XDrawArcs :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - arcs: [^]XArc, - narcs: i32, - ) --- - // Filling areas - XFillRectangle :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - width: u32, - height: u32, - ) --- - XFillRectangles :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - rects: [^]XRectangle, - nrects: i32, - ) --- - XFillPolygon :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - points: [^]XPoint, - npoints: i32, - shape: Shape, - mode: CoordMode, - ) --- - XFillArc :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - width: u32, - height: u32, - angle1: i32, - angle2: i32, - ) --- - XFillArcs :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - arcs: [^]XArc, - narcs: i32, - ) --- - // Font metrics - XLoadFont :: proc(display: ^Display, name: cstring) -> Font --- - XQueryFont :: proc(display: ^Display, id: XID) -> ^XFontStruct --- - XLoadQueryFont :: proc(display: ^Display, name: cstring) -> ^XFontStruct --- - XFreeFont :: proc(display: ^Display, font_struct: ^XFontStruct) --- - XGetFontProperty :: proc(font_struct: ^XFontStruct, atom: Atom, ret: ^uint) -> b32 --- - XUnloadFont :: proc(display: ^Display, font: Font) --- - XListFonts :: proc(display: ^Display, pat: cstring, max: i32, count: ^i32) -> [^]cstring --- - XFreeFontNames :: proc(display: ^Display, list: [^]cstring) --- - XListFontsWithInfo :: proc( - display: ^Display, - pat: cstring, - max: i32, - count: ^i32, - info: ^[^]XFontStruct, - ) -> [^]cstring --- - XFreeFontInfo :: proc(names: [^]cstring, info: [^]XFontStruct, count: i32) --- - // Computing character string sizes - XTextWidth :: proc(font_struct: ^XFontStruct, string: [^]u8, count: i32) -> i32 --- - XTextWidth16 :: proc(font_struct: ^XFontStruct, string: [^]XChar2b, count: i32) -> i32 --- - XTextExtents :: proc( - font_struct: ^XFontStruct, - string: [^]u8, - nchars: i32, - direction: ^FontDirection, - ascent: ^i32, - descent: ^i32, - ret: ^XCharStruct, - ) --- - XTextExtents16 :: proc( - font_struct: ^XFontStruct, - string: [^]XChar2b, - nchars: i32, - direction: ^FontDirection, - ascent: ^i32, - descent: ^i32, - ret: ^XCharStruct, - ) --- - XQueryTextExtents :: proc( - display: ^Display, - font_id: XID, - string: [^]u8, - nchars: i32, - direction: ^FontDirection, - ascent: ^i32, - descent: ^i32, - ret: ^XCharStruct, - ) --- - XQueryTextExtents16 :: proc( - display: ^Display, - font_id: XID, - string: [^]XChar2b, - nchars: i32, - direction: ^FontDirection, - ascent: ^i32, - descent: ^i32, - ret: ^XCharStruct, - ) --- - // Drawing complex text - XDrawText :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - items: XTextItem, - nitems: i32, - ) --- - XDrawText16 :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - items: XTextItem16, - nitems: i32, - ) --- - // Drawing text characters - XDrawString :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - string: [^]u8, - length: i32, - ) --- - XDrawString16 :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - string: [^]XChar2b, - length: i32, - ) --- - XDrawImageString :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - string: [^]u8, - length: i32, - ) --- - XDrawImageString16 :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - x: i32, - y: i32, - string: [^]XChar2b, - length: i32, - ) --- - // Transferring images between client and server - XInitImage :: proc(image: ^XImage) -> Status --- - XPutImage :: proc( - display: ^Display, - drawable: Drawable, - gc: GC, - image: ^XImage, - src_x: i32, - src_y: i32, - dst_x: i32, - dst_y: i32, - width: u32, - height: u32, - ) --- - XGetImage :: proc( - display: ^Display, - drawable: Drawable, - x: i32, - y: i32, - width: u32, - height: u32, - mask: uint, - format: ImageFormat, - ) -> ^XImage --- - XGetSubImage :: proc( - display: ^Display, - drawable: Drawable, - src_x: i32, - src_y: i32, - width: u32, - height: u32, - mask: uint, - format: ImageFormat, - dst: ^XImage, - dst_x: i32, - dst_y: i32, - ) -> ^XImage --- - // Window and session manager functions - XReparentWindow :: proc( - display: ^Display, - window: Window, - parent: Window, - x: i32, - y: i32, - ) --- - XChangeSaveSet :: proc( - display: ^Display, - window: Window, - mode: SaveSetChangeMode, - ) --- - XAddToSaveSet :: proc( - display: ^Display, - window: Window, - ) --- - XRemoveFromSaveSet :: proc( - display: ^Display, - window: Window, - ) --- - // Managing installed colormaps - XInstallColormap :: proc(display: ^Display, colormap: Colormap) --- - XUninstallColormap :: proc(display: ^Display, colormap: Colormap) --- - XListInstalledColormaps :: proc(display: ^Display, window: Window, n: ^i32) -> [^]Colormap --- - // Setting and retrieving font search paths - XSetFontPath :: proc(display: ^Display, dirs: [^]cstring, ndirs: i32) --- - XGetFontPath :: proc(display: ^Display, npaths: ^i32) -> [^]cstring --- - XFreeFontPath :: proc(list: [^]cstring) --- - // Grabbing the server - XGrabServer :: proc(display: ^Display) --- - XUngrabServer :: proc(display: ^Display) --- - // Killing clients - XKillClient :: proc(display: ^Display, resource: XID) --- - // Controlling the screen saver - XSetScreenSaver :: proc( - display: ^Display, - timeout: i32, - interval: i32, - blanking: ScreenSaverBlanking, - exposures: ScreenSavingExposures, - ) --- - XForceScreenSaver :: proc(display: ^Display, mode: ScreenSaverForceMode) --- - XActivateScreenSaver :: proc(display: ^Display) --- - XResetScreenSaver :: proc(display: ^Display) --- - XGetScreenSaver :: proc( - display: ^Display, - timeout: ^i32, - interval: ^i32, - blanking: ^ScreenSaverBlanking, - exposures: ^ScreenSavingExposures, - ) --- - // Controlling host address - XAddHost :: proc(display: ^Display, addr: ^XHostAddress) --- - XAddHosts :: proc(display: ^Display, hosts: [^]XHostAddress, nhosts: i32) --- - XListHosts :: proc(display: ^Display, nhosts: ^i32, state: [^]b32) -> [^]XHostAddress --- - XRemoveHost :: proc(display: ^Display, host: XHostAddress) --- - XRemoveHosts :: proc(display: ^Display, hosts: [^]XHostAddress, nhosts: i32) --- - // Access control list - XSetAccessControl :: proc(display: ^Display, mode: AccessControlMode) --- - XEnableAccessControl :: proc(display: ^Display) --- - XDisableAccessControl :: proc(display: ^Display) --- - // Events - XSelectInput :: proc(display: ^Display, window: Window, mask: EventMask) --- - XFlush :: proc(display: ^Display) --- - XSync :: proc(display: ^Display) --- - XEventsQueued :: proc(display: ^Display, mode: EventQueueMode) -> i32 --- - XPending :: proc(display: ^Display) -> i32 --- - XNextEvent :: proc(display: ^Display, event: ^XEvent) --- - XPeekEvent :: proc(display: ^Display, event: ^XEvent) --- - XGetEventData :: proc(display: ^Display, cookie: ^XGenericEventCookie) -> b32 --- - XFreeEventData :: proc(display: ^Display, cookie: ^XGenericEventCookie) --- - // Selecting events using a predicate procedure - XIfEvent :: proc( - display: ^Display, - event: ^XEvent, - predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, - ctx: rawptr, - ) --- - XCheckIfEvent :: proc( - display: ^Display, - event: ^XEvent, - predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, - arg: rawptr, - ) -> b32 --- - XPeekIfEvent :: proc( - display: ^Display, - event: ^XEvent, - predicate: #type proc "c" (display: ^Display, event: ^XEvent, ctx: rawptr) -> b32, - ctx: rawptr, - ) --- - // Selecting events using a window or event mask - XWindowEvent :: proc( - display: ^Display, - window: Window, - mask: EventMask, - event: ^XEvent, - ) --- - XCheckWindowEvent :: proc( - display: ^Display, - window: Window, - mask: EventMask, - event: ^XEvent, - ) -> b32 --- - XMaskEvent :: proc( - display: ^Display, - mask: EventMask, - event: ^XEvent, - ) --- - XCheckMaskEvent :: proc( - display: ^Display, - mask: EventMask, - event: ^XEvent, - ) -> b32 --- - XCheckTypedEvent :: proc( - display: ^Display, - type: EventType, - event: ^XEvent, - ) -> b32 --- - XCheckTypedWindowEvent :: proc( - display: ^Display, - window: Window, - type: EventType, - event: ^XEvent, - ) -> b32 --- - // Putting events back - XPutBackEvent :: proc( - display: ^Display, - event: ^XEvent, - ) --- - // Sending events to other applications - XSendEvent :: proc( - display: ^Display, - window: Window, - propagate: b32, - mask: EventMask, - event: ^XEvent, - ) -> Status --- - // Getting the history of pointer motion - XDisplayMotionBufferSize :: proc(display: ^Display) -> uint --- - XGetMotionEvents :: proc( - display: ^Display, - window: Window, - start: Time, - stop: Time, - nevents: ^i32, - ) -> [^]XTimeCoord --- - // Enabling or disabling synchronization - XSetAfterFunction :: proc( - display: ^Display, - procedure: #type proc "c" (display: ^Display) -> i32, - ) -> i32 --- - XSynchronize :: proc( - display: ^Display, - onoff: b32, - ) -> i32 --- - // Error handling - XSetErrorHandler :: proc( - handler: #type proc "c" (display: ^Display, event: ^XErrorEvent) -> i32, - ) -> i32 --- - XGetErrorText :: proc( - display: ^Display, - code: i32, - buffer: [^]u8, - size: i32, - ) --- - XGetErrorDatabaseText :: proc( - display: ^Display, - name: cstring, - message: cstring, - default_string: cstring, - buffer: [^]u8, - size: i32, - ) --- - XDisplayName :: proc(string: cstring) -> cstring --- - XSetIOErrorHandler :: proc( - handler: #type proc "c" (display: ^Display) -> i32, - ) -> i32 --- - // Pointer grabbing - XGrabPointer :: proc( - display: ^Display, - grab_window: Window, - owner_events: b32, - mask: EventMask, - pointer_mode: GrabMode, - keyboard_mode: GrabMode, - confine_to: Window, - cursor: Cursor, - time: Time, - ) -> i32 --- - XUngrabPointer :: proc( - display: ^Display, - time: Time, - ) -> i32 --- - XChangeActivePointerGrab :: proc( - display: ^Display, - event_mask: EventMask, - cursor: Cursor, - time: Time, - ) --- - XGrabButton :: proc( - display: ^Display, - button: u32, - modifiers: InputMask, - grab_window: Window, - owner_events: b32, - event_mask: EventMask, - pointer_mode: GrabMode, - keyboard_mode: GrabMode, - confine_to: Window, - cursor: Cursor, - ) --- - XUngrabButton :: proc( - display: ^Display, - button: u32, - modifiers: InputMask, - grab_window: Window, - ) --- - XGrabKeyboard :: proc( - display: ^Display, - grab_window: Window, - owner_events: b32, - pointer_mode: GrabMode, - keyboard_mode: GrabMode, - time: Time, - ) -> i32 --- - XUngrabKeyboard :: proc( - display: ^Display, - time: Time, - ) --- - XGrabKey :: proc( - display: ^Display, - keycode: i32, - modifiers: InputMask, - grab_window: Window, - owner_events: b32, - pointer_mode: GrabMode, - keyboard_mode: GrabMode, - ) --- - XUngrabKey :: proc( - display: ^Display, - keycode: i32, - modifiers: InputMask, - grab_window: Window, - ) --- - // Resuming event processing - XAllowEvents :: proc(display: ^Display, evend_mode: AllowEventsMode, time: Time) --- - // Moving the pointer - XWarpPointer :: proc( - display: ^Display, - src_window: Window, - dst_window: Window, - src_x: i32, - src_y: i32, - src_width: u32, - src_height: u32, - dst_x: i32, - dst_y: i32, - ) --- - // Controlling input focus - XSetInputFocus :: proc( - display: ^Display, - focus: Window, - revert_to: FocusRevert, - time: Time, - ) --- - XGetInputFocus :: proc( - display: ^Display, - focus: ^Window, - revert_to: ^FocusRevert, - ) --- - // Manipulating the keyboard and pointer settings - XChangeKeyboardControl :: proc( - display: ^Display, - mask: KeyboardControlMask, - values: ^XKeyboardControl, - ) --- - XGetKeyboardControl :: proc( - display: ^Display, - values: ^XKeyboardState, - ) --- - XAutoRepeatOn :: proc(display: ^Display) --- - XAutoRepeatOff :: proc(display: ^Display) --- - XBell :: proc(display: ^Display, percent: i32) --- - XQueryKeymap :: proc(display: ^Display, keys: [^]u32) --- - XSetPointerMapping :: proc(display: ^Display, map_should_not_be_a_keyword: [^]u8, nmap: i32) -> i32 --- - XGetPointerMapping :: proc(display: ^Display, map_should_not_be_a_keyword: [^]u8, nmap: i32) -> i32 --- - XChangePointerControl :: proc( - display: ^Display, - do_accel: b32, - do_threshold: b32, - accel_numerator: i32, - accel_denominator: i32, - threshold: i32, - ) --- - XGetPointerControl :: proc( - display: ^Display, - accel_numerator: ^i32, - accel_denominator: ^i32, - threshold: ^i32, - ) --- - // Manipulating the keyboard encoding - XDisplayKeycodes :: proc( - display: ^Display, - min_keycodes: ^i32, - max_keycodes: ^i32, - ) --- - XGetKeyboardMapping :: proc( - display: ^Display, - first: KeyCode, - count: i32, - keysyms_per: ^i32, - ) -> ^KeySym --- - XChangeKeyboardMapping :: proc( - display: ^Display, - first: KeyCode, - keysyms_per: i32, - keysyms: [^]KeySym, - num_codes: i32, - ) --- - XNewModifiermap :: proc(max_keys_per_mode: i32) -> ^XModifierKeymap --- - XInsertModifiermapEntry :: proc( - modmap: ^XModifierKeymap, - keycode_entry: KeyCode, - modifier: i32, - ) -> ^XModifierKeymap --- - XDeleteModifiermapEntry :: proc( - modmap: ^XModifierKeymap, - keycode_entry: KeyCode, - modifier: i32, - ) -> ^XModifierKeymap --- - XFreeModifiermap :: proc(modmap: ^XModifierKeymap) --- - XSetModifierMapping :: proc(display: ^Display, modmap: ^XModifierKeymap) -> i32 --- - XGetModifierMapping :: proc(display: ^Display) -> ^XModifierKeymap --- - // Manipulating top-level windows - XIconifyWindow :: proc( - dipslay: ^Display, - window: Window, - screen_no: i32, - ) -> Status --- - XWithdrawWindow :: proc( - dipslay: ^Display, - window: Window, - screen_no: i32, - ) -> Status --- - XReconfigureWMWindow :: proc( - dipslay: ^Display, - window: Window, - screen_no: i32, - mask: WindowChangesMask, - changes: ^XWindowChanges, - ) -> Status --- - // Getting and setting the WM_NAME property - XSetWMName :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) --- - XGetWMName :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) -> Status --- - XStoreName :: proc( - display: ^Display, - window: Window, - name: cstring, - ) --- - XFetchName :: proc( - display: ^Display, - window: Window, - name: ^cstring, - ) -> Status --- - XSetWMIconName :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) --- - XGetWMIconName :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) -> Status --- - XSetIconName :: proc( - display: ^Display, - window: Window, - name: cstring, - ) --- - XGetIconName :: proc( - display: ^Display, - window: Window, - prop: ^cstring, - ) -> Status --- - // Setting and reading WM_HINTS property - XAllocWMHints :: proc() -> ^XWMHints --- - XSetWMHints :: proc( - display: ^Display, - window: Window, - hints: ^XWMHints, - ) --- - XGetWMHints :: proc( - display: ^Display, - window: Window, - ) -> ^XWMHints --- - // Setting and reading MW_NORMAL_HINTS property - XAllocSizeHints :: proc() -> ^XSizeHints --- - XSetWMNormalHints :: proc( - display: ^Display, - window: Window, - hints: ^XSizeHints, - ) --- - XGetWMNormalHints :: proc( - display: ^Display, - window: Window, - hints: ^XSizeHints, - flags: ^SizeHints, - ) -> Status --- - XSetWMSizeHints :: proc( - display: ^Display, - window: Window, - hints: ^XSizeHints, - prop: Atom, - ) --- - XGetWMSizeHints :: proc( - display: ^Display, - window: Window, - hints: ^XSizeHints, - masks: ^SizeHints, - prop: Atom, - ) -> Status --- - // Setting and reading the WM_CLASS property - XAllocClassHint :: proc() -> ^XClassHint --- - XSetClassHint :: proc( - display: ^Display, - window: Window, - hint: ^XClassHint, - ) --- - XGetClassHint :: proc( - display: ^Display, - window: Window, - hint: ^XClassHint, - ) -> Status --- - // Setting and reading WM_TRANSIENT_FOR property - XSetTransientForHint :: proc( - display: ^Display, - window: Window, - prop_window: Window, - ) --- - XGetTransientForHint :: proc( - display: ^Display, - window: Window, - prop_window: ^Window, - ) -> Status --- - // Setting and reading the WM_PROTOCOLS property - XSetWMProtocols :: proc( - display: ^Display, - window: Window, - protocols: [^]Atom, - count: i32, - ) -> Status --- - XGetWMProtocols :: proc( - display: ^Display, - window: Window, - protocols: ^[^]Atom, - count: ^i32, - ) -> Status --- - // Setting and reading the WM_COLORMAP_WINDOWS property - XSetWMColormapWindows :: proc( - display: ^Display, - window: Window, - colormap_windows: [^]Window, - count: i32, - ) -> Status --- - XGetWMColormapWindows :: proc( - display: ^Display, - window: Window, - colormap_windows: ^[^]Window, - count: ^i32, - ) -> Status --- - // Setting and reading the WM_ICON_SIZE_PROPERTY - XAllocIconSize :: proc() -> ^XIconSize --- - XSetIconSizes :: proc( - display: ^Display, - window: Window, - size_list: [^]XIconSize, - count: i32, - ) --- - XGetIconSizes :: proc( - display: ^Display, - window: Window, - size_list: ^[^]XIconSize, - count: ^i32, - ) -> Status --- - // Using window manager convenience functions - XmbSetWMProperties :: proc( - display: ^Display, - window: Window, - window_name: cstring, - icon_name: cstring, - argv: [^]cstring, - argc: i32, - normal_hints: ^XSizeHints, - wm_hints: ^XWMHints, - class_hints: ^XClassHint, - ) --- - XSetWMProperties :: proc( - display: ^Display, - window: Window, - window_name: ^XTextProperty, - argv: [^]cstring, - argc: i32, - normal_hints: ^XSizeHints, - wm_hints: ^XWMHints, - class_hints: ^XWMHints, - ) --- - // Client to session manager communication - XSetCommand :: proc( - display: ^Display, - window: Window, - argv: [^]cstring, - argc: i32, - ) --- - XGetCommand :: proc( - display: ^Display, - window: Window, - argv: ^[^]cstring, - argc: ^i32, - ) -> Status --- - XSetWMClientMachine :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) --- - XGetWMClientMachine :: proc( - display: ^Display, - window: Window, - prop: ^XTextProperty, - ) -> Status --- - XSetRGBColormaps :: proc( - display: ^Display, - window: Window, - colormap: ^XStandardColormap, - prop: Atom, - ) --- - XGetRGBColormaps :: proc( - display: ^Display, - window: Window, - colormap: ^[^]XStandardColormap, - count: ^i32, - prop: Atom, - ) -> Status --- - // Keyboard utility functions - XLookupKeysym :: proc( - event: ^XKeyEvent, - index: i32, - ) -> KeySym --- - XKeycodeToKeysym :: proc( - display: ^Display, - keycode: KeyCode, - index: i32, - ) -> KeySym --- - XKeysymToKeycode :: proc( - display: ^Display, - keysym: KeySym, - ) -> KeyCode --- - XRefreshKeyboardMapping :: proc(event_map: ^XMappingEvent) --- - XConvertCase :: proc( - keysym: KeySym, - lower: ^KeySym, - upper: ^KeySym, - ) --- - XStringToKeysym :: proc(str: cstring) -> KeySym --- - XKeysymToString :: proc(keysym: KeySym) -> cstring --- - XLookupString :: proc( - event: ^XKeyEvent, - buffer: [^]u8, - count: i32, - keysym: ^KeySym, - status: ^XComposeStatus, - ) -> i32 --- - XRebindKeysym :: proc( - display: ^Display, - keysym: KeySym, - list: [^]KeySym, - mod_count: i32, - string: [^]u8, - num_bytes: i32, - ) --- - // Allocating permanent storage - XPermalloc :: proc(size: u32) -> rawptr --- - // Parsing the window geometry - XParseGeometry :: proc( - parsestring: cstring, - x_ret: ^i32, - y_ret: ^i32, - width: ^u32, - height: ^u32, - ) -> i32 --- - XWMGeometry :: proc( - display: ^Display, - screen_no: i32, - user_geom: cstring, - def_geom: cstring, - bwidth: u32, - hints: ^XSizeHints, - x_ret: ^i32, - y_ret: ^i32, - w_ret: ^u32, - h_ret: ^u32, - grav: ^Gravity, - ) -> i32 --- - // Creating, copying and destroying regions - XCreateRegion :: proc() -> Region --- - XPolygonRegion :: proc( - points: [^]XPoint, - n: i32, - fill: FillRule, - ) -> Region --- - XSetRegion :: proc( - display: ^Display, - gc: GC, - region: Region, - ) --- - XDestroyRegion :: proc(r: Region) --- - // Moving or shrinking regions - XOffsetRegion :: proc(region: Region, dx, dy: i32) --- - XShrinkRegion :: proc(region: Region, dx, dy: i32) --- - // Computing with regions - XClipBox :: proc(region: Region, rect: ^XRectangle) --- - XIntersectRegion :: proc(sra, srb, ret: Region) --- - XUnionRegion :: proc(sra, srb, ret: Region) --- - XUnionRectWithRegion :: proc(rect: ^XRectangle, src, dst: Region) --- - XSubtractRegion :: proc(sra, srb, ret: Region) --- - XXorRegion :: proc(sra, srb, ret: Region) --- - XEmptyRegion :: proc(reg: Region) -> b32 --- - XEqualRegion :: proc(a,b: Region) -> b32 --- - XPointInRegion :: proc(reg: Region, x,y: i32) -> b32 --- - XRectInRegion :: proc(reg: Region, x,y: i32, w,h: u32) -> b32 --- - // Using cut buffers - XStoreBytes :: proc(display: ^Display, bytes: [^]u8, nbytes: i32) --- - XStoreBuffer :: proc(display: ^Display, bytes: [^]u8, nbytes: i32, buffer: i32) --- - XFetchBytes :: proc(display: ^Display, nbytes: ^i32) -> [^]u8 --- - XFetchBuffer :: proc(display: ^Display, nbytes: ^i32, buffer: i32) -> [^]u8 --- - // Determining the appropriate visual types - XGetVisualInfo :: proc( - display: ^Display, - mask: VisualInfoMask, - info: ^XVisualInfo, - nret: ^i32, - ) -> [^]XVisualInfo --- - XMatchVisualInfo :: proc( - display: ^Display, - screen_no: i32, - depth: i32, - class: i32, - ret: ^XVisualInfo, - ) -> Status --- - // Manipulating images - XCreateImage :: proc( - display: ^Display, - visual: ^Visual, - depth: u32, - format: ImageFormat, - offset: i32, - data: rawptr, - width: u32, - height: u32, - pad: i32, - stride: i32, - ) -> ^XImage --- - XGetPixel :: proc( - image: ^XImage, - x: i32, - y: i32, - ) -> uint --- - XPutPixel :: proc( - image: ^XImage, - x: i32, - y: i32, - pixel: uint, - ) --- - XSubImage :: proc( - image: ^XImage, - x: i32, - y: i32, - w: u32, - h: u32, - ) -> ^XImage --- - XAddPixel :: proc( - image: ^XImage, - value: int, - ) --- - XDestroyImage :: proc(image: ^XImage) --- + XrmInitialize :: proc() --- + XrmGetStringDatabase :: proc(data: cstring) -> XrmDatabase --- + XrmGetResource :: proc(db: XrmDatabase, name: cstring, class: cstring, type_return: ^cstring, val_return: ^XrmValue) -> b32 --- } From 303d86ab7e54abb084bb96a556b6ff6d4a2d0f63 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 4 Jun 2024 11:26:57 +0200 Subject: [PATCH 141/270] Complete nightly scripts update to new b2 cli version --- ci/create_nightly_json.py | 5 ++--- ci/delete_old_binaries.py | 6 ++---- ci/upload_create_nightly.sh | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/ci/create_nightly_json.py b/ci/create_nightly_json.py index d7029399a..03c653040 100644 --- a/ci/create_nightly_json.py +++ b/ci/create_nightly_json.py @@ -13,7 +13,7 @@ def main(): for x in files_lines: parts = x.split(" ", 1) if parts[0]: - json_str = execute_cli(f"b2 get-file-info {parts[0]}") + json_str = execute_cli(f"b2 file info {parts[0]}") data = json.loads(json_str) name = remove_prefix(data['fileName'], "nightly/") url = f"https://f001.backblazeb2.com/file/{bucket}/nightly/{urllib.parse.quote_plus(name)}" @@ -47,5 +47,4 @@ def execute_cli(command): return sb.stdout.read().decode("utf-8"); if __name__ == '__main__': - sys.exit(main()) - + sys.exit(main()) \ No newline at end of file diff --git a/ci/delete_old_binaries.py b/ci/delete_old_binaries.py index 39e8ff2ac..6ea3cb78d 100644 --- a/ci/delete_old_binaries.py +++ b/ci/delete_old_binaries.py @@ -12,7 +12,6 @@ def main(): print(f"Looking for binaries to delete older than {days_to_keep} days") files_lines = execute_cli(f"b2 ls --long --versions b2://{bucket}/nightly/").split("\n") - print(files_lines) for x in files_lines: parts = [y for y in x.split(' ') if y] @@ -23,7 +22,7 @@ def main(): if delta.days > days_to_keep: print(f'Deleting {parts[5]}') - execute_cli(f'b2 delete-file-version {parts[0]}') + execute_cli(f'b2 rm {parts[0]}') def execute_cli(command): @@ -31,5 +30,4 @@ def execute_cli(command): return sb.stdout.read().decode("utf-8"); if __name__ == '__main__': - sys.exit(main()) - + sys.exit(main()) \ No newline at end of file diff --git a/ci/upload_create_nightly.sh b/ci/upload_create_nightly.sh index a4122f1da..8404b33ff 100755 --- a/ci/upload_create_nightly.sh +++ b/ci/upload_create_nightly.sh @@ -22,4 +22,4 @@ else 7z a -bd "output/$filename" -r "$artifact" fi -b2 upload-file "$bucket" "output/$filename" "nightly/$filename" +b2 file upload "$bucket" "output/$filename" "nightly/$filename" From 52ba3357ee75fbaaf8676fe15307c148eddeb16c Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Tue, 4 Jun 2024 04:48:24 -0700 Subject: [PATCH 142/270] oops, missed shuffling a few x-funcs --- vendor/x11/xlib/xlib_procs.odin | 59 ++++++++++++++++----------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/vendor/x11/xlib/xlib_procs.odin b/vendor/x11/xlib/xlib_procs.odin index 5465cd71b..735ddc9c4 100644 --- a/vendor/x11/xlib/xlib_procs.odin +++ b/vendor/x11/xlib/xlib_procs.odin @@ -1592,6 +1592,31 @@ foreign xlib { image: ^XImage, value: int, ) --- + StoreNamedColor :: proc( + display: ^Display, + colormap: Colormap, + name: cstring, + pixel: uint, + flags: ColorFlags, + ) --- + QueryColor :: proc( + display: ^Display, + colormap: Colormap, + color: ^XColor, + ) --- + QueryColors :: proc( + display: ^Display, + colormap: Colormap, + colors: [^]XColor, + ncolors: i32, + ) --- + QueryExtension :: proc( + display: ^Display, + name: cstring, + major_opcode_return: ^i32, + first_event_return: ^i32, + first_error_return: ^i32, + ) -> b32 --- DestroyImage :: proc(image: ^XImage) --- ResourceManagerString :: proc(display: ^Display) -> cstring --- } @@ -1618,31 +1643,6 @@ foreign xlib { ncolors: XcmsColor, cflags: [^]b32, ) -> Status --- - StoreNamedColor :: proc( - display: ^Display, - colormap: Colormap, - name: cstring, - pixel: uint, - flags: ColorFlags, - ) --- - QueryColor :: proc( - display: ^Display, - colormap: Colormap, - color: ^XColor, - ) --- - QueryColors :: proc( - display: ^Display, - colormap: Colormap, - colors: [^]XColor, - ncolors: i32, - ) --- - QueryExtension :: proc( - display: ^Display, - name: cstring, - major_opcode_return: ^i32, - first_event_return: ^i32, - first_error_return: ^i32, - ) -> b32 --- XcmsQueryColor :: proc( display: ^Display, colormap: Colormap, @@ -1669,12 +1669,9 @@ foreign xlib { // Color conversion context macros XcmsDisplayOfCCC :: proc(ccc: XcmsCCC) -> ^Display --- XcmsVisualOfCCC :: proc(ccc: XcmsCCC) -> ^Visual --- - XcmsScreenNumberOfCCC :: - proc(ccc: XcmsCCC) -> i32 --- - XcmsScreenWhitePointOfCCC :: - proc(ccc: XcmsCCC) -> XcmsColor --- - XcmsClientWhitePointOfCCC :: - proc(ccc: XcmsCCC) -> XcmsColor --- + XcmsScreenNumberOfCCC :: proc(ccc: XcmsCCC) -> i32 --- + XcmsScreenWhitePointOfCCC :: proc(ccc: XcmsCCC) -> XcmsColor --- + XcmsClientWhitePointOfCCC :: proc(ccc: XcmsCCC) -> XcmsColor --- // Modifying the attributes of color conversion context XcmsSetWhitePoint :: proc( ccc: XcmsCCC, From 7d293898348173f4a8c373c037b3035f83f9dd13 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 4 Jun 2024 14:21:51 +0200 Subject: [PATCH 143/270] fix debug info IR error on LLVM < 13 --- src/llvm_backend_debug.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index 2654a1d28..afbf3e046 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -46,6 +46,15 @@ gb_internal LLVMMetadataRef lb_debug_end_location_from_ast(lbProcedure *p, Ast * return lb_debug_location_from_token_pos(p, ast_end_token(node).pos); } +gb_internal void lb_debug_file_line(lbModule *m, Ast *node, LLVMMetadataRef *file, unsigned *line) { + if (*file == nullptr) { + if (node) { + *file = lb_get_llvm_metadata(m, node->file()); + *line = cast(unsigned)ast_token(node).pos.line; + } + } +} + gb_internal LLVMMetadataRef lb_debug_type_internal_proc(lbModule *m, Type *type) { i64 size = type_size_of(type); // Check size gb_unused(size); @@ -117,6 +126,8 @@ gb_internal LLVMMetadataRef lb_debug_basic_struct(lbModule *m, String const &nam gb_internal LLVMMetadataRef lb_debug_struct(lbModule *m, Type *type, Type *bt, String name, LLVMMetadataRef scope, LLVMMetadataRef file, unsigned line) { GB_ASSERT(bt->kind == Type_Struct); + lb_debug_file_line(m, bt->Struct.node, &file, &line); + unsigned tag = DW_TAG_structure_type; if (is_type_raw_union(bt)) { tag = DW_TAG_union_type; @@ -336,6 +347,8 @@ gb_internal LLVMMetadataRef lb_debug_union(lbModule *m, Type *type, String name, Type *bt = base_type(type); GB_ASSERT(bt->kind == Type_Union); + lb_debug_file_line(m, bt->Union.node, &file, &line); + u64 size_in_bits = 8*type_size_of(bt); u32 align_in_bits = 8*cast(u32)type_align_of(bt); @@ -415,6 +428,8 @@ gb_internal LLVMMetadataRef lb_debug_bitset(lbModule *m, Type *type, String name Type *bt = base_type(type); GB_ASSERT(bt->kind == Type_BitSet); + lb_debug_file_line(m, bt->BitSet.node, &file, &line); + u64 size_in_bits = 8*type_size_of(bt); u32 align_in_bits = 8*cast(u32)type_align_of(bt); @@ -494,6 +509,8 @@ gb_internal LLVMMetadataRef lb_debug_enum(lbModule *m, Type *type, String name, Type *bt = base_type(type); GB_ASSERT(bt->kind == Type_Enum); + lb_debug_file_line(m, bt->Enum.node, &file, &line); + u64 size_in_bits = 8*type_size_of(bt); u32 align_in_bits = 8*cast(u32)type_align_of(bt); From 606608c02bb7271717245c0c66c302d5aa046477 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 4 Jun 2024 15:49:17 +0100 Subject: [PATCH 144/270] Utilize `foreign import` constant strings for foreign imports --- vendor/raylib/raygui.odin | 42 ++++-------------- vendor/raylib/raylib.odin | 89 ++++++++++----------------------------- vendor/raylib/rlgl.odin | 19 +++------ 3 files changed, 36 insertions(+), 114 deletions(-) diff --git a/vendor/raylib/raygui.odin b/vendor/raylib/raygui.odin index 41a4250a1..18f1a9c51 100644 --- a/vendor/raylib/raygui.odin +++ b/vendor/raylib/raygui.odin @@ -5,47 +5,21 @@ import "core:c" RAYGUI_SHARED :: #config(RAYGUI_SHARED, false) when ODIN_OS == .Windows { - when RAYGUI_SHARED { - foreign import lib { - "windows/rayguidll.lib", - } - } else { - foreign import lib { - "windows/raygui.lib", - } + foreign import lib { + "windows/rayguidll.lib" when RAYGUI_SHARED else "windows/raygui.lib", } } else when ODIN_OS == .Linux { - when RAYGUI_SHARED { - foreign import lib "linux/libraygui.so" - } else { - foreign import lib "linux/libraygui.a" + foreign import lib { + "linux/libraygui.so" when RAYGUI_SHARED else "linux/libraygui.a" } } else when ODIN_OS == .Darwin { when ODIN_ARCH == .arm64 { - when RAYGUI_SHARED { - foreign import lib { - "macos-arm64/libraygui.dylib", - } - } else { - foreign import lib { - "macos-arm64/libraygui.a", - // "system:Cocoa.framework", - // "system:OpenGL.framework", - // "system:IOKit.framework", - } + foreign import lib { + "macos-arm64/libraygui.dylib" when RAYGUI_SHARED else "macos-arm64/libraygui.a", } } else { - when RAYGUI_SHARED { - foreign import lib { - "macos/libraygui.dylib", - } - } else { - foreign import lib { - "macos/libraygui.a", - // "system:Cocoa.framework", - // "system:OpenGL.framework", - // "system:IOKit.framework", - } + foreign import lib { + "macos/libraygui.dylib" when RAYGUI_SHARED else "macos/libraygui.a", } } } else { diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index 1d9451b3d..730575a2c 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -97,76 +97,33 @@ MAX_TEXT_BUFFER_LENGTH :: #config(RAYLIB_MAX_TEXT_BUFFER_LENGTH, 1024) RAYLIB_SHARED :: #config(RAYLIB_SHARED, false) when ODIN_OS == .Windows { - when RAYLIB_SHARED { - @(extra_linker_flags="/NODEFAULTLIB:msvcrt") - foreign import lib { - "windows/raylibdll.lib", - "system:Winmm.lib", - "system:Gdi32.lib", - "system:User32.lib", - "system:Shell32.lib", - } - } else { - @(extra_linker_flags="/NODEFAULTLIB:libcmt") - foreign import lib { - "windows/raylib.lib", - "system:Winmm.lib", - "system:Gdi32.lib", - "system:User32.lib", - "system:Shell32.lib", - } + _WINDOWS_LIB :: "windows/raylib" +" dll" when RAYLIB_SHARED else "" + ".lib" + @(extra_linker_flags="/NODEFAULTLIB:msvcrt") + foreign import lib { + "windows/raylibdll.lib" when RAYLIB_SHARED else "windows/raylib.lib" , + "system:Winmm.lib", + "system:Gdi32.lib", + "system:User32.lib", + "system:Shell32.lib", } } else when ODIN_OS == .Linux { - when RAYLIB_SHARED { - foreign import lib { - // Note(bumbread): I'm not sure why in `linux/` folder there are - // multiple copies of raylib.so, but since these bindings are for - // particular version of the library, I better specify it. Ideally, - // though, it's best specified in terms of major (.so.4) - "linux/libraylib.so.500", - "system:dl", - "system:pthread", - } - } else { - foreign import lib { - "linux/libraylib.a", - "system:dl", - "system:pthread", - } + foreign import lib { + // Note(bumbread): I'm not sure why in `linux/` folder there are + // multiple copies of raylib.so, but since these bindings are for + // particular version of the library, I better specify it. Ideally, + // though, it's best specified in terms of major (.so.4) + "linux/libraylib.so.500" when RAYLIB_SHARED else "linux/libraylib.a", + "system:dl", + "system:pthread", } } else when ODIN_OS == .Darwin { - when ODIN_ARCH == .arm64 { - when RAYLIB_SHARED { - foreign import lib { - "macos-arm64/libraylib.500.dylib", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } else { - foreign import lib { - "macos-arm64/libraylib.a", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } - } else { - when RAYLIB_SHARED { - foreign import lib { - "macos/libraylib.500.dylib", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } else { - foreign import lib { - "macos/libraylib.a", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } + foreign import lib { + "macos" + + "-arm64" when ODIN_ARCH == .arm64 else "" + + "/libraylib" + ".500.dylib" when RAYLIB_SHARED else ".a", + "system:Cocoa.framework", + "system:OpenGL.framework", + "system:IOKit.framework", } } else { foreign import lib "system:raylib" diff --git a/vendor/raylib/rlgl.odin b/vendor/raylib/rlgl.odin index 97ab0fd07..c9e8c28c2 100644 --- a/vendor/raylib/rlgl.odin +++ b/vendor/raylib/rlgl.odin @@ -122,20 +122,11 @@ when ODIN_OS == .Windows { } else when ODIN_OS == .Linux { foreign import lib "linux/libraylib.a" } else when ODIN_OS == .Darwin { - when ODIN_ARCH == .arm64 { - foreign import lib { - "macos-arm64/libraylib.a", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } - } else { - foreign import lib { - "macos/libraylib.a", - "system:Cocoa.framework", - "system:OpenGL.framework", - "system:IOKit.framework", - } + foreign import lib { + "macos-arm64/libraylib.a" when ODIN_ARCH == .arm64 else "macos/libraylib.a", + "system:Cocoa.framework", + "system:OpenGL.framework", + "system:IOKit.framework", } } else { foreign import lib "system:raylib" From 72ce111a95cdddd60c90ada285d19e3c4e9e41f8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 4 Jun 2024 15:51:19 +0100 Subject: [PATCH 145/270] Keep `-vet` happy --- vendor/raylib/raygui.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/raylib/raygui.odin b/vendor/raylib/raygui.odin index 18f1a9c51..ff528ea7b 100644 --- a/vendor/raylib/raygui.odin +++ b/vendor/raylib/raygui.odin @@ -10,7 +10,7 @@ when ODIN_OS == .Windows { } } else when ODIN_OS == .Linux { foreign import lib { - "linux/libraygui.so" when RAYGUI_SHARED else "linux/libraygui.a" + "linux/libraygui.so" when RAYGUI_SHARED else "linux/libraygui.a", } } else when ODIN_OS == .Darwin { when ODIN_ARCH == .arm64 { From 11b1a48bf04f61edf5587fa627e78fb525da6090 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 4 Jun 2024 15:54:57 +0100 Subject: [PATCH 146/270] Fix `extra_linker_flags` for raylib on windows --- vendor/raylib/raylib.odin | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index 730575a2c..6204f0bde 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -97,8 +97,7 @@ MAX_TEXT_BUFFER_LENGTH :: #config(RAYLIB_MAX_TEXT_BUFFER_LENGTH, 1024) RAYLIB_SHARED :: #config(RAYLIB_SHARED, false) when ODIN_OS == .Windows { - _WINDOWS_LIB :: "windows/raylib" +" dll" when RAYLIB_SHARED else "" + ".lib" - @(extra_linker_flags="/NODEFAULTLIB:msvcrt") + @(extra_linker_flags="/NODEFAULTLIB:" + ("msvcrt" when RAYLIB_SHARED else "libcmt")) foreign import lib { "windows/raylibdll.lib" when RAYLIB_SHARED else "windows/raylib.lib" , "system:Winmm.lib", From 6b386631ddd7305f3111310a2ed3f921d5035298 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 4 Jun 2024 16:16:27 +0100 Subject: [PATCH 147/270] Improve error message suggestion for passing enums to integers --- src/check_expr.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2e008fe93..6af80eed5 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -125,6 +125,8 @@ gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types gb_internal bool complete_soa_type(Checker *checker, Type *t, bool wait_to_finish); +gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type *y); + enum LoadDirectiveResult { LoadDirective_Success = 0, LoadDirective_Error = 1, @@ -2252,6 +2254,17 @@ gb_internal bool check_representable_as_constant(CheckerContext *c, ExactValue i gb_internal bool check_integer_exceed_suggestion(CheckerContext *c, Operand *o, Type *type, i64 max_bit_size=0) { if (is_type_integer(type) && o->value.kind == ExactValue_Integer) { gbString b = type_to_string(type); + defer (gb_string_free(b)); + + if (is_type_enum(o->type)) { + if (check_is_castable_to(c, o, type)) { + gbString ot = type_to_string(o->type); + error_line("\tSuggestion: Try casting the '%s' expression to '%s'", ot, b); + gb_string_free(ot); + } + return true; + } + i64 sz = type_size_of(type); i64 bit_size = 8*sz; @@ -2301,7 +2314,6 @@ gb_internal bool check_integer_exceed_suggestion(CheckerContext *c, Operand *o, } } - gb_string_free(b); return true; } From 3b7100f8e5cdf325a51926ba02f785a5443b0415 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 4 Jun 2024 16:17:49 +0100 Subject: [PATCH 148/270] raygui: change `c.int` to their correct enum types --- vendor/raylib/raygui.odin | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/vendor/raylib/raygui.odin b/vendor/raylib/raygui.odin index ff528ea7b..8cda9c072 100644 --- a/vendor/raylib/raygui.odin +++ b/vendor/raylib/raygui.odin @@ -30,8 +30,8 @@ RAYGUI_VERSION :: "4.0" // Style property GuiStyleProp :: struct { - controlId: u16, - propertyId: u16, + controlId: u16, + propertyId: u16, propertyValue: c.int, } @@ -200,7 +200,7 @@ GuiColorPickerProperty :: enum c.int { HUEBAR_SELECTOR_OVERFLOW, // ColorPicker right hue bar selector overflow } -SCROLLBAR_LEFT_SIDE :: 0 +SCROLLBAR_LEFT_SIDE :: 0 SCROLLBAR_RIGHT_SIDE :: 1 //---------------------------------------------------------------------------------- @@ -236,8 +236,8 @@ foreign lib { // Style set/get functions - GuiSetStyle :: proc(control: c.int, property: c.int, value: c.int) --- // Set one style property - GuiGetStyle :: proc(control: c.int, property: c.int) -> c.int --- // Get one style property + GuiSetStyle :: proc(control: GuiControl, property: GuiStyleProp, value: c.int) --- // Set one style property + GuiGetStyle :: proc(control: GuiControl, property: GuiStyleProp) -> c.int --- // Get one style property // Styles loading functions @@ -252,11 +252,11 @@ foreign lib { // Icons functionality - GuiIconText :: proc(iconId: c.int, text: cstring) -> cstring --- // Get text with icon id prepended (if supported) + GuiIconText :: proc(iconId: GuiIconName, text: cstring) -> cstring --- // Get text with icon id prepended (if supported) GuiSetIconScale :: proc(scale: c.int) --- // Set default icon drawing size GuiGetIcons :: proc() -> [^]u32 --- // Get raygui icons data pointer GuiLoadIcons :: proc(fileName: cstring, loadIconsName: bool) -> [^]cstring --- // Load raygui icons file (.rgi) into internal icons data - GuiDrawIcon :: proc(iconId: c.int, posX: c.int, posY: c.int, pixelSize: c.int, color: Color) --- // Draw icon using pixel size at specified position + GuiDrawIcon :: proc(iconId: GuiIconName, posX, posY: c.int, pixelSize: c.int, color: Color) --- // Draw icon using pixel size at specified position // Controls @@ -274,11 +274,11 @@ foreign lib { GuiLabel :: proc(bounds: Rectangle, text: cstring) -> c.int --- // Label control, shows text GuiButton :: proc(bounds: Rectangle, text: cstring) -> bool --- // Button control, returns true when clicked - GuiLabelButton :: proc(bounds: Rectangle, text: cstring) -> bool --- // Label button control, show true when clicked + GuiLabelButton :: proc(bounds: Rectangle, text: cstring) -> bool --- // Label button control, show true when clicked GuiToggle :: proc(bounds: Rectangle, text: cstring, active: ^bool) -> c.int --- // Toggle Button control, returns true when active GuiToggleGroup :: proc(bounds: Rectangle, text: cstring, active: ^c.int) -> c.int --- // Toggle Group control, returns active toggle index GuiToggleSlider :: proc(bounds: Rectangle, text: cstring, active: ^c.int) -> c.int --- - GuiCheckBox :: proc(bounds: Rectangle, text: cstring, checked: ^bool) -> bool --- // Check Box control, returns true when active + GuiCheckBox :: proc(bounds: Rectangle, text: cstring, checked: ^bool) -> bool --- // Check Box control, returns true when active GuiComboBox :: proc(bounds: Rectangle, text: cstring, active: ^c.int) -> c.int --- // Combo Box control, returns selected item index GuiDropdownBox :: proc(bounds: Rectangle, text: cstring, active: ^c.int, editMode: bool) -> bool --- // Dropdown Box control, returns selected item From 4f5b2bd12736b32c0a35496e8ca8f03d305c54b4 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 4 Jun 2024 19:01:50 +0200 Subject: [PATCH 149/270] fix crash when you have 2 `#load_directory` calls with the same path --- src/check_builtin.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index aa3be0bbd..15c63905e 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1345,6 +1345,8 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c map_set(&c->info->load_directory_map, call, new_cache); } else { cache->file_error = file_error; + + map_set(&c->info->load_directory_map, call, cache); } }); From b47a15733df5d0cb858a2be0d4eb334df3d50536 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 4 Jun 2024 19:06:13 +0200 Subject: [PATCH 150/270] implement `#exists(path)` --- src/check_builtin.cpp | 94 ++++++++++++++++++++++++++++++++----------- src/check_expr.cpp | 1 + src/checker.hpp | 9 +++++ 3 files changed, 80 insertions(+), 24 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 15c63905e..7e3bcb7ee 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1079,7 +1079,7 @@ gb_internal bool check_builtin_simd_operation(CheckerContext *c, Operand *operan return false; } -gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String const &original_string, bool err_on_not_found, LoadFileCache **cache_) { +gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String const &original_string, bool err_on_not_found, LoadFileCache **cache_, LoadFileTier tier) { ast_node(ce, CallExpr, call); ast_node(bd, BasicDirective, ce->proc); String builtin_name = bd->name.string; @@ -1105,12 +1105,16 @@ gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String gbFileError file_error = gbFileError_None; String data = {}; + bool exists = false; + LoadFileTier cache_tier = LoadFileTier_Invalid; LoadFileCache **cache_ptr = string_map_get(&c->info->load_file_cache, path); LoadFileCache *cache = cache_ptr ? *cache_ptr : nullptr; if (cache) { file_error = cache->file_error; data = cache->data; + exists = cache->exists; + cache_tier = cache->tier; } defer ({ if (cache == nullptr) { @@ -1118,60 +1122,78 @@ gb_internal bool cache_load_file_directive(CheckerContext *c, Ast *call, String new_cache->path = path; new_cache->data = data; new_cache->file_error = file_error; + new_cache->exists = exists; + new_cache->tier = cache_tier; string_map_init(&new_cache->hashes, 32); string_map_set(&c->info->load_file_cache, path, new_cache); if (cache_) *cache_ = new_cache; } else { cache->data = data; cache->file_error = file_error; + cache->exists = exists; + cache->tier = cache_tier; if (cache_) *cache_ = cache; } }); - TEMPORARY_ALLOCATOR_GUARD(); - char *c_str = alloc_cstring(temporary_allocator(), path); + if (tier > cache_tier) { + cache_tier = tier; - gbFile f = {}; - if (cache == nullptr) { + TEMPORARY_ALLOCATOR_GUARD(); + char *c_str = alloc_cstring(temporary_allocator(), path); + + gbFile f = {}; file_error = gb_file_open(&f, c_str); + defer (gb_file_close(&f)); + + if (file_error == gbFileError_None) { + exists = true; + + switch(tier) { + case LoadFileTier_Exists: + // Nothing to do. + break; + case LoadFileTier_Contents: { + isize file_size = cast(isize)gb_file_size(&f); + if (file_size > 0) { + u8 *ptr = cast(u8 *)gb_alloc(permanent_allocator(), file_size+1); + gb_file_read_at(&f, ptr, file_size, 0); + ptr[file_size] = '\0'; + data.text = ptr; + data.len = file_size; + } + break; + } + default: + GB_PANIC("Unhandled LoadFileTier"); + }; + } } - defer (gb_file_close(&f)); switch (file_error) { default: case gbFileError_Invalid: if (err_on_not_found) { - error(ce->proc, "Failed to `#%.*s` file: %s; invalid file or cannot be found", LIT(builtin_name), c_str); + error(ce->proc, "Failed to `#%.*s` file: %.*s; invalid file or cannot be found", LIT(builtin_name), LIT(path)); } call->state_flags |= StateFlag_DirectiveWasFalse; return false; case gbFileError_NotExists: if (err_on_not_found) { - error(ce->proc, "Failed to `#%.*s` file: %s; file cannot be found", LIT(builtin_name), c_str); + error(ce->proc, "Failed to `#%.*s` file: %.*s; file cannot be found", LIT(builtin_name), LIT(path)); } call->state_flags |= StateFlag_DirectiveWasFalse; return false; case gbFileError_Permission: if (err_on_not_found) { - error(ce->proc, "Failed to `#%.*s` file: %s; file permissions problem", LIT(builtin_name), c_str); + error(ce->proc, "Failed to `#%.*s` file: %.*s; file permissions problem", LIT(builtin_name), LIT(path)); } call->state_flags |= StateFlag_DirectiveWasFalse; return false; case gbFileError_None: // Okay break; - } - - if (cache == nullptr) { - isize file_size = cast(isize)gb_file_size(&f); - if (file_size > 0) { - u8 *ptr = cast(u8 *)gb_alloc(permanent_allocator(), file_size+1); - gb_file_read_at(&f, ptr, file_size, 0); - ptr[file_size] = '\0'; - data.text = ptr; - data.len = file_size; - } - } + }; return true; } @@ -1263,7 +1285,7 @@ gb_internal LoadDirectiveResult check_load_directive(CheckerContext *c, Operand operand->mode = Addressing_Constant; LoadFileCache *cache = nullptr; - if (cache_load_file_directive(c, call, o.value.value_string, err_on_not_found, &cache)) { + if (cache_load_file_directive(c, call, o.value.value_string, err_on_not_found, &cache, LoadFileTier_Contents)) { operand->value = exact_value_string(cache->data); return LoadDirective_Success; } @@ -1391,7 +1413,7 @@ gb_internal LoadDirectiveResult check_load_directory_directive(CheckerContext *c for (FileInfo fi : list) { LoadFileCache *cache = nullptr; - if (cache_load_file_directive(c, call, fi.fullpath, err_on_not_found, &cache)) { + if (cache_load_file_directive(c, call, fi.fullpath, err_on_not_found, &cache, LoadFileTier_Contents)) { array_add(&file_caches, cache); } else { result = LoadDirective_Error; @@ -1490,6 +1512,30 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o operand->type = t_source_code_location; operand->mode = Addressing_Value; + } else if (name == "exists") { + if (ce->args.count != 1) { + error(ce->close, "'#exists' expects 1 argument, got %td", ce->args.count); + return false; + } + + Operand o = {}; + check_expr(c, &o, ce->args[0]); + if (o.mode != Addressing_Constant || !is_type_string(o.type)) { + error(ce->args[0], "'#exists' expected a constant string argument"); + return false; + } + + operand->type = t_untyped_bool; + operand->mode = Addressing_Constant; + + String original_string = o.value.value_string; + LoadFileCache *cache = nullptr; + if (cache_load_file_directive(c, call, original_string, /* err_on_not_found=*/ false, &cache, LoadFileTier_Exists)) { + operand->value = exact_value_bool(cache->exists); + } else { + operand->value = exact_value_bool(false); + } + } else if (name == "load") { return check_load_directive(c, operand, call, type_hint, true) == LoadDirective_Success; } else if (name == "load_directory") { @@ -1542,7 +1588,7 @@ gb_internal bool check_builtin_procedure_directive(CheckerContext *c, Operand *o String hash_kind = o_hash.value.value_string; LoadFileCache *cache = nullptr; - if (cache_load_file_directive(c, call, original_string, true, &cache)) { + if (cache_load_file_directive(c, call, original_string, true, &cache, LoadFileTier_Contents)) { MUTEX_GUARD(&c->info->load_file_mutex); // TODO(bill): make these procedures fast :P u64 hash_value = 0; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2e008fe93..3bf2af24a 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7408,6 +7408,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c String name = bd->name.string; if ( name == "location" || + name == "exists" || name == "assert" || name == "panic" || name == "defined" || diff --git a/src/checker.hpp b/src/checker.hpp index 539b72b2d..e793540e3 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -336,7 +336,16 @@ struct ObjcMsgData { ObjcMsgKind kind; Type *proc_type; }; + +enum LoadFileTier { + LoadFileTier_Invalid, + LoadFileTier_Exists, + LoadFileTier_Contents, +}; + struct LoadFileCache { + LoadFileTier tier; + bool exists; String path; gbFileError file_error; String data; From d33668fa91b0f64cb6f69f315a2b2e8609fdbee0 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:24:46 -0400 Subject: [PATCH 151/270] Fix partial parsing of "infinity" in `parse_f64_prefix` It was previously reporting an invalid number of characters parsed for any string other than "inf", "+inf", or "-inf". --- core/strconv/strconv.odin | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index 94842617e..990b2be2f 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -878,13 +878,10 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { s = s[1:] fallthrough case 'i', 'I': - n = common_prefix_len_ignore_case(s, "infinity") - if 3 < n && n < 8 { // "inf" or "infinity" - n = 3 - } - if n == 3 || n == 8 { + m := common_prefix_len_ignore_case(s, "infinity") + if m == 3 || m == 8 { // "inf" or "infinity" f = 0h7ff00000_00000000 if sign == 1 else 0hfff00000_00000000 - n = nsign + 3 + n = nsign + m ok = true return } From 3e159736cdf7654d5739a1340458aa23aaccbf7a Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 4 Jun 2024 19:32:23 +0200 Subject: [PATCH 152/270] use `#exists` to provide good errors for common missing libraries --- src/check_expr.cpp | 1 + vendor/cgltf/cgltf.odin | 22 ++++++++++++++++++---- vendor/miniaudio/common.odin | 12 ++++++++---- vendor/miniaudio/data_conversion.odin | 6 +----- vendor/miniaudio/decoding.odin | 6 +----- vendor/miniaudio/device_io_procs.odin | 6 +----- vendor/miniaudio/effects.odin | 6 +----- vendor/miniaudio/encoding.odin | 6 +----- vendor/miniaudio/engine.odin | 6 +----- vendor/miniaudio/filtering.odin | 6 +----- vendor/miniaudio/generation.odin | 6 +----- vendor/miniaudio/job_queue.odin | 6 +----- vendor/miniaudio/logging.odin | 6 +----- vendor/miniaudio/node_graph.odin | 6 +----- vendor/miniaudio/resource_manager.odin | 6 +----- vendor/miniaudio/synchronization.odin | 6 +----- vendor/miniaudio/utilities.odin | 6 +----- vendor/miniaudio/vfs.odin | 6 +----- vendor/stb/image/stb_image.odin | 25 +++++++++++++++++++------ vendor/stb/image/stb_image_resize.odin | 21 +++++++++++++++++---- vendor/stb/image/stb_image_write.odin | 21 +++++++++++++++++---- vendor/stb/rect_pack/stb_rect_pack.odin | 21 +++++++++++++++++---- vendor/stb/truetype/stb_truetype.odin | 20 ++++++++++++++++---- vendor/stb/vorbis/stb_vorbis.odin | 20 +++++++++++++++----- 24 files changed, 143 insertions(+), 110 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 3bf2af24a..c03fcb184 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -8330,6 +8330,7 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A name == "assert" || name == "defined" || name == "config" || + name == "exists" || name == "load" || name == "load_hash" || name == "load_directory" || diff --git a/vendor/cgltf/cgltf.odin b/vendor/cgltf/cgltf.odin index 024e8dfaa..a5d474a7b 100644 --- a/vendor/cgltf/cgltf.odin +++ b/vendor/cgltf/cgltf.odin @@ -1,9 +1,23 @@ package cgltf -when ODIN_OS == .Windows { foreign import lib "lib/cgltf.lib" } -else when ODIN_OS == .Linux { foreign import lib "lib/cgltf.a" } -else when ODIN_OS == .Darwin { foreign import lib "lib/darwin/cgltf.a" } -else { foreign import lib "system:cgltf" } +@(private) +LIB :: ( + "lib/cgltf.lib" when ODIN_OS == .Windows + else "lib/cgltf.a" when ODIN_OS == .Linux + else "lib/darwin/cgltf.a" when ODIN_OS == .Darwin + else "" +) + +when LIB != "" { + when !#exists(LIB) { + // Windows library is shipped with the compiler, so a Windows specific message should not be needed. + #panic("Could not find the compiled cgltf library, it can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/cgltf/src\"`") + } + + foreign import lib { LIB } +} else { + foreign import lib "system:cgltf" +} import "core:c" diff --git a/vendor/miniaudio/common.odin b/vendor/miniaudio/common.odin index b38599d96..d72c3f251 100644 --- a/vendor/miniaudio/common.odin +++ b/vendor/miniaudio/common.odin @@ -8,12 +8,16 @@ when MINIAUDIO_SHARED { #panic("Shared linking for miniaudio is not supported yet") } -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" +@(private) +LIB :: "lib/miniaudio.lib" when ODIN_OS == .Windows else "lib/miniaudio.a" + +when !#exists(LIB) { + // Windows library is shipped with the compiler, so a Windows specific message should not be needed. + #panic("Could not find the compiled miniaudio library, it can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/miniaudio/src\"`") } +foreign import lib { LIB } + BINDINGS_VERSION_MAJOR :: 0 BINDINGS_VERSION_MINOR :: 11 BINDINGS_VERSION_REVISION :: 21 diff --git a/vendor/miniaudio/data_conversion.odin b/vendor/miniaudio/data_conversion.odin index aee26bc8c..c33f54707 100644 --- a/vendor/miniaudio/data_conversion.odin +++ b/vendor/miniaudio/data_conversion.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ ************************************************************************************************************************************************************* diff --git a/vendor/miniaudio/decoding.odin b/vendor/miniaudio/decoding.odin index 4433aa5a7..4860680c9 100644 --- a/vendor/miniaudio/decoding.odin +++ b/vendor/miniaudio/decoding.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/miniaudio/device_io_procs.odin b/vendor/miniaudio/device_io_procs.odin index 0d572ae2c..21ac1afd7 100644 --- a/vendor/miniaudio/device_io_procs.odin +++ b/vendor/miniaudio/device_io_procs.odin @@ -1,10 +1,6 @@ package miniaudio -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } import "core:c" diff --git a/vendor/miniaudio/effects.odin b/vendor/miniaudio/effects.odin index 273845001..a3710ad88 100644 --- a/vendor/miniaudio/effects.odin +++ b/vendor/miniaudio/effects.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /* Delay diff --git a/vendor/miniaudio/encoding.odin b/vendor/miniaudio/encoding.odin index 63aa45c6d..da8389b06 100644 --- a/vendor/miniaudio/encoding.odin +++ b/vendor/miniaudio/encoding.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/miniaudio/engine.odin b/vendor/miniaudio/engine.odin index 6eabd75c2..ecd3fb39d 100644 --- a/vendor/miniaudio/engine.odin +++ b/vendor/miniaudio/engine.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/miniaudio/filtering.odin b/vendor/miniaudio/filtering.odin index 31ddbd7a4..d1c053d20 100644 --- a/vendor/miniaudio/filtering.odin +++ b/vendor/miniaudio/filtering.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************** diff --git a/vendor/miniaudio/generation.odin b/vendor/miniaudio/generation.odin index 69be85234..746efcca7 100644 --- a/vendor/miniaudio/generation.odin +++ b/vendor/miniaudio/generation.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } waveform_type :: enum c.int { sine, diff --git a/vendor/miniaudio/job_queue.odin b/vendor/miniaudio/job_queue.odin index baa71c5f1..01ee31216 100644 --- a/vendor/miniaudio/job_queue.odin +++ b/vendor/miniaudio/job_queue.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /* Slot Allocator diff --git a/vendor/miniaudio/logging.odin b/vendor/miniaudio/logging.odin index 52b1c7980..afddf8e68 100644 --- a/vendor/miniaudio/logging.odin +++ b/vendor/miniaudio/logging.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c/libc" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } MAX_LOG_CALLBACKS :: 4 diff --git a/vendor/miniaudio/node_graph.odin b/vendor/miniaudio/node_graph.odin index 09ab50a3b..63482413b 100644 --- a/vendor/miniaudio/node_graph.odin +++ b/vendor/miniaudio/node_graph.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/miniaudio/resource_manager.odin b/vendor/miniaudio/resource_manager.odin index f27f3a53a..0284db86b 100644 --- a/vendor/miniaudio/resource_manager.odin +++ b/vendor/miniaudio/resource_manager.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/miniaudio/synchronization.odin b/vendor/miniaudio/synchronization.odin index cd4b0a5f0..012f52c2c 100644 --- a/vendor/miniaudio/synchronization.odin +++ b/vendor/miniaudio/synchronization.odin @@ -1,10 +1,6 @@ package miniaudio -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } @(default_calling_convention="c", link_prefix="ma_") foreign lib { diff --git a/vendor/miniaudio/utilities.odin b/vendor/miniaudio/utilities.odin index d518a514a..8728f40dc 100644 --- a/vendor/miniaudio/utilities.odin +++ b/vendor/miniaudio/utilities.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } @(default_calling_convention="c", link_prefix="ma_") foreign lib { diff --git a/vendor/miniaudio/vfs.odin b/vendor/miniaudio/vfs.odin index 475d118fc..b045a1501 100644 --- a/vendor/miniaudio/vfs.odin +++ b/vendor/miniaudio/vfs.odin @@ -2,11 +2,7 @@ package miniaudio import "core:c" -when ODIN_OS == .Windows { - foreign import lib "lib/miniaudio.lib" -} else { - foreign import lib "lib/miniaudio.a" -} +foreign import lib { LIB } /************************************************************************************************************************************************************ diff --git a/vendor/stb/image/stb_image.odin b/vendor/stb/image/stb_image.odin index c7caf801e..828a1c2bd 100644 --- a/vendor/stb/image/stb_image.odin +++ b/vendor/stb/image/stb_image.odin @@ -2,13 +2,26 @@ package stb_image import c "core:c/libc" +@(private) +LIB :: ( + "../lib/stb_image.lib" when ODIN_OS == .Windows + else "../lib/stb_image.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_image.a" when ODIN_OS == .Darwin + else "" +) + +when LIB != "" { + when !#exists(LIB) { + // The STB libraries are shipped with the compiler on Windows so a Windows specific message should not be needed. + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + + foreign import stbi { LIB } +} else { + foreign import stbi "system:stb_image" +} + #assert(size_of(c.int) == size_of(b32)) - - when ODIN_OS == .Windows { foreign import stbi "../lib/stb_image.lib" } -else when ODIN_OS == .Linux { foreign import stbi "../lib/stb_image.a" } -else when ODIN_OS == .Darwin { foreign import stbi "../lib/darwin/stb_image.a" } -else { foreign import stbi "system:stb_image" } - #assert(size_of(b32) == size_of(c.int)) // diff --git a/vendor/stb/image/stb_image_resize.odin b/vendor/stb/image/stb_image_resize.odin index c464964df..d407a1852 100644 --- a/vendor/stb/image/stb_image_resize.odin +++ b/vendor/stb/image/stb_image_resize.odin @@ -2,11 +2,24 @@ package stb_image import c "core:c/libc" - when ODIN_OS == .Windows { foreign import lib "../lib/stb_image_resize.lib" } -else when ODIN_OS == .Linux { foreign import lib "../lib/stb_image_resize.a" } -else when ODIN_OS == .Darwin { foreign import lib "../lib/darwin/stb_image_resize.a" } -else { foreign import lib "system:stb_image_resize" } +@(private) +RESIZE_LIB :: ( + "../lib/stb_image_resize.lib" when ODIN_OS == .Windows + else "../lib/stb_image_resize.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_image_resize.a" when ODIN_OS == .Darwin + else "" +) +when RESIZE_LIB != "" { + when !#exists(RESIZE_LIB) { + // The STB libraries are shipped with the compiler on Windows so a Windows specific message should not be needed. + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + + foreign import lib { RESIZE_LIB } +} else { + foreign import lib "system:stb_image_resize" +} ////////////////////////////////////////////////////////////////////////////// // diff --git a/vendor/stb/image/stb_image_write.odin b/vendor/stb/image/stb_image_write.odin index 9ed97eb48..f030f1e28 100644 --- a/vendor/stb/image/stb_image_write.odin +++ b/vendor/stb/image/stb_image_write.odin @@ -2,11 +2,24 @@ package stb_image import c "core:c/libc" - when ODIN_OS == .Windows { foreign import stbiw "../lib/stb_image_write.lib" } -else when ODIN_OS == .Linux { foreign import stbiw "../lib/stb_image_write.a" } -else when ODIN_OS == .Darwin { foreign import stbiw "../lib/darwin/stb_image_write.a" } -else { foreign import stbiw "system:stb_image_write" } +@(private) +WRITE_LIB :: ( + "../lib/stb_image_write.lib" when ODIN_OS == .Windows + else "../lib/stb_image_write.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_image_write.a" when ODIN_OS == .Darwin + else "" +) +when WRITE_LIB != "" { + when !#exists(WRITE_LIB) { + // The STB libraries are shipped with the compiler on Windows so a Windows specific message should not be needed. + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + + foreign import stbiw { WRITE_LIB } +} else { + foreign import stbiw "system:stb_image_write" +} write_func :: proc "c" (ctx: rawptr, data: rawptr, size: c.int) diff --git a/vendor/stb/rect_pack/stb_rect_pack.odin b/vendor/stb/rect_pack/stb_rect_pack.odin index 3a2544b81..6c0b56378 100644 --- a/vendor/stb/rect_pack/stb_rect_pack.odin +++ b/vendor/stb/rect_pack/stb_rect_pack.odin @@ -4,10 +4,23 @@ import "core:c" #assert(size_of(b32) == size_of(c.int)) - when ODIN_OS == .Windows { foreign import lib "../lib/stb_rect_pack.lib" } -else when ODIN_OS == .Linux { foreign import lib "../lib/stb_rect_pack.a" } -else when ODIN_OS == .Darwin { foreign import lib "../lib/darwin/stb_rect_pack.a" } -else { foreign import lib "system:stb_rect_pack" } +@(private) +LIB :: ( + "../lib/stb_rect_pack.lib" when ODIN_OS == .Windows + else "../lib/stb_rect_pack.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_rect_pack.a" when ODIN_OS == .Darwin + else "" +) + +when LIB != "" { + when !#exists(LIB) { + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + + foreign import lib { LIB } +} else { + foreign import lib "system:stb_rect_pack" +} Coord :: distinct c.int _MAXVAL :: max(Coord) diff --git a/vendor/stb/truetype/stb_truetype.odin b/vendor/stb/truetype/stb_truetype.odin index 1600041de..876138c3a 100644 --- a/vendor/stb/truetype/stb_truetype.odin +++ b/vendor/stb/truetype/stb_truetype.odin @@ -3,11 +3,23 @@ package stb_truetype import c "core:c" import stbrp "vendor:stb/rect_pack" - when ODIN_OS == .Windows { foreign import stbtt "../lib/stb_truetype.lib" } -else when ODIN_OS == .Linux { foreign import stbtt "../lib/stb_truetype.a" } -else when ODIN_OS == .Darwin { foreign import stbtt "../lib/darwin/stb_truetype.a" } -else { foreign import stbtt "system:stb_truetype" } +@(private) +LIB :: ( + "../lib/stb_truetype.lib" when ODIN_OS == .Windows + else "../lib/stb_truetype.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_truetype.a" when ODIN_OS == .Darwin + else "" +) +when LIB != "" { + when !#exists(LIB) { + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + + foreign import stbtt { LIB } +} else { + foreign import stbtt "system:stb_truetype" +} /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// diff --git a/vendor/stb/vorbis/stb_vorbis.odin b/vendor/stb/vorbis/stb_vorbis.odin index 0c887a473..867ffb86d 100644 --- a/vendor/stb/vorbis/stb_vorbis.odin +++ b/vendor/stb/vorbis/stb_vorbis.odin @@ -2,13 +2,23 @@ package stb_vorbis import c "core:c/libc" +@(private) +LIB :: ( + "../lib/stb_vorbis.lib" when ODIN_OS == .Windows + else "../lib/stb_vorbis.a" when ODIN_OS == .Linux + else "../lib/darwin/stb_vorbis.a" when ODIN_OS == .Darwin + else "" +) - when ODIN_OS == .Windows { foreign import lib "../lib/stb_vorbis.lib" } -else when ODIN_OS == .Linux { foreign import lib "../lib/stb_vorbis.a" } -else when ODIN_OS == .Darwin { foreign import lib "../lib/darwin/stb_vorbis.a" } -else { foreign import lib "system:stb_vorbis" } - +when LIB != "" { + when !#exists(LIB) { + #panic("Could not find the compiled STB libraries, they can be compiled by running `make -C \"" + ODIN_ROOT + "vendor/stb/src\"`") + } + foreign import lib { LIB } +} else { + foreign import lib "system:stb_vorbis" +} /////////// THREAD SAFETY From 2a526058b3cbcd645dd35f3541ba1fc305dd02f9 Mon Sep 17 00:00:00 2001 From: laytan Date: Tue, 4 Jun 2024 20:15:47 +0200 Subject: [PATCH 153/270] fix passing pointer to constant in non-odin cc --- src/llvm_backend_proc.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 3b9b1be05..6cb1efab2 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1097,15 +1097,7 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c ptr = lb_address_from_load_or_generate_local(p, x); } } else { - if (LLVMIsConstant(x.value)) { - // NOTE(bill): if the value is already constant, then just it as a global variable - // and pass it by pointer - lbAddr addr = lb_add_global_generated(p->module, original_type, x); - lb_make_global_private_const(addr); - ptr = addr.addr; - } else { - ptr = lb_copy_value_to_ptr(p, x, original_type, 16); - } + ptr = lb_copy_value_to_ptr(p, x, original_type, 16); } array_add(&processed_args, ptr); } From 7d670f65624c7c1f0d9a93808a99e259b95bd4b6 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:39:31 -0400 Subject: [PATCH 154/270] Add initial test suite for `core:strconv` --- tests/core/Makefile | 4 ++ tests/core/build.bat | 5 ++ tests/core/strconv/test_core_strconv.odin | 79 +++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 tests/core/strconv/test_core_strconv.odin diff --git a/tests/core/Makefile b/tests/core/Makefile index 85f3783b4..79af9c3c7 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -25,6 +25,7 @@ all_bsd: download_test_assets \ reflect_test \ runtime_test \ slice_test \ + strconv_test \ strings_test \ thread_test \ time_test @@ -98,6 +99,9 @@ runtime_test: slice_test: $(ODIN) test slice $(COMMON) -out:test_core_slice +strconv_test: + $(ODIN) test strconv $(COMMON) -out:test_core_strconv + strings_test: $(ODIN) test strings $(COMMON) -out:test_core_strings diff --git a/tests/core/build.bat b/tests/core/build.bat index 67ac10f86..18506408a 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -103,6 +103,11 @@ echo Running core:slice tests echo --- %PATH_TO_ODIN% test slice %COMMON% -out:test_core_slice.exe || exit /b +echo --- +echo Running core:strconv tests +echo --- +%PATH_TO_ODIN% test strconv %COMMON% -out:test_core_strconv.exe || exit /b + echo --- echo Running core:strings tests echo --- diff --git a/tests/core/strconv/test_core_strconv.odin b/tests/core/strconv/test_core_strconv.odin new file mode 100644 index 000000000..9d713a356 --- /dev/null +++ b/tests/core/strconv/test_core_strconv.odin @@ -0,0 +1,79 @@ +package test_core_strconv + +import "core:math" +import "core:strconv" +import "core:testing" + +@(test) +test_infinity :: proc(t: ^testing.T) { + pos_inf := math.inf_f64(+1) + neg_inf := math.inf_f64(-1) + + n: int + s := "infinity" + + for i in 1 ..< len(s) + 1 { + ss := s[:i] + f, ok := strconv.parse_f64(ss, &n) + if i == 3 { // "inf" + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, true) + } else if i == 8 { // "infinity" + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 8) + testing.expect_value(t, ok, true) + } else { // invalid substring + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 0) + testing.expect_value(t, ok, false) + } + } + + s = "+infinity" + for i in 1 ..< len(s) + 1 { + ss := s[:i] + f, ok := strconv.parse_f64(ss, &n) + if i == 4 { // "+inf" + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 4) + testing.expect_value(t, ok, true) + } else if i == 9 { // "+infinity" + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 9) + testing.expect_value(t, ok, true) + } else { // invalid substring + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 0) + testing.expect_value(t, ok, false) + } + } + + s = "-infinity" + for i in 1 ..< len(s) + 1 { + ss := s[:i] + f, ok := strconv.parse_f64(ss, &n) + if i == 4 { // "-inf" + testing.expect_value(t, f, neg_inf) + testing.expect_value(t, n, 4) + testing.expect_value(t, ok, true) + } else if i == 9 { // "-infinity" + testing.expect_value(t, f, neg_inf) + testing.expect_value(t, n, 9) + testing.expect_value(t, ok, true) + } else { // invalid substring + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 0) + testing.expect_value(t, ok, false) + } + } + + // Make sure odd casing works. + batch := [?]string {"INFiniTY", "iNfInItY", "InFiNiTy"} + for ss in batch { + f, ok := strconv.parse_f64(ss, &n) + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 8) + testing.expect_value(t, ok, true) + } +} From cd99625dd352e2ceaf621c8ae0f50de8ace3177d Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 4 Jun 2024 20:19:34 +0200 Subject: [PATCH 155/270] ci: compile needed libraries --- .github/workflows/ci.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffb2077d1..c808bb05e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,6 +53,12 @@ jobs: - name: Odin report run: ./odin report timeout-minutes: 1 + - name: Compile needed Vendor + run: | + make -C $(./odin root)/vendor/stb/src + make -C $(./odin root)/vendor/cgltf/src + make -C $(./odin root)/vendor/miniaudio/src + timeout-minutes: 10 - name: Odin check run: ./odin check examples/demo -vet timeout-minutes: 10 @@ -109,6 +115,12 @@ jobs: - name: Odin report run: ./odin report timeout-minutes: 1 + - name: Compile needed Vendor + run: | + make -C $(./odin root)/vendor/stb/src + make -C $(./odin root)/vendor/cgltf/src + make -C $(./odin root)/vendor/miniaudio/src + timeout-minutes: 10 - name: Odin check run: ./odin check examples/demo -vet timeout-minutes: 10 @@ -148,6 +160,12 @@ jobs: - name: Odin report run: ./odin report timeout-minutes: 1 + - name: Compile needed Vendor + run: | + make -C $(./odin root)/vendor/stb/src + make -C $(./odin root)/vendor/cgltf/src + make -C $(./odin root)/vendor/miniaudio/src + timeout-minutes: 10 - name: Odin check run: ./odin check examples/demo -vet timeout-minutes: 10 From c656a9e4cdd566b1aa473227d9d9a35b3c6812d3 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:08:19 -0400 Subject: [PATCH 156/270] Fix and subsume `test_issue_2087` into `strconv` test suite The full "infinity" strings were expected to be partial consumes, but this is not the case. That has been fixed and the relevant extra tests from that file have been added to this test suite. Fixes #2670 --- tests/core/strconv/test_core_strconv.odin | 60 ++++++++++++++++++++++ tests/issues/test_issue_2087.odin | 62 ----------------------- 2 files changed, 60 insertions(+), 62 deletions(-) delete mode 100644 tests/issues/test_issue_2087.odin diff --git a/tests/core/strconv/test_core_strconv.odin b/tests/core/strconv/test_core_strconv.odin index 9d713a356..d33ac06b6 100644 --- a/tests/core/strconv/test_core_strconv.odin +++ b/tests/core/strconv/test_core_strconv.odin @@ -4,6 +4,56 @@ import "core:math" import "core:strconv" import "core:testing" +@(test) +test_float :: proc(t: ^testing.T) { + n: int + f: f64 + ok: bool + + f, ok = strconv.parse_f64("1.2", &n) + testing.expect_value(t, f, 1.2) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, true) + + f, ok = strconv.parse_f64("1.2a", &n) + testing.expect_value(t, f, 1.2) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, false) + + f, ok = strconv.parse_f64("+", &n) + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 0) + testing.expect_value(t, ok, false) + + f, ok = strconv.parse_f64("-", &n) + testing.expect_value(t, f, 0) + testing.expect_value(t, n, 0) + testing.expect_value(t, ok, false) + +} + +@(test) +test_nan :: proc(t: ^testing.T) { + n: int + f: f64 + ok: bool + + f, ok = strconv.parse_f64("nan", &n) + testing.expect_value(t, math.classify(f), math.Float_Class.NaN) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, true) + + f, ok = strconv.parse_f64("nAN", &n) + testing.expect_value(t, math.classify(f), math.Float_Class.NaN) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, true) + + f, ok = strconv.parse_f64("Nani", &n) + testing.expect_value(t, math.classify(f), math.Float_Class.NaN) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, false) +} + @(test) test_infinity :: proc(t: ^testing.T) { pos_inf := math.inf_f64(+1) @@ -19,14 +69,17 @@ test_infinity :: proc(t: ^testing.T) { testing.expect_value(t, f, pos_inf) testing.expect_value(t, n, 3) testing.expect_value(t, ok, true) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } else if i == 8 { // "infinity" testing.expect_value(t, f, pos_inf) testing.expect_value(t, n, 8) testing.expect_value(t, ok, true) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } else { // invalid substring testing.expect_value(t, f, 0) testing.expect_value(t, n, 0) testing.expect_value(t, ok, false) + testing.expect_value(t, math.classify(f), math.Float_Class.Zero) } } @@ -38,14 +91,17 @@ test_infinity :: proc(t: ^testing.T) { testing.expect_value(t, f, pos_inf) testing.expect_value(t, n, 4) testing.expect_value(t, ok, true) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } else if i == 9 { // "+infinity" testing.expect_value(t, f, pos_inf) testing.expect_value(t, n, 9) testing.expect_value(t, ok, true) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } else { // invalid substring testing.expect_value(t, f, 0) testing.expect_value(t, n, 0) testing.expect_value(t, ok, false) + testing.expect_value(t, math.classify(f), math.Float_Class.Zero) } } @@ -57,14 +113,17 @@ test_infinity :: proc(t: ^testing.T) { testing.expect_value(t, f, neg_inf) testing.expect_value(t, n, 4) testing.expect_value(t, ok, true) + testing.expect_value(t, math.classify(f), math.Float_Class.Neg_Inf) } else if i == 9 { // "-infinity" testing.expect_value(t, f, neg_inf) testing.expect_value(t, n, 9) testing.expect_value(t, ok, true) + testing.expect_value(t, math.classify(f), math.Float_Class.Neg_Inf) } else { // invalid substring testing.expect_value(t, f, 0) testing.expect_value(t, n, 0) testing.expect_value(t, ok, false) + testing.expect_value(t, math.classify(f), math.Float_Class.Zero) } } @@ -75,5 +134,6 @@ test_infinity :: proc(t: ^testing.T) { testing.expect_value(t, f, pos_inf) testing.expect_value(t, n, 8) testing.expect_value(t, ok, true) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } } diff --git a/tests/issues/test_issue_2087.odin b/tests/issues/test_issue_2087.odin deleted file mode 100644 index 26b6d487d..000000000 --- a/tests/issues/test_issue_2087.odin +++ /dev/null @@ -1,62 +0,0 @@ -// Tests issue #2087 https://github.com/odin-lang/Odin/issues/2087 -package test_issues - -import "core:math" -import "core:strconv" -import "core:testing" - -@(test) -test_parse_float :: proc(t: ^testing.T) { - { - f, ok := strconv.parse_f64("1.2") - testing.expect(t, ok && f == 1.2, "expected f64(1.2), fully consumed") - f, ok = strconv.parse_f64("1.2a") - testing.expect(t, !ok && f == 1.2, "expected f64(1.2), partially consumed") - f, ok = strconv.parse_f64("+") - testing.expect(t, !ok && f == 0.0, "expected f64(0.0), with ok=false") - f, ok = strconv.parse_f64("-") - testing.expect(t, !ok && f == 0.0, "expected f64(0.0), with ok=false") - - - f, ok = strconv.parse_f64("inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), fully consumed") - f, ok = strconv.parse_f64("+inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), fully consumed") - f, ok = strconv.parse_f64("-inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f64(-inf), fully consumed") - f, ok = strconv.parse_f64("inFinity") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), partially consumed") - f, ok = strconv.parse_f64("+InFinity") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f64(+inf), partially consumed") - f, ok = strconv.parse_f64("-InfiniTy") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f64(-inf), partially consumed") - f, ok = strconv.parse_f64("nan") - testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f64(nan), fully consumed") - f, ok = strconv.parse_f64("nAN") - testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f64(nan), fully consumed") - } - { - f, ok := strconv.parse_f32("1.2") - testing.expect(t, ok && f == 1.2, "expected f32(1.2), fully consumed") - - f, ok = strconv.parse_f32("1.2a") - testing.expect(t, !ok && f == 1.2, "expected f32(1.2), partially consumed") - - f, ok = strconv.parse_f32("inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), fully consumed") - f, ok = strconv.parse_f32("+inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), fully consumed") - f, ok = strconv.parse_f32("-inf") - testing.expect(t, ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f32(-inf), fully consumed") - f, ok = strconv.parse_f32("inFinity") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), partially consumed") - f, ok = strconv.parse_f32("+InFinity") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Inf, "expected f32(+inf), partially consumed") - f, ok = strconv.parse_f32("-InfiniTy") - testing.expect(t, !ok && math.classify(f) == math.Float_Class.Neg_Inf, "expected f32(-inf), partially consumed") - f, ok = strconv.parse_f32("nan") - testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f32(nan), fully consumed") - f, ok = strconv.parse_f32("nAN") - testing.expect(t, ok && math.classify(f) == math.Float_Class.NaN, "expected f32(nan), fully consumed") - } -} \ No newline at end of file From dbaf1a1ce0a4228e9b3b07a020069dbf0b17136c Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 4 Jun 2024 20:26:21 +0200 Subject: [PATCH 157/270] compile stb_vorbis --- vendor/stb/src/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/stb/src/Makefile b/vendor/stb/src/Makefile index a6db3e297..1027cf8d2 100644 --- a/vendor/stb/src/Makefile +++ b/vendor/stb/src/Makefile @@ -14,7 +14,7 @@ unix: $(AR) rcs ../lib/stb_image_resize.a stb_image_resize.o $(AR) rcs ../lib/stb_truetype.a stb_truetype.o $(AR) rcs ../lib/stb_rect_pack.a stb_rect_pack.o - #$(AR) rcs ../lib/stb_vorbis_pack.a stb_vorbis_pack.o + $(AR) rcs ../lib/stb_vorbis.a stb_vorbis.o #$(CC) -fPIC -shared -Wl,-soname=stb_image.so -o ../lib/stb_image.so stb_image.o #$(CC) -fPIC -shared -Wl,-soname=stb_image_write.so -o ../lib/stb_image_write.so stb_image_write.o #$(CC) -fPIC -shared -Wl,-soname=stb_image_resize.so -o ../lib/stb_image_resize.so stb_image_resize.o From c2ddf4266f478a5d331a7301d26cef4432b21455 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 4 Jun 2024 22:26:51 +0200 Subject: [PATCH 158/270] Update `delete_old_binaries.py` --- ci/delete_old_binaries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/delete_old_binaries.py b/ci/delete_old_binaries.py index 6ea3cb78d..f7d5d02af 100644 --- a/ci/delete_old_binaries.py +++ b/ci/delete_old_binaries.py @@ -21,8 +21,8 @@ def main(): delta = now - date if delta.days > days_to_keep: - print(f'Deleting {parts[5]}') - execute_cli(f'b2 rm {parts[0]}') + print(f'Deleting b2://{bucket}/{parts[5]}') + execute_cli(f'b2 rm b2://{bucket}/{parts[5]}') def execute_cli(command): From 9d234998c0e4e412155c7f1debf32b562fa59dad Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 4 Jun 2024 22:44:49 +0200 Subject: [PATCH 159/270] Update `create_nightly_json` --- ci/create_nightly_json.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/create_nightly_json.py b/ci/create_nightly_json.py index 03c653040..3a1fcd1ef 100644 --- a/ci/create_nightly_json.py +++ b/ci/create_nightly_json.py @@ -13,7 +13,8 @@ def main(): for x in files_lines: parts = x.split(" ", 1) if parts[0]: - json_str = execute_cli(f"b2 file info {parts[0]}") + print(f"Parts[0]: {parts[0]}", flush=True) + json_str = execute_cli(f"b2 file info b2://{bucket}/{parts[0]}") data = json.loads(json_str) name = remove_prefix(data['fileName'], "nightly/") url = f"https://f001.backblazeb2.com/file/{bucket}/nightly/{urllib.parse.quote_plus(name)}" From 265e6aa781e487a3880f97bb6e8c12d2abcb8b57 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 16:59:07 -0400 Subject: [PATCH 160/270] Add `parse_complex/quaternion*` to `core:strconv` --- core/strconv/strconv.odin | 269 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 269 insertions(+) diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index 94842617e..2c8365922 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -1124,6 +1124,275 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { ok = !overflow return } +/* +Parses a 128-bit complex number from a string + +**Inputs** +- str: The input string containing a 128-bit complex number. +- n: An optional pointer to an int to store the length of the parsed substring (default: nil). + +Example: + + import "core:fmt" + import "core:strconv" + parse_complex128_example :: proc() { + n: int + c, ok := strconv.parse_complex128("3+1i", &n) + fmt.printfln("%v %i %t", c, n, ok) + + c, ok = strconv.parse_complex128("5+7i hellope", &n) + fmt.printfln("%v %i %t", c, n, ok) + } + +Output: + + 3+1i 4 true + 5+7i 4 false + +**Returns** +- value: The parsed 128-bit complex number. +- ok: `false` if a complex number could not be found, or if the input string contained more than just the number. +*/ +parse_complex128 :: proc(str: string, n: ^int = nil) -> (value: complex128, ok: bool) { + real_value, imag_value: f64 + nr_r, nr_i: int + + real_value, nr_r, _ = parse_f64_prefix(str) + imag_value, nr_i, _ = parse_f64_prefix(str[nr_r:]) + + i_parsed := len(str) >= nr_r + nr_i + 1 && str[nr_r + nr_i] == 'i' + if !i_parsed { + // No `i` means we refuse to treat the second float we parsed as an + // imaginary value. + imag_value = 0 + nr_i = 0 + } + + ok = i_parsed && len(str) == nr_r + nr_i + 1 + + if n != nil { + n^ = nr_r + nr_i + (1 if i_parsed else 0) + } + + value = complex(real_value, imag_value) + return +} +/* +Parses a 64-bit complex number from a string + +**Inputs** +- str: The input string containing a 64-bit complex number. +- n: An optional pointer to an int to store the length of the parsed substring (default: nil). + +Example: + + import "core:fmt" + import "core:strconv" + parse_complex64_example :: proc() { + n: int + c, ok := strconv.parse_complex64("3+1i", &n) + fmt.printfln("%v %i %t", c, n, ok) + + c, ok = strconv.parse_complex64("5+7i hellope", &n) + fmt.printfln("%v %i %t", c, n, ok) + } + +Output: + + 3+1i 4 true + 5+7i 4 false + +**Returns** +- value: The parsed 64-bit complex number. +- ok: `false` if a complex number could not be found, or if the input string contained more than just the number. +*/ +parse_complex64 :: proc(str: string, n: ^int = nil) -> (value: complex64, ok: bool) { + v: complex128 = --- + v, ok = parse_complex128(str, n) + return cast(complex64)v, ok +} +/* +Parses a 32-bit complex number from a string + +**Inputs** +- str: The input string containing a 32-bit complex number. +- n: An optional pointer to an int to store the length of the parsed substring (default: nil). + +Example: + + import "core:fmt" + import "core:strconv" + parse_complex32_example :: proc() { + n: int + c, ok := strconv.parse_complex32("3+1i", &n) + fmt.printfln("%v %i %t", c, n, ok) + + c, ok = strconv.parse_complex32("5+7i hellope", &n) + fmt.printfln("%v %i %t", c, n, ok) + } + +Output: + + 3+1i 4 true + 5+7i 4 false + +**Returns** +- value: The parsed 32-bit complex number. +- ok: `false` if a complex number could not be found, or if the input string contained more than just the number. +*/ +parse_complex32 :: proc(str: string, n: ^int = nil) -> (value: complex32, ok: bool) { + v: complex128 = --- + v, ok = parse_complex128(str, n) + return cast(complex32)v, ok +} +/* +Parses a 256-bit quaternion from a string + +**Inputs** +- str: The input string containing a 256-bit quaternion. +- n: An optional pointer to an int to store the length of the parsed substring (default: nil). + +Example: + + import "core:fmt" + import "core:strconv" + parse_quaternion256_example :: proc() { + n: int + q, ok := strconv.parse_quaternion256("1+2i+3j+4k", &n) + fmt.printfln("%v %i %t", q, n, ok) + + q, ok = strconv.parse_quaternion256("1+2i+3j+4k hellope", &n) + fmt.printfln("%v %i %t", q, n, ok) + } + +Output: + + 1+2i+3j+4k 10 true + 1+2i+3j+4k 10 false + +**Returns** +- value: The parsed 256-bit quaternion. +- ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion. +*/ +parse_quaternion256 :: proc(str: string, n: ^int = nil) -> (value: quaternion256, ok: bool) { + iterate_and_assign :: proc (iter: ^string, terminator: byte, nr_total: ^int, state: bool) -> (value: f64, ok: bool) { + if !state { + return + } + + nr: int + value, nr, _ = parse_f64_prefix(iter^) + iter^ = iter[nr:] + + if len(iter) > 0 && iter[0] == terminator { + iter^ = iter[1:] + nr_total^ += nr + 1 + ok = true + } else { + value = 0 + } + + return + } + + real_value, imag_value, jmag_value, kmag_value: f64 + nr: int + + real_value, nr, _ = parse_f64_prefix(str) + iter := str[nr:] + + // Need to have parsed at least something in order to get started. + ok = nr > 0 + + // Quaternion parsing is done this way to honour the rest of the API with + // regards to partial parsing. Otherwise, we could error out early. + imag_value, ok = iterate_and_assign(&iter, 'i', &nr, ok) + jmag_value, ok = iterate_and_assign(&iter, 'j', &nr, ok) + kmag_value, ok = iterate_and_assign(&iter, 'k', &nr, ok) + + if len(iter) != 0 { + ok = false + } + + if n != nil { + n^ = nr + } + + value = quaternion( + real = real_value, + imag = imag_value, + jmag = jmag_value, + kmag = kmag_value) + return +} +/* +Parses a 128-bit quaternion from a string + +**Inputs** +- str: The input string containing a 128-bit quaternion. +- n: An optional pointer to an int to store the length of the parsed substring (default: nil). + +Example: + + import "core:fmt" + import "core:strconv" + parse_quaternion128_example :: proc() { + n: int + q, ok := strconv.parse_quaternion128("1+2i+3j+4k", &n) + fmt.printfln("%v %i %t", q, n, ok) + + q, ok = strconv.parse_quaternion128("1+2i+3j+4k hellope", &n) + fmt.printfln("%v %i %t", q, n, ok) + } + +Output: + + 1+2i+3j+4k 10 true + 1+2i+3j+4k 10 false + +**Returns** +- value: The parsed 128-bit quaternion. +- ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion. +*/ +parse_quaternion128 :: proc(str: string, n: ^int = nil) -> (value: quaternion128, ok: bool) { + v: quaternion256 = --- + v, ok = parse_quaternion256(str, n) + return cast(quaternion128)v, ok +} +/* +Parses a 64-bit quaternion from a string + +**Inputs** +- str: The input string containing a 64-bit quaternion. +- n: An optional pointer to an int to store the length of the parsed substring (default: nil). + +Example: + + import "core:fmt" + import "core:strconv" + parse_quaternion64_example :: proc() { + n: int + q, ok := strconv.parse_quaternion64("1+2i+3j+4k", &n) + fmt.printfln("%v %i %t", q, n, ok) + + q, ok = strconv.parse_quaternion64("1+2i+3j+4k hellope", &n) + fmt.printfln("%v %i %t", q, n, ok) + } + +Output: + + 1+2i+3j+4k 10 true + 1+2i+3j+4k 10 false + +**Returns** +- value: The parsed 64-bit quaternion. +- ok: `false` if a quaternion could not be found, or if the input string contained more than just the quaternion. +*/ +parse_quaternion64 :: proc(str: string, n: ^int = nil) -> (value: quaternion64, ok: bool) { + v: quaternion256 = --- + v, ok = parse_quaternion256(str, n) + return cast(quaternion64)v, ok +} /* Appends a boolean value as a string to the given buffer From b4cfae222cf51ff39d62d38ac3350192cc43b174 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 17:47:19 -0400 Subject: [PATCH 161/270] Strike incorrect note from `parse_f64_prefix` doc --- core/strconv/strconv.odin | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index 94842617e..d4727d862 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -835,17 +835,21 @@ Example: n, _, ok = strconv.parse_f64_prefix("12.34e2") fmt.printfln("%.3f %v", n, ok) + + n, _, ok = strconv.parse_f64_prefix("13.37 hellope") + fmt.printfln("%.3f %v", n, ok) } Output: 0.000 false 1234.000 true + 13.370 true **Returns** - value: The parsed 64-bit floating point number. - nr: The length of the parsed substring. -- ok: `false` if a base 10 float could not be found, or if the input string contained more than just the number. +- ok: `false` if a base 10 float could not be found */ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { common_prefix_len_ignore_case :: proc "contextless" (s, prefix: string) -> int { From 1fc6ff91b205836fad0fb30b36bfd042e381aa19 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:17:57 -0400 Subject: [PATCH 162/270] Add test for `infinity` with trailing characters --- tests/core/strconv/test_core_strconv.odin | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/core/strconv/test_core_strconv.odin b/tests/core/strconv/test_core_strconv.odin index d33ac06b6..ed4adaf01 100644 --- a/tests/core/strconv/test_core_strconv.odin +++ b/tests/core/strconv/test_core_strconv.odin @@ -136,4 +136,19 @@ test_infinity :: proc(t: ^testing.T) { testing.expect_value(t, ok, true) testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } + + // Explicitly check how trailing characters are handled. + s = "infinityyyy" + f, ok := strconv.parse_f64(s, &n) + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 8) + testing.expect_value(t, ok, false) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) + + s = "inflippity" + f, ok = strconv.parse_f64(s, &n) + testing.expect_value(t, f, pos_inf) + testing.expect_value(t, n, 3) + testing.expect_value(t, ok, false) + testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } From 25feff3eb4c3845e6da4e8e56010301e316b2dd9 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:50:08 -0400 Subject: [PATCH 163/270] Permit parsing of incomplete `infinity` but do not return true To clarify, `parse_f64` will indeed take `infi` to mean `+Inf` and return that as the value, but it will not return `ok = true`. It treats it as `inf` followed by any other trailing character. `parse_f64_prefix` is the lenient one which will return true so long as it finds some meaningful value. --- core/strconv/strconv.odin | 10 ++++- tests/core/strconv/test_core_strconv.odin | 45 +++++++++-------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index 990b2be2f..60b4b4e2e 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -879,9 +879,15 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { fallthrough case 'i', 'I': m := common_prefix_len_ignore_case(s, "infinity") - if m == 3 || m == 8 { // "inf" or "infinity" + if 3 <= m && m < 9 { // "inf" to "infinity" f = 0h7ff00000_00000000 if sign == 1 else 0hfff00000_00000000 - n = nsign + m + if m == 8 { + // We only count the entire prefix if it is precisely "infinity". + n = nsign + m + } else { + // The string was either only "inf" or incomplete. + n = nsign + 3 + } ok = true return } diff --git a/tests/core/strconv/test_core_strconv.odin b/tests/core/strconv/test_core_strconv.odin index ed4adaf01..6b70654cc 100644 --- a/tests/core/strconv/test_core_strconv.odin +++ b/tests/core/strconv/test_core_strconv.odin @@ -62,18 +62,15 @@ test_infinity :: proc(t: ^testing.T) { n: int s := "infinity" - for i in 1 ..< len(s) + 1 { + for i in 0 ..< len(s) + 1 { ss := s[:i] f, ok := strconv.parse_f64(ss, &n) - if i == 3 { // "inf" + if i >= 3 { // "inf" .. "infinity" + expected_n := 8 if i == 8 else 3 + expected_ok := i == 3 || i == 8 testing.expect_value(t, f, pos_inf) - testing.expect_value(t, n, 3) - testing.expect_value(t, ok, true) - testing.expect_value(t, math.classify(f), math.Float_Class.Inf) - } else if i == 8 { // "infinity" - testing.expect_value(t, f, pos_inf) - testing.expect_value(t, n, 8) - testing.expect_value(t, ok, true) + testing.expect_value(t, n, expected_n) + testing.expect_value(t, ok, expected_ok) testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } else { // invalid substring testing.expect_value(t, f, 0) @@ -84,18 +81,15 @@ test_infinity :: proc(t: ^testing.T) { } s = "+infinity" - for i in 1 ..< len(s) + 1 { + for i in 0 ..< len(s) + 1 { ss := s[:i] f, ok := strconv.parse_f64(ss, &n) - if i == 4 { // "+inf" + if i >= 4 { // "+inf" .. "+infinity" + expected_n := 9 if i == 9 else 4 + expected_ok := i == 4 || i == 9 testing.expect_value(t, f, pos_inf) - testing.expect_value(t, n, 4) - testing.expect_value(t, ok, true) - testing.expect_value(t, math.classify(f), math.Float_Class.Inf) - } else if i == 9 { // "+infinity" - testing.expect_value(t, f, pos_inf) - testing.expect_value(t, n, 9) - testing.expect_value(t, ok, true) + testing.expect_value(t, n, expected_n) + testing.expect_value(t, ok, expected_ok) testing.expect_value(t, math.classify(f), math.Float_Class.Inf) } else { // invalid substring testing.expect_value(t, f, 0) @@ -106,18 +100,15 @@ test_infinity :: proc(t: ^testing.T) { } s = "-infinity" - for i in 1 ..< len(s) + 1 { + for i in 0 ..< len(s) + 1 { ss := s[:i] f, ok := strconv.parse_f64(ss, &n) - if i == 4 { // "-inf" + if i >= 4 { // "-inf" .. "infinity" + expected_n := 9 if i == 9 else 4 + expected_ok := i == 4 || i == 9 testing.expect_value(t, f, neg_inf) - testing.expect_value(t, n, 4) - testing.expect_value(t, ok, true) - testing.expect_value(t, math.classify(f), math.Float_Class.Neg_Inf) - } else if i == 9 { // "-infinity" - testing.expect_value(t, f, neg_inf) - testing.expect_value(t, n, 9) - testing.expect_value(t, ok, true) + testing.expect_value(t, n, expected_n) + testing.expect_value(t, ok, expected_ok) testing.expect_value(t, math.classify(f), math.Float_Class.Neg_Inf) } else { // invalid substring testing.expect_value(t, f, 0) From b2f9f0af689ff6ffdd9db149c04a99cd6b2a46b4 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 4 Jun 2024 19:01:30 -0400 Subject: [PATCH 164/270] Fix some typos --- base/runtime/docs.odin | 2 +- core/odin/ast/ast.odin | 2 +- core/time/{iso8061.odin => iso8601.odin} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename core/time/{iso8061.odin => iso8601.odin} (100%) diff --git a/base/runtime/docs.odin b/base/runtime/docs.odin index 865eeb9ef..f6b439aa0 100644 --- a/base/runtime/docs.odin +++ b/base/runtime/docs.odin @@ -157,7 +157,7 @@ __dynamic_map_get // dynamic map calls __dynamic_map_set // dynamic map calls -## Dynamic literals ([dymamic]T and map[K]V) (can be disabled with -no-dynamic-literals) +## Dynamic literals ([dynamic]T and map[K]V) (can be disabled with -no-dynamic-literals) __dynamic_array_reserve __dynamic_array_append diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index 7891fb12d..229f03d3d 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -753,7 +753,7 @@ Array_Type :: struct { using node: Expr, open: tokenizer.Pos, tag: ^Expr, - len: ^Expr, // Ellipsis node for [?]T arrray types, nil for slice types + len: ^Expr, // Ellipsis node for [?]T array types, nil for slice types close: tokenizer.Pos, elem: ^Expr, } diff --git a/core/time/iso8061.odin b/core/time/iso8601.odin similarity index 100% rename from core/time/iso8061.odin rename to core/time/iso8601.odin From 58f07698e849ca7542c53f9687b6c3d4a7f478e9 Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Wed, 5 Jun 2024 10:18:47 +0200 Subject: [PATCH 165/270] Added arm64 support for NetBSD --- .github/workflows/ci.yml | 3 ++- src/build_settings.cpp | 17 +++++++++++++++-- src/gb/gb.h | 3 +++ src/threading.cpp | 2 ++ 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aefc8add5..33bc303f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build, Check, and Test - timeout-minutes: 25 + timeout-minutes: 15 uses: vmactions/netbsd-vm@v1 with: release: "10.0" @@ -31,6 +31,7 @@ jobs: ./odin version ./odin report ./odin check examples/all -vet -strict-style -target:netbsd_amd64 + ./odin check examples/all -vet -strict-style -target:netbsd_arm64 (cd tests/core; gmake all_bsd) (cd tests/internal; gmake all_bsd) (cd tests/issues; ./run.sh) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 3c7ff3f1e..a7e455818 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1039,6 +1039,13 @@ gb_global TargetMetrics target_netbsd_amd64 = { str_lit("x86_64-unknown-netbsd-elf"), }; +gb_global TargetMetrics target_netbsd_arm64 = { + TargetOs_netbsd, + TargetArch_arm64, + 8, 8, 16, 16, + str_lit("aarch64-unknown-netbsd-elf"), +}; + gb_global TargetMetrics target_haiku_amd64 = { TargetOs_haiku, TargetArch_amd64, @@ -1154,8 +1161,10 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freebsd_amd64"), &target_freebsd_amd64 }, { str_lit("freebsd_arm64"), &target_freebsd_arm64 }, - { str_lit("openbsd_amd64"), &target_openbsd_amd64 }, { str_lit("netbsd_amd64"), &target_netbsd_amd64 }, + { str_lit("netbsd_arm64"), &target_netbsd_arm64 }, + + { str_lit("openbsd_amd64"), &target_openbsd_amd64 }, { str_lit("haiku_amd64"), &target_haiku_amd64 }, { str_lit("freestanding_wasm32"), &target_freestanding_wasm32 }, @@ -1916,7 +1925,11 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta #elif defined(GB_SYSTEM_OPENBSD) metrics = &target_openbsd_amd64; #elif defined(GB_SYSTEM_NETBSD) - metrics = &target_netbsd_amd64; + #if defined(GB_CPU_ARM) + metrics = &target_netbsd_arm64; + #else + metrics = &target_netbsd_amd64; + #endif #elif defined(GB_SYSTEM_HAIKU) metrics = &target_haiku_amd64; #elif defined(GB_CPU_ARM) diff --git a/src/gb/gb.h b/src/gb/gb.h index 17d5e97d1..22a30a04b 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -256,6 +256,7 @@ extern "C" { #if defined(GB_SYSTEM_NETBSD) #include + #include #define lseek64 lseek #endif @@ -3027,6 +3028,8 @@ gb_inline u32 gb_thread_current_id(void) { thread_id = find_thread(NULL); #elif defined(GB_SYSTEM_FREEBSD) thread_id = pthread_getthreadid_np(); +#elif defined(GB_SYSTEM_NETBSD) + thread_id = (u32)_lwp_self(); #else #error Unsupported architecture for gb_thread_current_id() #endif diff --git a/src/threading.cpp b/src/threading.cpp index 48c58e8f4..717dcb874 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -494,6 +494,8 @@ gb_internal u32 thread_current_id(void) { thread_id = find_thread(NULL); #elif defined(GB_SYSTEM_FREEBSD) thread_id = pthread_getthreadid_np(); +#elif defined(GB_SYSTEM_NETBSD) + thread_id = (u32)_lwp_self(); #else #error Unsupported architecture for thread_current_id() #endif From ed6667ebf2fd5d85bcafc789c8e5e5adbd7524cc Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Wed, 5 Jun 2024 11:06:14 +0200 Subject: [PATCH 166/270] Propper thread identification on NetBSD --- core/os/os_netbsd.odin | 7 ++++++- core/sync/primitives_netbsd.odin | 8 ++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index e8e551340..ab4f877bb 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -328,6 +328,11 @@ foreign dl { @(link_name="dlerror") _unix_dlerror :: proc() -> cstring --- } +@(private) +foreign libc { + _lwp_self :: proc() -> i32 --- +} + // NOTE(phix): Perhaps share the following functions with FreeBSD if they turn out to be the same in the end. is_path_separator :: proc(r: rune) -> bool { @@ -721,7 +726,7 @@ exit :: proc "contextless" (code: int) -> ! { } current_thread_id :: proc "contextless" () -> int { - return cast(int) unix.pthread_self() + return int(_lwp_self()) } dlopen :: proc(filename: string, flags: int) -> rawptr { diff --git a/core/sync/primitives_netbsd.odin b/core/sync/primitives_netbsd.odin index 042e744e8..594f2ff5c 100644 --- a/core/sync/primitives_netbsd.odin +++ b/core/sync/primitives_netbsd.odin @@ -1,8 +1,12 @@ //+private package sync -import "core:sys/unix" +foreign import libc "system:c" + +foreign libc { + _lwp_self :: proc "c" () -> i32 --- +} _current_thread_id :: proc "contextless" () -> int { - return cast(int) unix.pthread_self() + return int(_lwp_self()) } From 929437c7bc6c5e9efaff988930e2cdc7d07602df Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Wed, 5 Jun 2024 11:17:13 +0200 Subject: [PATCH 167/270] Fixed broken import --- core/os/os_netbsd.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/core/os/os_netbsd.odin b/core/os/os_netbsd.odin index ab4f877bb..c0f237bf5 100644 --- a/core/os/os_netbsd.odin +++ b/core/os/os_netbsd.odin @@ -5,7 +5,6 @@ foreign import libc "system:c" import "base:runtime" import "core:strings" -import "core:sys/unix" import "core:c" Handle :: distinct i32 From f0e98372fb361cfd63e390eb2593b1457bc2e9c6 Mon Sep 17 00:00:00 2001 From: Andreas T Jonsson Date: Wed, 5 Jun 2024 15:37:02 +0200 Subject: [PATCH 168/270] Minor cleanup of makefiles and scripts --- .github/workflows/ci.yml | 1 - tests/internal/Makefile | 10 ++++++---- tests/issues/run.sh | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33bc303f0..2884fb301 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,6 @@ jobs: /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/llvm-17.0.6.tgz /usr/sbin/pkg_add https://github.com/andreas-jonsson/llvm17-netbsd-bin/releases/download/pkgsrc-current/clang-17.0.6.tgz ln -s /usr/pkg/bin/python3.11 /usr/bin/python3 - ln -s /usr/pkg/bin/bash /bin/bash run: | git config --global --add safe.directory $(pwd) gmake release diff --git a/tests/internal/Makefile b/tests/internal/Makefile index fb22767d2..307badb41 100644 --- a/tests/internal/Makefile +++ b/tests/internal/Makefile @@ -1,7 +1,9 @@ ODIN=../../odin COMMON=-file -vet -strict-style -o:minimal -all: asan_test rtti_test map_test pow_test 128_test string_compare_test +all: all_bsd asan_test + +all_bsd: rtti_test map_test pow_test 128_test string_compare_test rtti_test: $(ODIN) test test_rtti.odin $(COMMON) @@ -15,8 +17,8 @@ pow_test: 128_test: $(ODIN) test test_128.odin $(COMMON) +string_compare_test: + $(ODIN) test test_string_compare.odin $(COMMON) + asan_test: $(ODIN) test test_asan.odin $(COMMON) -sanitize:address -debug - -string_compare_test: - $(ODIN) test test_string_compare.odin $(COMMON) \ No newline at end of file diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 24b388b07..a5280485e 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -eu mkdir -p build From cd4375061ce005ae66826a3082c7780528455d5d Mon Sep 17 00:00:00 2001 From: Mark Sniffen Date: Wed, 5 Jun 2024 10:11:18 -0400 Subject: [PATCH 169/270] D3D11 - fixed VIDEO_SUPPORT flag --- vendor/directx/d3d11/d3d11.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/directx/d3d11/d3d11.odin b/vendor/directx/d3d11/d3d11.odin index a1e3cf039..3af0f2965 100644 --- a/vendor/directx/d3d11/d3d11.odin +++ b/vendor/directx/d3d11/d3d11.odin @@ -3374,7 +3374,7 @@ CREATE_DEVICE_FLAG :: enum u32 { DEBUGGABLE = 6, PREVENT_ALTERING_LAYER_SETTINGS_FROM_REGISTRY = 7, DISABLE_GPU_TIMEOUT = 8, - VIDEO_SUPPORT = 12, + VIDEO_SUPPORT = 11, } PFN_CREATE_DEVICE :: #type proc "c" (a0: ^dxgi.IAdapter, a1: DRIVER_TYPE, a2: HMODULE, a3: u32, a4: ^FEATURE_LEVEL, a5: u32, a6: u32, a7: ^^IDevice, a8: ^FEATURE_LEVEL, a9: ^^IDeviceContext) -> HRESULT From d3342c2381a0e9c6f7a47396b44008dfc3b7c97f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 5 Jun 2024 15:15:39 +0100 Subject: [PATCH 170/270] `core:mem/tlsf` - "Two-Level Segregated Fit" memory allocator --- core/mem/tlsf/tlsf.odin | 156 +++++++ core/mem/tlsf/tlsf_internal.odin | 736 +++++++++++++++++++++++++++++++ 2 files changed, 892 insertions(+) create mode 100644 core/mem/tlsf/tlsf.odin create mode 100644 core/mem/tlsf/tlsf_internal.odin diff --git a/core/mem/tlsf/tlsf.odin b/core/mem/tlsf/tlsf.odin new file mode 100644 index 000000000..95adba258 --- /dev/null +++ b/core/mem/tlsf/tlsf.odin @@ -0,0 +1,156 @@ +/* + Copyright 2024 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Matt Conte: Original C implementation, see LICENSE file in this package + Jeroen van Rijn: Source port +*/ + +// package mem_tlsf implements a Two Level Segregated Fit memory allocator. +package mem_tlsf + +import "base:runtime" + +Error :: enum byte { + None = 0, + Invalid_Backing_Allocator = 1, + Invalid_Alignment = 2, + Backing_Buffer_Too_Small = 3, + Backing_Buffer_Too_Large = 4, + Backing_Allocator_Error = 5, +} + + +Allocator :: struct { + // Empty lists point at this block to indicate they are free. + block_null: Block_Header, + + // Bitmaps for free lists. + fl_bitmap: u32 `fmt:"-"`, + sl_bitmap: [FL_INDEX_COUNT]u32 `fmt:"-"`, + + // Head of free lists. + blocks: [FL_INDEX_COUNT][SL_INDEX_COUNT]^Block_Header `fmt:"-"`, + + // Keep track of pools so we can deallocate them. + // If `pool.allocator` is blank, we don't do anything. + // We also use this linked list of pools to report + // statistics like how much memory is still available, + // fragmentation, etc. + pool: Pool, +} +#assert(size_of(Allocator) % ALIGN_SIZE == 0) + + + + +@(require_results) +allocator :: proc(t: ^Allocator) -> runtime.Allocator { + return runtime.Allocator{ + procedure = allocator_proc, + data = t, + } +} + +@(require_results) +create_from_buf :: proc(buf: []byte) -> (control: ^Allocator, err: Error) { + if uintptr(raw_data(buf)) % ALIGN_SIZE != 0 { + return nil, .Invalid_Alignment + } + + pool_bytes := align_down(len(buf) - POOL_OVERHEAD - size_of(Allocator), ALIGN_SIZE) + if pool_bytes < BLOCK_SIZE_MIN { + return nil, .Backing_Buffer_Too_Small + } else if pool_bytes > BLOCK_SIZE_MAX { + return nil, .Backing_Buffer_Too_Large + } + + control = (^Allocator)(raw_data(buf)) + clear(control) + pool_add(control, buf[size_of(Allocator):]) or_return + return +} + +@(require_results) +create_from_allocator :: proc(backing: runtime.Allocator, initial_pool_size: int, new_pool_size := 0) -> (control: ^Allocator, err: Error) { + pool_bytes := align_up(uint(initial_pool_size) + POOL_OVERHEAD + size_of(Allocator), ALIGN_SIZE) + if pool_bytes < BLOCK_SIZE_MIN { + return nil, .Backing_Buffer_Too_Small + } else if pool_bytes > BLOCK_SIZE_MAX { + return nil, .Backing_Buffer_Too_Large + } + + if buf, backing_err := runtime.make_aligned([]byte, pool_bytes, ALIGN_SIZE, backing); backing_err != nil { + return nil, .Backing_Allocator_Error + } else { + control, err = create_from_buf(buf) + control.pool = Pool{ + data = buf, + allocator = backing, + } + } + return +} +create :: proc{create_from_buf, create_from_allocator} + +destroy :: proc(control: ^Allocator) { + if control == nil { return } + + // No need to call `pool_remove` or anything, as they're they're embedded in the backing memory. + // We do however need to free the `Pool` tracking entities and the backing memory itself. + // As `Allocator` is embedded in the first backing slice, the `control` pointer will be + // invalid after this call. + for p := control.pool.next; p != nil; { + next := p.next + + // Free the allocation on the backing allocator + runtime.delete(p.data, p.allocator) + free(p, p.allocator) + + p = next + } +} + +allocator_proc :: proc(allocator_data: rawptr, mode: runtime.Allocator_Mode, + size, alignment: int, + old_memory: rawptr, old_size: int, location := #caller_location) -> ([]byte, runtime.Allocator_Error) { + + control := (^Allocator)(allocator_data) + if control == nil { + return nil, .Invalid_Argument + } + + switch mode { + case .Alloc: + return alloc_bytes(control, uint(size), uint(alignment)) + case .Alloc_Non_Zeroed: + return alloc_bytes_non_zeroed(control, uint(size), uint(alignment)) + + case .Free: + free_with_size(control, old_memory, uint(old_size)) + return nil, nil + + case .Free_All: + clear(control) + return nil, nil + + case .Resize: + return resize(control, old_memory, uint(old_size), uint(size), uint(alignment)) + + case .Resize_Non_Zeroed: + return resize_non_zeroed(control, old_memory, uint(old_size), uint(size), uint(alignment)) + + case .Query_Features: + set := (^runtime.Allocator_Mode_Set)(old_memory) + if set != nil { + set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Free_All, .Resize, .Resize_Non_Zeroed, .Query_Features} + } + return nil, nil + + case .Query_Info: + return nil, .Mode_Not_Implemented + } + + return nil, nil +} \ No newline at end of file diff --git a/core/mem/tlsf/tlsf_internal.odin b/core/mem/tlsf/tlsf_internal.odin new file mode 100644 index 000000000..d92c88e23 --- /dev/null +++ b/core/mem/tlsf/tlsf_internal.odin @@ -0,0 +1,736 @@ +/* + Copyright 2024 Jeroen van Rijn . + Made available under Odin's BSD-3 license. + + List of contributors: + Matt Conte: Original C implementation, see LICENSE file in this package + Jeroen van Rijn: Source port +*/ + + +package mem_tlsf + +import "base:intrinsics" +import "base:runtime" +// import "core:fmt" + +// log2 of number of linear subdivisions of block sizes. +// Larger values require more memory in the control structure. +// Values of 4 or 5 are typical. +TLSF_SL_INDEX_COUNT_LOG2 :: #config(TLSF_SL_INDEX_COUNT_LOG2, 5) + +// All allocation sizes and addresses are aligned to 4/8 bytes +ALIGN_SIZE_LOG2 :: 3 when size_of(uintptr) == 8 else 2 + +// We can increase this to support larger allocation sizes, +// at the expense of more overhead in the TLSF structure +FL_INDEX_MAX :: 32 when size_of(uintptr) == 8 else 30 +#assert(FL_INDEX_MAX < 36) + +ALIGN_SIZE :: 1 << ALIGN_SIZE_LOG2 +SL_INDEX_COUNT :: 1 << TLSF_SL_INDEX_COUNT_LOG2 +FL_INDEX_SHIFT :: TLSF_SL_INDEX_COUNT_LOG2 + ALIGN_SIZE_LOG2 +FL_INDEX_COUNT :: FL_INDEX_MAX - FL_INDEX_SHIFT + 1 +SMALL_BLOCK_SIZE :: 1 << FL_INDEX_SHIFT + +/* +We support allocations of sizes up to (1 << `FL_INDEX_MAX`) bits. +However, because we linearly subdivide the second-level lists, and +our minimum size granularity is 4 bytes, it doesn't make sense to +create first-level lists for sizes smaller than `SL_INDEX_COUNT` * 4, +or (1 << (`TLSF_SL_INDEX_COUNT_LOG2` + 2)) bytes, as there we will be +trying to split size ranges into more slots than we have available. +Instead, we calculate the minimum threshold size, and place all +blocks below that size into the 0th first-level list. +*/ + +// SL_INDEX_COUNT must be <= number of bits in sl_bitmap's storage tree +#assert(size_of(uint) * 8 >= SL_INDEX_COUNT) + +// Ensure we've properly tuned our sizes. +#assert(ALIGN_SIZE == SMALL_BLOCK_SIZE / SL_INDEX_COUNT) + +#assert(size_of(Allocator) % ALIGN_SIZE == 0) + +Pool :: struct { + data: []u8 `fmt:"-"`, + allocator: runtime.Allocator, + next: ^Pool, +} + + +/* +Block header structure. + +There are several implementation subtleties involved: +- The `prev_phys_block` field is only valid if the previous block is free. +- The `prev_phys_block` field is actually stored at the end of the + previous block. It appears at the beginning of this structure only to + simplify the implementation. +- The `next_free` / `prev_free` fields are only valid if the block is free. +*/ +Block_Header :: struct { + prev_phys_block: ^Block_Header, + size: uint, // The size of this block, excluding the block header + + // Next and previous free blocks. + next_free: ^Block_Header, + prev_free: ^Block_Header, +} +#assert(offset_of(Block_Header, prev_phys_block) == 0) + +/* +Since block sizes are always at least a multiple of 4, the two least +significant bits of the size field are used to store the block status: +- bit 0: whether block is busy or free +- bit 1: whether previous block is busy or free +*/ +BLOCK_HEADER_FREE :: uint(1 << 0) +BLOCK_HEADER_PREV_FREE :: uint(1 << 1) + +/* +The size of the block header exposed to used blocks is the `size` field. +The `prev_phys_block` field is stored *inside* the previous free block. +*/ +BLOCK_HEADER_OVERHEAD :: uint(size_of(uint)) + +POOL_OVERHEAD :: 2 * BLOCK_HEADER_OVERHEAD + +// User data starts directly after the size field in a used block. +BLOCK_START_OFFSET :: offset_of(Block_Header, size) + size_of(Block_Header{}.size) + +/* +A free block must be large enough to store its header minus the size of +the `prev_phys_block` field, and no larger than the number of addressable +bits for `FL_INDEX`. +*/ +BLOCK_SIZE_MIN :: uint(size_of(Block_Header) - size_of(^Block_Header)) +BLOCK_SIZE_MAX :: uint(1) << FL_INDEX_MAX + +/* + TLSF achieves O(1) cost for `alloc` and `free` operations by limiting + the search for a free block to a free list of guaranteed size + adequate to fulfill the request, combined with efficient free list + queries using bitmasks and architecture-specific bit-manipulation + routines. + + NOTE: TLSF spec relies on ffs/fls returning value 0..31. +*/ + +@(require_results) +ffs :: proc "contextless" (word: u32) -> (bit: i32) { + return -1 if word == 0 else i32(intrinsics.count_trailing_zeros(word)) +} + +@(require_results) +fls :: proc "contextless" (word: u32) -> (bit: i32) { + return i32(31 - intrinsics.count_leading_zeros(word)) +} +@(require_results) +fls_uint :: proc "contextless" (size: uint) -> (bit: i32) { + N :: size_of(uintptr)-1 + return i32(N - intrinsics.count_leading_zeros(size)) +} + +@(require_results) +block_size :: proc "contextless" (block: ^Block_Header) -> (size: uint) { + return block.size &~ (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE) +} + +block_set_size :: proc "contextless" (block: ^Block_Header, size: uint) { + old_size := block.size + block.size = size | (old_size & (BLOCK_HEADER_FREE | BLOCK_HEADER_PREV_FREE)) +} + +@(require_results) +block_is_last :: proc "contextless" (block: ^Block_Header) -> (is_last: bool) { + return block_size(block) == 0 +} + +@(require_results) +block_is_free :: proc "contextless" (block: ^Block_Header) -> (is_free: bool) { + return (block.size & BLOCK_HEADER_FREE) == BLOCK_HEADER_FREE +} + +block_set_free :: proc "contextless" (block: ^Block_Header) { + block.size |= BLOCK_HEADER_FREE +} + +block_set_used :: proc "contextless" (block: ^Block_Header) { + block.size &~= BLOCK_HEADER_FREE +} + +@(require_results) +block_is_prev_free :: proc "contextless" (block: ^Block_Header) -> (is_prev_free: bool) { + return (block.size & BLOCK_HEADER_PREV_FREE) == BLOCK_HEADER_PREV_FREE +} + +block_set_prev_free :: proc "contextless" (block: ^Block_Header) { + block.size |= BLOCK_HEADER_PREV_FREE +} + +block_set_prev_used :: proc "contextless" (block: ^Block_Header) { + block.size &~= BLOCK_HEADER_PREV_FREE +} + +@(require_results) +block_from_ptr :: proc(ptr: rawptr) -> (block_ptr: ^Block_Header) { + return (^Block_Header)(uintptr(ptr) - BLOCK_START_OFFSET) +} + +@(require_results) +block_to_ptr :: proc(block: ^Block_Header) -> (ptr: rawptr) { + return rawptr(uintptr(block) + BLOCK_START_OFFSET) +} + +// Return location of next block after block of given size. +@(require_results) +offset_to_block :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) { + return (^Block_Header)(uintptr(ptr) + uintptr(size)) +} + +@(require_results) +offset_to_block_backwards :: proc(ptr: rawptr, size: uint) -> (block: ^Block_Header) { + return (^Block_Header)(uintptr(ptr) - uintptr(size)) +} + +// Return location of previous block. +@(require_results) +block_prev :: proc(block: ^Block_Header) -> (prev: ^Block_Header) { + assert(block_is_prev_free(block), "previous block must be free") + return block.prev_phys_block +} + +// Return location of next existing block. +@(require_results) +block_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) { + return offset_to_block(block_to_ptr(block), block_size(block) - BLOCK_HEADER_OVERHEAD) +} + +// Link a new block with its physical neighbor, return the neighbor. +@(require_results) +block_link_next :: proc(block: ^Block_Header) -> (next: ^Block_Header) { + next = block_next(block) + next.prev_phys_block = block + return +} + +block_mark_as_free :: proc(block: ^Block_Header) { + // Link the block to the next block, first. + next := block_link_next(block) + block_set_prev_free(next) + block_set_free(block) +} + +block_mark_as_used :: proc(block: ^Block_Header) { + next := block_next(block) + block_set_prev_used(next) + block_set_used(block) +} + +@(require_results) +align_up :: proc(x, align: uint) -> (aligned: uint) { + assert(0 == (align & (align - 1)), "must align to a power of two") + return (x + (align - 1)) &~ (align - 1) +} + +@(require_results) +align_down :: proc(x, align: uint) -> (aligned: uint) { + assert(0 == (align & (align - 1)), "must align to a power of two") + return x - (x & (align - 1)) +} + +@(require_results) +align_ptr :: proc(ptr: rawptr, align: uint) -> (aligned: rawptr) { + assert(0 == (align & (align - 1)), "must align to a power of two") + align_mask := uintptr(align) - 1 + _ptr := uintptr(ptr) + _aligned := (_ptr + align_mask) &~ (align_mask) + return rawptr(_aligned) +} + +// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. +@(require_results) +adjust_request_size :: proc(size, align: uint) -> (adjusted: uint) { + if size == 0 { + return 0 + } + + // aligned size must not exceed `BLOCK_SIZE_MAX`, or we'll go out of bounds on `sl_bitmap`. + if aligned := align_up(size, align); aligned < BLOCK_SIZE_MAX { + adjusted = min(aligned, BLOCK_SIZE_MAX) + } + return +} + +// Adjust an allocation size to be aligned to word size, and no smaller than internal minimum. +@(require_results) +adjust_request_size_with_err :: proc(size, align: uint) -> (adjusted: uint, err: runtime.Allocator_Error) { + if size == 0 { + return 0, nil + } + + // aligned size must not exceed `BLOCK_SIZE_MAX`, or we'll go out of bounds on `sl_bitmap`. + if aligned := align_up(size, align); aligned < BLOCK_SIZE_MAX { + adjusted = min(aligned, BLOCK_SIZE_MAX) + } else { + err = .Out_Of_Memory + } + return +} + +// TLSF utility functions. In most cases these are direct translations of +// the documentation in the research paper. + +@(optimization_mode="speed", require_results) +mapping_insert :: proc(size: uint) -> (fl, sl: i32) { + if size < SMALL_BLOCK_SIZE { + // Store small blocks in first list. + sl = i32(size) / (SMALL_BLOCK_SIZE / SL_INDEX_COUNT) + } else { + fl = fls_uint(size) + sl = i32(size >> (uint(fl) - TLSF_SL_INDEX_COUNT_LOG2)) ~ (1 << TLSF_SL_INDEX_COUNT_LOG2) + fl -= (FL_INDEX_SHIFT - 1) + } + return +} + +@(optimization_mode="speed", require_results) +mapping_round :: #force_inline proc(size: uint) -> (rounded: uint) { + rounded = size + if size >= SMALL_BLOCK_SIZE { + round := uint(1 << (uint(fls_uint(size) - TLSF_SL_INDEX_COUNT_LOG2))) - 1 + rounded += round + } + return +} + +// This version rounds up to the next block size (for allocations) +@(optimization_mode="speed", require_results) +mapping_search :: proc(size: uint) -> (fl, sl: i32) { + return mapping_insert(mapping_round(size)) +} + +@(require_results) +search_suitable_block :: proc(control: ^Allocator, fli, sli: ^i32) -> (block: ^Block_Header) { + // First, search for a block in the list associated with the given fl/sl index. + fl := fli^; sl := sli^ + + sl_map := control.sl_bitmap[fli^] & (~u32(0) << uint(sl)) + if sl_map == 0 { + // No block exists. Search in the next largest first-level list. + fl_map := control.fl_bitmap & (~u32(0) << uint(fl + 1)) + if fl_map == 0 { + // No free blocks available, memory has been exhausted. + return {} + } + + fl = ffs(fl_map) + fli^ = fl + sl_map = control.sl_bitmap[fl] + } + assert(sl_map != 0, "internal error - second level bitmap is null") + sl = ffs(sl_map) + sli^ = sl + + // Return the first block in the free list. + return control.blocks[fl][sl] +} + +// Remove a free block from the free list. +remove_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) { + prev := block.prev_free + next := block.next_free + assert(prev != nil, "prev_free can not be nil") + assert(next != nil, "next_free can not be nil") + next.prev_free = prev + prev.next_free = next + + // If this block is the head of the free list, set new head. + if control.blocks[fl][sl] == block { + control.blocks[fl][sl] = next + + // If the new head is nil, clear the bitmap + if next == &control.block_null { + control.sl_bitmap[fl] &~= (u32(1) << uint(sl)) + + // If the second bitmap is now empty, clear the fl bitmap + if control.sl_bitmap[fl] == 0 { + control.fl_bitmap &~= (u32(1) << uint(fl)) + } + } + } +} + +// Insert a free block into the free block list. +insert_free_block :: proc(control: ^Allocator, block: ^Block_Header, fl: i32, sl: i32) { + current := control.blocks[fl][sl] + assert(current != nil, "free lists cannot have a nil entry") + assert(block != nil, "cannot insert a nil entry into the free list") + block.next_free = current + block.prev_free = &control.block_null + current.prev_free = block + + assert(block_to_ptr(block) == align_ptr(block_to_ptr(block), ALIGN_SIZE), "block not properly aligned") + + // Insert the new block at the head of the list, and mark the first- and second-level bitmaps appropriately. + control.blocks[fl][sl] = block + control.fl_bitmap |= (u32(1) << uint(fl)) + control.sl_bitmap[fl] |= (u32(1) << uint(sl)) +} + +// Remove a given block from the free list. +block_remove :: proc(control: ^Allocator, block: ^Block_Header) { + fl, sl := mapping_insert(block_size(block)) + remove_free_block(control, block, fl, sl) +} + +// Insert a given block into the free list. +block_insert :: proc(control: ^Allocator, block: ^Block_Header) { + fl, sl := mapping_insert(block_size(block)) + insert_free_block(control, block, fl, sl) +} + +@(require_results) +block_can_split :: proc(block: ^Block_Header, size: uint) -> (can_split: bool) { + return block_size(block) >= size_of(Block_Header) + size +} + +// Split a block into two, the second of which is free. +@(require_results) +block_split :: proc(block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) { + // Calculate the amount of space left in the remaining block. + remaining = offset_to_block(block_to_ptr(block), size - BLOCK_HEADER_OVERHEAD) + + remain_size := block_size(block) - (size + BLOCK_HEADER_OVERHEAD) + + assert(block_to_ptr(remaining) == align_ptr(block_to_ptr(remaining), ALIGN_SIZE), + "remaining block not aligned properly") + + assert(block_size(block) == remain_size + size + BLOCK_HEADER_OVERHEAD) + block_set_size(remaining, remain_size) + assert(block_size(remaining) >= BLOCK_SIZE_MIN, "block split with invalid size") + + block_set_size(block, size) + block_mark_as_free(remaining) + + return remaining +} + +// Absorb a free block's storage into an adjacent previous free block. +@(require_results) +block_absorb :: proc(prev: ^Block_Header, block: ^Block_Header) -> (absorbed: ^Block_Header) { + assert(!block_is_last(prev), "previous block can't be last") + // Note: Leaves flags untouched. + prev.size += block_size(block) + BLOCK_HEADER_OVERHEAD + _ = block_link_next(prev) + return prev +} + +// Merge a just-freed block with an adjacent previous free block. +@(require_results) +block_merge_prev :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) { + merged = block + if (block_is_prev_free(block)) { + prev := block_prev(block) + assert(prev != nil, "prev physical block can't be nil") + assert(block_is_free(prev), "prev block is not free though marked as such") + block_remove(control, prev) + merged = block_absorb(prev, block) + } + return merged +} + +// Merge a just-freed block with an adjacent free block. +@(require_results) +block_merge_next :: proc(control: ^Allocator, block: ^Block_Header) -> (merged: ^Block_Header) { + merged = block + next := block_next(block) + assert(next != nil, "next physical block can't be nil") + + if (block_is_free(next)) { + assert(!block_is_last(block), "previous block can't be last") + block_remove(control, next) + merged = block_absorb(block, next) + } + return merged +} + +// Trim any trailing block space off the end of a free block, return to pool. +block_trim_free :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { + assert(block_is_free(block), "block must be free") + if (block_can_split(block, size)) { + remaining_block := block_split(block, size) + _ = block_link_next(block) + block_set_prev_free(remaining_block) + block_insert(control, remaining_block) + } +} + +// Trim any trailing block space off the end of a used block, return to pool. +block_trim_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) { + assert(!block_is_free(block), "Block must be used") + if (block_can_split(block, size)) { + // If the next block is free, we must coalesce. + remaining_block := block_split(block, size) + block_set_prev_used(remaining_block) + + remaining_block = block_merge_next(control, remaining_block) + block_insert(control, remaining_block) + } +} + +// Trim leading block space, return to pool. +@(require_results) +block_trim_free_leading :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (remaining: ^Block_Header) { + remaining = block + if block_can_split(block, size) { + // We want the 2nd block. + remaining = block_split(block, size - BLOCK_HEADER_OVERHEAD) + block_set_prev_free(remaining) + + _ = block_link_next(block) + block_insert(control, block) + } + return remaining +} + +@(require_results) +block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Header) { + fl, sl: i32 + if size != 0 { + fl, sl = mapping_search(size) + + /* + `mapping_search` can futz with the size, so for excessively large sizes it can sometimes wind up + with indices that are off the end of the block array. So, we protect against that here, + since this is the only call site of `mapping_search`. Note that we don't need to check `sl`, + as it comes from a modulo operation that guarantees it's always in range. + */ + if fl < FL_INDEX_COUNT { + block = search_suitable_block(control, &fl, &sl) + } + } + + if block != nil { + assert(block_size(block) >= size) + remove_free_block(control, block, fl, sl) + } + return block +} + +@(require_results) +block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) { + if block != nil { + assert(size != 0, "Size must be non-zero") + block_trim_free(control, block, size) + block_mark_as_used(block) + res = ([^]byte)(block_to_ptr(block))[:size] + } + return +} + +// Clear control structure and point all empty lists at the null block +clear :: proc(control: ^Allocator) { + control.block_null.next_free = &control.block_null + control.block_null.prev_free = &control.block_null + + control.fl_bitmap = 0 + for i in 0.. (err: Error) { + assert(uintptr(raw_data(pool)) % ALIGN_SIZE == 0, "Added memory must be aligned") + + pool_overhead := POOL_OVERHEAD + pool_bytes := align_down(len(pool) - pool_overhead, ALIGN_SIZE) + + if pool_bytes < BLOCK_SIZE_MIN { + return .Backing_Buffer_Too_Small + } else if pool_bytes > BLOCK_SIZE_MAX { + return .Backing_Buffer_Too_Large + } + + // Create the main free block. Offset the start of the block slightly, + // so that the `prev_phys_block` field falls outside of the pool - + // it will never be used. + block := offset_to_block_backwards(raw_data(pool), BLOCK_HEADER_OVERHEAD) + + block_set_size(block, pool_bytes) + block_set_free(block) + block_set_prev_used(block) + block_insert(control, block) + + // Split the block to create a zero-size sentinel block + next := block_link_next(block) + block_set_size(next, 0) + block_set_used(next) + block_set_prev_free(next) + return +} + +pool_remove :: proc(control: ^Allocator, pool: []u8) { + block := offset_to_block_backwards(raw_data(pool), BLOCK_HEADER_OVERHEAD) + + assert(block_is_free(block), "Block should be free") + assert(!block_is_free(block_next(block)), "Next block should not be free") + assert(block_size(block_next(block)) == 0, "Next block size should be zero") + + fl, sl := mapping_insert(block_size(block)) + remove_free_block(control, block, fl, sl) +} + +@(require_results) +alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) { + adjust := adjust_request_size(size, ALIGN_SIZE) + + GAP_MINIMUM :: size_of(Block_Header) + size_with_gap := adjust_request_size(adjust + align + GAP_MINIMUM, align) + + aligned_size := size_with_gap if adjust != 0 && align > ALIGN_SIZE else adjust + if aligned_size == 0 && size > 0 { + return nil, .Out_Of_Memory + } + + block := block_locate_free(control, aligned_size) + if block == nil { + return nil, .Out_Of_Memory + } + ptr := block_to_ptr(block) + aligned := align_ptr(ptr, align) + gap := uint(int(uintptr(aligned)) - int(uintptr(ptr))) + + if gap != 0 && gap < GAP_MINIMUM { + gap_remain := GAP_MINIMUM - gap + offset := uintptr(max(gap_remain, align)) + next_aligned := rawptr(uintptr(aligned) + offset) + + aligned = align_ptr(next_aligned, align) + + gap = uint(int(uintptr(aligned)) - int(uintptr(ptr))) + } + + if gap != 0 { + assert(gap >= GAP_MINIMUM, "gap size too small") + block = block_trim_free_leading(control, block, gap) + } + + return block_prepare_used(control, block, adjust) +} + +@(require_results) +alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) { + res, err = alloc_bytes_non_zeroed(control, size, align) + if err != nil { + intrinsics.mem_zero(raw_data(res), len(res)) + } + return +} + + +free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) { + // `size` is currently ignored + if ptr == nil { + return + } + + block := block_from_ptr(ptr) + assert(!block_is_free(block), "block already marked as free") // double free + block_mark_as_free(block) + block = block_merge_prev(control, block) + block = block_merge_next(control, block) + block_insert(control, block) +} + + +@(require_results) +resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) { + // `size` is currently ignored + + if ptr != nil && new_size == 0 { + free_with_size(control, ptr, old_size) + return + } else if ptr == nil { + return alloc_bytes(control, new_size, alignment) + } + + block := block_from_ptr(ptr) + next := block_next(block) + + curr_size := block_size(block) + combined := curr_size + block_size(next) + BLOCK_HEADER_OVERHEAD + adjust := adjust_request_size(new_size, max(ALIGN_SIZE, alignment)) + + assert(!block_is_free(block), "block already marked as free") // double free + + min_size := min(curr_size, new_size, old_size) + + if adjust > curr_size && (!block_is_free(next) || adjust > combined) { + res = alloc_bytes(control, new_size, alignment) or_return + if res != nil { + copy(res, ([^]byte)(ptr)[:min_size]) + free_with_size(control, ptr, curr_size) + } + return + } + if adjust > curr_size { + _ = block_merge_next(control, block) + block_mark_as_used(block) + } + + block_trim_used(control, block, adjust) + res = ([^]byte)(ptr)[:new_size] + + if min_size < new_size { + to_zero := ([^]byte)(ptr)[min_size:new_size] + runtime.mem_zero(raw_data(to_zero), len(to_zero)) + } + return +} + +@(require_results) +resize_non_zeroed :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) { + // `size` is currently ignored + + if ptr != nil && new_size == 0 { + free_with_size(control, ptr, old_size) + return + } else if ptr == nil { + return alloc_bytes_non_zeroed(control, new_size, alignment) + } + + block := block_from_ptr(ptr) + next := block_next(block) + + curr_size := block_size(block) + combined := curr_size + block_size(next) + BLOCK_HEADER_OVERHEAD + adjust := adjust_request_size(new_size, max(ALIGN_SIZE, alignment)) + + assert(!block_is_free(block), "block already marked as free") // double free + + min_size := min(curr_size, new_size, old_size) + + if adjust > curr_size && (!block_is_free(next) || adjust > combined) { + res = alloc_bytes_non_zeroed(control, new_size, alignment) or_return + if res != nil { + copy(res, ([^]byte)(ptr)[:min_size]) + free_with_size(control, ptr, old_size) + } + return + } + + if adjust > curr_size { + _ = block_merge_next(control, block) + block_mark_as_used(block) + } + + block_trim_used(control, block, adjust) + res = ([^]byte)(ptr)[:new_size] + return +} \ No newline at end of file From fac9ce5d83542e9dadcc8aae23a3f8c2e0143745 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 5 Jun 2024 15:30:35 +0100 Subject: [PATCH 171/270] Change to `init` from `create` --- core/mem/tlsf/tlsf.odin | 44 ++++++++++++++++---------------- core/mem/tlsf/tlsf_internal.odin | 16 ++++++------ 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/core/mem/tlsf/tlsf.odin b/core/mem/tlsf/tlsf.odin index 95adba258..76ecbb4b1 100644 --- a/core/mem/tlsf/tlsf.odin +++ b/core/mem/tlsf/tlsf.odin @@ -54,45 +54,45 @@ allocator :: proc(t: ^Allocator) -> runtime.Allocator { } @(require_results) -create_from_buf :: proc(buf: []byte) -> (control: ^Allocator, err: Error) { +init_from_buffer :: proc(control: ^Allocator, buf: []byte) -> Error { + assert(control != nil) if uintptr(raw_data(buf)) % ALIGN_SIZE != 0 { - return nil, .Invalid_Alignment + return .Invalid_Alignment } - pool_bytes := align_down(len(buf) - POOL_OVERHEAD - size_of(Allocator), ALIGN_SIZE) + pool_bytes := align_down(len(buf) - POOL_OVERHEAD, ALIGN_SIZE) if pool_bytes < BLOCK_SIZE_MIN { - return nil, .Backing_Buffer_Too_Small + return .Backing_Buffer_Too_Small } else if pool_bytes > BLOCK_SIZE_MAX { - return nil, .Backing_Buffer_Too_Large + return .Backing_Buffer_Too_Large } - control = (^Allocator)(raw_data(buf)) clear(control) - pool_add(control, buf[size_of(Allocator):]) or_return - return + return pool_add(control, buf[:]) } @(require_results) -create_from_allocator :: proc(backing: runtime.Allocator, initial_pool_size: int, new_pool_size := 0) -> (control: ^Allocator, err: Error) { - pool_bytes := align_up(uint(initial_pool_size) + POOL_OVERHEAD + size_of(Allocator), ALIGN_SIZE) +init_from_allocator :: proc(control: ^Allocator, backing: runtime.Allocator, initial_pool_size: int, new_pool_size := 0) -> Error { + assert(control != nil) + pool_bytes := align_up(uint(initial_pool_size) + POOL_OVERHEAD, ALIGN_SIZE) if pool_bytes < BLOCK_SIZE_MIN { - return nil, .Backing_Buffer_Too_Small + return .Backing_Buffer_Too_Small } else if pool_bytes > BLOCK_SIZE_MAX { - return nil, .Backing_Buffer_Too_Large + return .Backing_Buffer_Too_Large } - if buf, backing_err := runtime.make_aligned([]byte, pool_bytes, ALIGN_SIZE, backing); backing_err != nil { - return nil, .Backing_Allocator_Error - } else { - control, err = create_from_buf(buf) - control.pool = Pool{ - data = buf, - allocator = backing, - } + buf, backing_err := runtime.make_aligned([]byte, pool_bytes, ALIGN_SIZE, backing) + if backing_err != nil { + return .Backing_Allocator_Error } - return + err := init_from_buffer(control, buf) + control.pool = Pool{ + data = buf, + allocator = backing, + } + return err } -create :: proc{create_from_buf, create_from_allocator} +init :: proc{init_from_buffer, init_from_allocator} destroy :: proc(control: ^Allocator) { if control == nil { return } diff --git a/core/mem/tlsf/tlsf_internal.odin b/core/mem/tlsf/tlsf_internal.odin index d92c88e23..1dc9c5da7 100644 --- a/core/mem/tlsf/tlsf_internal.odin +++ b/core/mem/tlsf/tlsf_internal.odin @@ -522,10 +522,10 @@ block_locate_free :: proc(control: ^Allocator, size: uint) -> (block: ^Block_Hea @(require_results) block_prepare_used :: proc(control: ^Allocator, block: ^Block_Header, size: uint) -> (res: []byte, err: runtime.Allocator_Error) { if block != nil { - assert(size != 0, "Size must be non-zero") - block_trim_free(control, block, size) - block_mark_as_used(block) - res = ([^]byte)(block_to_ptr(block))[:size] + assert(size != 0, "Size must be non-zero") + block_trim_free(control, block, size) + block_mark_as_used(block) + res = ([^]byte)(block_to_ptr(block))[:size] } return } @@ -588,6 +588,7 @@ pool_remove :: proc(control: ^Allocator, pool: []u8) { @(require_results) alloc_bytes_non_zeroed :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byte, err: runtime.Allocator_Error) { + assert(control != nil) adjust := adjust_request_size(size, ALIGN_SIZE) GAP_MINIMUM :: size_of(Block_Header) @@ -635,6 +636,7 @@ alloc_bytes :: proc(control: ^Allocator, size: uint, align: uint) -> (res: []byt free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) { + assert(control != nil) // `size` is currently ignored if ptr == nil { return @@ -651,8 +653,7 @@ free_with_size :: proc(control: ^Allocator, ptr: rawptr, size: uint) { @(require_results) resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) { - // `size` is currently ignored - + assert(control != nil) if ptr != nil && new_size == 0 { free_with_size(control, ptr, old_size) return @@ -696,8 +697,7 @@ resize :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, align @(require_results) resize_non_zeroed :: proc(control: ^Allocator, ptr: rawptr, old_size, new_size: uint, alignment: uint) -> (res: []byte, err: runtime.Allocator_Error) { - // `size` is currently ignored - + assert(control != nil) if ptr != nil && new_size == 0 { free_with_size(control, ptr, old_size) return From cbabcb0907e6430571c14d21fcb09c150275d99b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 5 Jun 2024 15:43:15 +0100 Subject: [PATCH 172/270] Fix #3682 --- src/check_expr.cpp | 7 +++++++ src/llvm_backend_expr.cpp | 20 ++++++++++++++++++-- src/types.cpp | 9 +++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 0830f65bd..d2d01deda 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -8866,6 +8866,10 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, SliceArray.elem; break; + case Type_BitField: + is_constant = false; + ft = bt->BitField.fields[index]->type; + break; default: GB_PANIC("invalid type: %s", type_to_string(ft)); break; @@ -8892,6 +8896,9 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, SliceArray.elem; break; + case Type_BitField: + nested_ft = bt->BitField.fields[index]->type; + break; default: GB_PANIC("invalid type %s", type_to_string(nested_ft)); break; diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index c12489598..7772ba930 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4533,10 +4533,26 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { if (lb_is_nested_possibly_constant(type, sel, elem)) { continue; } - lbValue dst = lb_emit_deep_field_gep(p, comp_lit_ptr, sel); field_expr = lb_build_expr(p, elem); field_expr = lb_emit_conv(p, field_expr, sel.entity->type); - lb_emit_store(p, dst, field_expr); + if (sel.is_bit_field) { + Selection sub_sel = trim_selection(sel); + lbValue trimmed_dst = lb_emit_deep_field_gep(p, comp_lit_ptr, sub_sel); + Type *bf = base_type(type_deref(trimmed_dst.type)); + if (is_type_pointer(bf)) { + trimmed_dst = lb_emit_load(p, trimmed_dst); + bf = base_type(type_deref(trimmed_dst.type)); + } + GB_ASSERT(bf->kind == Type_BitField); + + isize idx = sel.index[sel.index.count-1]; + lbAddr dst = lb_addr_bit_field(trimmed_dst, bf->BitField.fields[idx]->type, bf->BitField.bit_offsets[idx], bf->BitField.bit_sizes[idx]); + lb_addr_store(p, dst, field_expr); + + } else { + lbValue dst = lb_emit_deep_field_gep(p, comp_lit_ptr, sel); + lb_emit_store(p, dst, field_expr); + } continue; } diff --git a/src/types.cpp b/src/types.cpp index e568d2af2..618e5bd8a 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -457,6 +457,15 @@ gb_internal Selection sub_selection(Selection const &sel, isize offset) { return res; } +gb_internal Selection trim_selection(Selection const &sel) { + Selection res = {}; + res.index.data = sel.index.data; + res.index.count = gb_max(sel.index.count - 1, 0); + res.index.capacity = res.index.count; + return res; +} + + gb_global Type basic_types[] = { {Type_Basic, {Basic_Invalid, 0, 0, STR_LIT("invalid type")}}, From 8455e159f5f6c6b1a3553d82ea891232fd56d336 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 5 Jun 2024 20:57:39 +0200 Subject: [PATCH 173/270] improve orca target --- base/runtime/core.odin | 15 ++++++- base/runtime/default_allocators_general.odin | 2 +- base/runtime/entry_wasm.odin | 36 +++++++++++----- base/runtime/error_checks.odin | 4 ++ base/runtime/heap_allocator_orca.odin | 29 +++++++++++++ base/runtime/heap_allocator_other.odin | 2 +- base/runtime/os_specific_orca.odin | 43 ++++++++++++++++++++ base/runtime/procs.odin | 2 +- core/fmt/fmt_os.odin | 1 + core/time/time_orca.odin | 24 +++++++++++ src/build_settings.cpp | 9 ++-- src/linker.cpp | 36 +++++++++++----- src/main.cpp | 28 +++++++++++++ 13 files changed, 200 insertions(+), 31 deletions(-) create mode 100644 base/runtime/heap_allocator_orca.odin create mode 100644 base/runtime/os_specific_orca.odin create mode 100644 core/time/time_orca.odin diff --git a/base/runtime/core.odin b/base/runtime/core.odin index 47b9a690c..3e24060af 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -701,7 +701,7 @@ default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code when ODIN_OS == .Freestanding { // Do nothing } else { - when !ODIN_DISABLE_ASSERT { + when ODIN_OS != .Orca && !ODIN_DISABLE_ASSERT { print_caller_location(loc) print_string(" ") } @@ -710,7 +710,18 @@ default_assertion_failure_proc :: proc(prefix, message: string, loc: Source_Code print_string(": ") print_string(message) } - print_byte('\n') + + when ODIN_OS == .Orca { + assert_fail( + cstring(raw_data(loc.file_path)), + cstring(raw_data(loc.procedure)), + loc.line, + "", + cstring(raw_data(orca_stderr_buffer[:orca_stderr_buffer_idx])), + ) + } else { + print_byte('\n') + } } trap() } diff --git a/base/runtime/default_allocators_general.odin b/base/runtime/default_allocators_general.odin index ab4dd1db8..64af6c904 100644 --- a/base/runtime/default_allocators_general.odin +++ b/base/runtime/default_allocators_general.odin @@ -6,7 +6,7 @@ when ODIN_DEFAULT_TO_NIL_ALLOCATOR { } else when ODIN_DEFAULT_TO_PANIC_ALLOCATOR { default_allocator_proc :: panic_allocator_proc default_allocator :: panic_allocator -} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { +} else when ODIN_OS != .Orca && (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) { default_allocator :: default_wasm_allocator default_allocator_proc :: wasm_allocator_proc } else { diff --git a/base/runtime/entry_wasm.odin b/base/runtime/entry_wasm.odin index c608942ba..a24c6f4b7 100644 --- a/base/runtime/entry_wasm.odin +++ b/base/runtime/entry_wasm.odin @@ -6,15 +6,29 @@ package runtime import "base:intrinsics" when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { - @(link_name="_start", linkage="strong", require, export) - _start :: proc "c" () { - context = default_context() - #force_no_inline _startup_runtime() - intrinsics.__entry_point() + when ODIN_OS == .Orca { + @(linkage="strong", require, export) + oc_on_init :: proc "c" () { + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + } + @(linkage="strong", require, export) + oc_on_terminate :: proc "c" () { + context = default_context() + #force_no_inline _cleanup_runtime() + } + } else { + @(link_name="_start", linkage="strong", require, export) + _start :: proc "c" () { + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + } + @(link_name="_end", linkage="strong", require, export) + _end :: proc "c" () { + context = default_context() + #force_no_inline _cleanup_runtime() + } } - @(link_name="_end", linkage="strong", require, export) - _end :: proc "c" () { - context = default_context() - #force_no_inline _cleanup_runtime() - } -} \ No newline at end of file +} diff --git a/base/runtime/error_checks.odin b/base/runtime/error_checks.odin index 742e06a71..32a895c3f 100644 --- a/base/runtime/error_checks.odin +++ b/base/runtime/error_checks.odin @@ -4,6 +4,8 @@ package runtime bounds_trap :: proc "contextless" () -> ! { when ODIN_OS == .Windows { windows_trap_array_bounds() + } else when ODIN_OS == .Orca { + abort_ext("", "", 0, "bounds trap") } else { trap() } @@ -13,6 +15,8 @@ bounds_trap :: proc "contextless" () -> ! { type_assertion_trap :: proc "contextless" () -> ! { when ODIN_OS == .Windows { windows_trap_type_assertion() + } else when ODIN_OS == .Orca { + abort_ext("", "", 0, "type assertion trap") } else { trap() } diff --git a/base/runtime/heap_allocator_orca.odin b/base/runtime/heap_allocator_orca.odin new file mode 100644 index 000000000..c22a67ca1 --- /dev/null +++ b/base/runtime/heap_allocator_orca.odin @@ -0,0 +1,29 @@ +//+build orca +//+private +package runtime + +foreign { + @(link_name="malloc") _orca_malloc :: proc "c" (size: int) -> rawptr --- + @(link_name="calloc") _orca_calloc :: proc "c" (num, size: int) -> rawptr --- + @(link_name="free") _orca_free :: proc "c" (ptr: rawptr) --- + @(link_name="realloc") _orca_realloc :: proc "c" (ptr: rawptr, size: int) -> rawptr --- +} + +_heap_alloc :: proc(size: int, zero_memory := true) -> rawptr { + if size <= 0 { + return nil + } + if zero_memory { + return _orca_calloc(1, size) + } else { + return _orca_malloc(size) + } +} + +_heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { + return _orca_realloc(ptr, new_size) +} + +_heap_free :: proc(ptr: rawptr) { + _orca_free(ptr) +} diff --git a/base/runtime/heap_allocator_other.odin b/base/runtime/heap_allocator_other.odin index 45049c7e9..74536ada9 100644 --- a/base/runtime/heap_allocator_other.odin +++ b/base/runtime/heap_allocator_other.odin @@ -12,4 +12,4 @@ _heap_resize :: proc(ptr: rawptr, new_size: int) -> rawptr { _heap_free :: proc(ptr: rawptr) { unimplemented("base:runtime 'heap_free' procedure is not supported on this platform") -} \ No newline at end of file +} diff --git a/base/runtime/os_specific_orca.odin b/base/runtime/os_specific_orca.odin new file mode 100644 index 000000000..b6f5930ab --- /dev/null +++ b/base/runtime/os_specific_orca.odin @@ -0,0 +1,43 @@ +//+build orca +//+private +package runtime + +import "base:intrinsics" + +// Constants allowing to specify the level of logging verbosity. +log_level :: enum u32 { + // Only errors are logged. + ERROR = 0, + // Only warnings and errors are logged. + WARNING = 1, + // All messages are logged. + INFO = 2, + COUNT = 3, +} + +@(default_calling_convention="c", link_prefix="oc_") +foreign { + abort_ext :: proc(file: cstring, function: cstring, line: i32, fmt: cstring, #c_vararg args: ..any) -> ! --- + assert_fail :: proc(file: cstring, function: cstring, line: i32, src: cstring, fmt: cstring, #c_vararg args: ..any) -> ! --- + log_ext :: proc(level: log_level, function: cstring, file: cstring, line: i32, fmt: cstring, #c_vararg args: ..any) --- +} + +// NOTE: This is all pretty gross, don't look. + +// WASM is single threaded so this should be fine. +orca_stderr_buffer: [4096]byte +orca_stderr_buffer_idx: int + +_stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { + for b in data { + orca_stderr_buffer[orca_stderr_buffer_idx] = b + orca_stderr_buffer_idx += 1 + + if b == '\n' || orca_stderr_buffer_idx == len(orca_stderr_buffer)-1 { + log_ext(.ERROR, "", "", 0, cstring(raw_data(orca_stderr_buffer[:orca_stderr_buffer_idx]))) + orca_stderr_buffer_idx = 0 + } + } + + return len(data), 0 +} diff --git a/base/runtime/procs.odin b/base/runtime/procs.odin index c9347463b..002a6501f 100644 --- a/base/runtime/procs.odin +++ b/base/runtime/procs.odin @@ -25,7 +25,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { RtlMoveMemory(dst, src, len) return dst } -} else when ODIN_NO_CRT || (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32) { +} else when ODIN_NO_CRT || (ODIN_OS != .Orca && (ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32)) { // NOTE: on wasm, calls to these procs are generated (by LLVM) with type `i32` instead of `int`. // // NOTE: `#any_int` is also needed, because calls that we generate (and package code) diff --git a/core/fmt/fmt_os.odin b/core/fmt/fmt_os.odin index a403dcd65..9de0d43be 100644 --- a/core/fmt/fmt_os.odin +++ b/core/fmt/fmt_os.odin @@ -1,5 +1,6 @@ //+build !freestanding //+build !js +//+build !orca package fmt import "base:runtime" diff --git a/core/time/time_orca.odin b/core/time/time_orca.odin new file mode 100644 index 000000000..d222c8247 --- /dev/null +++ b/core/time/time_orca.odin @@ -0,0 +1,24 @@ +//+private +//+build orca +package time + +_IS_SUPPORTED :: false + +_now :: proc "contextless" () -> Time { + return {} +} + +_sleep :: proc "contextless" (d: Duration) { +} + +_tick_now :: proc "contextless" () -> Tick { + // mul_div_u64 :: proc "contextless" (val, num, den: i64) -> i64 { + // q := val / den + // r := val % den + // return q * num + r * num / den + // } + return {} +} + +_yield :: proc "contextless" () { +} diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 3c7ff3f1e..8a08c2b34 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1080,8 +1080,7 @@ gb_global TargetMetrics target_orca_wasm32 = { TargetOs_orca, TargetArch_wasm32, 4, 4, 8, 16, - str_lit("wasm32-wasi-js"), - // str_lit("e-m:e-p:32:32-i64:64-n32:64-S128"), + str_lit("wasm32-freestanding-js"), }; @@ -1161,6 +1160,7 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freestanding_wasm32"), &target_freestanding_wasm32 }, { str_lit("wasi_wasm32"), &target_wasi_wasm32 }, { str_lit("js_wasm32"), &target_js_wasm32 }, + { str_lit("orca_wasm32"), &target_orca_wasm32 }, { str_lit("freestanding_wasm64p32"), &target_freestanding_wasm64p32 }, { str_lit("js_wasm64p32"), &target_js_wasm64p32 }, @@ -2032,11 +2032,10 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta // } if (bc->no_entry_point || bc->metrics.os == TargetOs_orca) { link_flags = gb_string_appendc(link_flags, "--no-entry "); - bc->no_entry_point = true; // just in case for the "orca" target } - + bc->link_flags = make_string_c(link_flags); - + // Disallow on wasm bc->use_separate_modules = false; } else { diff --git a/src/linker.cpp b/src/linker.cpp index b699c0dfb..91055a604 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -13,6 +13,7 @@ struct LinkerData { }; gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, ...); +gb_internal bool system_exec_command_line_app_output(char const *command, gbString *output); #if defined(GB_SYSTEM_OSX) gb_internal void linker_enable_system_library_linking(LinkerData *ld) { @@ -69,27 +70,42 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (is_arch_wasm()) { timings_start_section(timings, str_lit("wasm-ld")); - String extra_orca_flags = {}; + gbString extra_orca_flags = gb_string_make(temporary_allocator(), ""); + + gbString inputs = gb_string_make(temporary_allocator(), ""); + inputs = gb_string_append_fmt(inputs, "\"%.*s.o\"", LIT(output_filename)); - #if defined(GB_SYSTEM_WINDOWS) if (build_context.metrics.os == TargetOs_orca) { - extra_orca_flags = str_lit(" W:/orca/installation/dev-afb9591/bin/liborca_wasm.a --export-dynamic"); + // TODO: Orca windows. + + gbString orca_sdk_path = gb_string_make(temporary_allocator(), ""); + if (!system_exec_command_line_app_output("orca sdk-path", &orca_sdk_path)) { + gb_printf_err("executing `orca sdk-path` failed, make sure Orca is installed and added to your path\n"); + return 1; + } + if (gb_string_length(orca_sdk_path) == 0) { + gb_printf_err("executing `orca sdk-path` did not produce output\n"); + return 1; + } + inputs = gb_string_append_fmt(inputs, " \"%s/orca-libc/lib/crt1.o\" \"%s/orca-libc/lib/libc.o\"", orca_sdk_path, orca_sdk_path); + + extra_orca_flags = gb_string_append_fmt(extra_orca_flags, " -L \"%s/bin\" -lorca_wasm --export-dynamic", orca_sdk_path); } + + #if defined(GB_SYSTEM_WINDOWS) result = system_exec_command_line_app("wasm-ld", "\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s %.*s", LIT(build_context.ODIN_ROOT), LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags), LIT(extra_orca_flags)); #else - if (build_context.metrics.os == TargetOs_orca) { - extra_orca_flags = str_lit(" -L . -lorca --export-dynamic"); - } - result = system_exec_command_line_app("wasm-ld", - "wasm-ld \"%.*s.o\" -o \"%.*s\" %.*s %.*s %.*s", - LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags), - LIT(extra_orca_flags)); + "wasm-ld %s -o \"%.*s\" %.*s %.*s %s", + inputs, LIT(output_filename), + LIT(build_context.link_flags), + LIT(build_context.extra_linker_flags), + extra_orca_flags); #endif return result; } diff --git a/src/main.cpp b/src/main.cpp index 3ca024ed9..a30cad059 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -155,6 +155,34 @@ gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, return exit_code; } +// TODO: windows. +gb_internal bool system_exec_command_line_app_output(char const *command, gbString *output) { + GB_ASSERT(output); + + u8 buffer[256]; + FILE *stream; + stream = popen(command, "r"); + if (!stream) { + return false; + } + defer (pclose(stream)); + + while (!feof(stream)) { + size_t n = fread(buffer, 1, 255, stream); + *output = gb_string_append_length(*output, buffer, n); + + if (ferror(stream)) { + return false; + } + } + + if (build_context.show_system_calls) { + gb_printf_err("[SYSTEM CALL OUTPUT] %s -> %s\n", command, *output); + } + + return true; +} + gb_internal Array setup_args(int argc, char const **argv) { gbAllocator a = heap_allocator(); From 08382cb05dc23816f36c0a52b23c4d501431f88c Mon Sep 17 00:00:00 2001 From: laytan Date: Wed, 5 Jun 2024 19:26:23 +0200 Subject: [PATCH 174/270] orca windows --- src/build_settings.cpp | 2 +- src/linker.cpp | 10 ++++------ src/main.cpp | 6 +++++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 8a08c2b34..05117a9b2 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1080,7 +1080,7 @@ gb_global TargetMetrics target_orca_wasm32 = { TargetOs_orca, TargetArch_wasm32, 4, 4, 8, 16, - str_lit("wasm32-freestanding-js"), + str_lit("wasm32-wasi-js"), }; diff --git a/src/linker.cpp b/src/linker.cpp index 91055a604..25c54a6ab 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -72,12 +72,10 @@ gb_internal i32 linker_stage(LinkerData *gen) { gbString extra_orca_flags = gb_string_make(temporary_allocator(), ""); - gbString inputs = gb_string_make(temporary_allocator(), ""); + gbString inputs = gb_string_make(temporary_allocator(), ""); inputs = gb_string_append_fmt(inputs, "\"%.*s.o\"", LIT(output_filename)); if (build_context.metrics.os == TargetOs_orca) { - // TODO: Orca windows. - gbString orca_sdk_path = gb_string_make(temporary_allocator(), ""); if (!system_exec_command_line_app_output("orca sdk-path", &orca_sdk_path)) { gb_printf_err("executing `orca sdk-path` failed, make sure Orca is installed and added to your path\n"); @@ -95,10 +93,10 @@ gb_internal i32 linker_stage(LinkerData *gen) { #if defined(GB_SYSTEM_WINDOWS) result = system_exec_command_line_app("wasm-ld", - "\"%.*s\\bin\\wasm-ld\" \"%.*s.o\" -o \"%.*s\" %.*s %.*s %.*s", + "\"%.*s\\bin\\wasm-ld\" %s -o \"%.*s\" %.*s %.*s %s", LIT(build_context.ODIN_ROOT), - LIT(output_filename), LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags), - LIT(extra_orca_flags)); + inputs, LIT(output_filename), LIT(build_context.link_flags), LIT(build_context.extra_linker_flags), + extra_orca_flags); #else result = system_exec_command_line_app("wasm-ld", "wasm-ld %s -o \"%.*s\" %.*s %.*s %s", diff --git a/src/main.cpp b/src/main.cpp index a30cad059..70def5802 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -155,7 +155,11 @@ gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, return exit_code; } -// TODO: windows. +#if defined(GB_SYSTEM_WINDOWS) +#define popen _popen +#define pclose _pclose +#endif + gb_internal bool system_exec_command_line_app_output(char const *command, gbString *output) { GB_ASSERT(output); From 460ffe1aee6688fa9887b861ad4ab9a2fd431598 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 5 Jun 2024 21:04:35 +0200 Subject: [PATCH 175/270] Rewrite upload_b2 nightly action against B2 SDK --- .github/workflows/nightly.yml | 31 ++------ ci/create_nightly_json.py | 51 ------------- ci/delete_old_binaries.py | 33 -------- ci/nightly.py | 140 ++++++++++++++++++++++++++++++++++ ci/upload_create_nightly.sh | 25 ------ 5 files changed, 148 insertions(+), 132 deletions(-) delete mode 100644 ci/create_nightly_json.py delete mode 100644 ci/delete_old_binaries.py create mode 100644 ci/nightly.py delete mode 100755 ci/upload_create_nightly.sh diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 221ab1cdb..8d8392f95 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -151,11 +151,11 @@ jobs: with: python-version: '3.8.x' - - name: Install B2 CLI + - name: Install B2 SDK shell: bash run: | python -m pip install --upgrade pip - pip install --upgrade b2 + pip install --upgrade b2sdk - name: Display Python version run: python -c "import sys; print(sys.version)" @@ -188,24 +188,9 @@ jobs: BUCKET: ${{ secrets.B2_BUCKET }} DAYS_TO_KEEP: ${{ secrets.B2_DAYS_TO_KEEP }} run: | - echo Authorizing B2 account - b2 account authorize "$APPID" "$APPKEY" - - echo Uploading artifcates to B2 - chmod +x ./ci/upload_create_nightly.sh - ./ci/upload_create_nightly.sh "$BUCKET" windows-amd64 windows_artifacts/ - ./ci/upload_create_nightly.sh "$BUCKET" ubuntu-amd64 ubuntu_artifacts/dist.zip - ./ci/upload_create_nightly.sh "$BUCKET" macos-amd64 macos_artifacts/dist.zip - ./ci/upload_create_nightly.sh "$BUCKET" macos-arm64 macos_arm_artifacts/dist.zip - - echo Deleting old artifacts in B2 - python3 ci/delete_old_binaries.py "$BUCKET" "$DAYS_TO_KEEP" - - echo Creating nightly.json - python3 ci/create_nightly_json.py "$BUCKET" > nightly.json - - echo Uploading nightly.json - b2 upload-file "$BUCKET" nightly.json nightly.json - - echo Clear B2 account info - b2 clear-account + python3 ci/nightly.py artifact windows-amd64 windows_artifacts/ + python3 ci/nightly.py artifact ubuntu-amd64 ubuntu_artifacts/dist.zip + python3 ci/nightly.py artifact macos-amd64 macos_artifacts/dist.zip + python3 ci/nightly.py artifact macos-arm64 macos_arm_artifacts/dist.zip + python3 ci/nightly.py prune + python3 ci/nightly.py json \ No newline at end of file diff --git a/ci/create_nightly_json.py b/ci/create_nightly_json.py deleted file mode 100644 index 3a1fcd1ef..000000000 --- a/ci/create_nightly_json.py +++ /dev/null @@ -1,51 +0,0 @@ -import subprocess -import sys -import json -import datetime -import urllib.parse -import sys - -def main(): - files_by_date = {} - bucket = sys.argv[1] - - files_lines = execute_cli(f"b2 ls --long b2://{bucket}/nightly/").split("\n") - for x in files_lines: - parts = x.split(" ", 1) - if parts[0]: - print(f"Parts[0]: {parts[0]}", flush=True) - json_str = execute_cli(f"b2 file info b2://{bucket}/{parts[0]}") - data = json.loads(json_str) - name = remove_prefix(data['fileName'], "nightly/") - url = f"https://f001.backblazeb2.com/file/{bucket}/nightly/{urllib.parse.quote_plus(name)}" - sha1 = data['contentSha1'] - size = int(data['size']) - ts = int(data['fileInfo']['src_last_modified_millis']) - date = datetime.datetime.fromtimestamp(ts/1000).strftime('%Y-%m-%d') - - if date not in files_by_date.keys(): - files_by_date[date] = [] - - files_by_date[date].append({ - 'name': name, - 'url': url, - 'sha1': sha1, - 'sizeInBytes': size, - }) - - now = datetime.datetime.utcnow().isoformat() - - print(json.dumps({ - 'last_updated' : now, - 'files': files_by_date - }, sort_keys=True, indent=4)) - -def remove_prefix(text, prefix): - return text[text.startswith(prefix) and len(prefix):] - -def execute_cli(command): - sb = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - return sb.stdout.read().decode("utf-8"); - -if __name__ == '__main__': - sys.exit(main()) \ No newline at end of file diff --git a/ci/delete_old_binaries.py b/ci/delete_old_binaries.py deleted file mode 100644 index f7d5d02af..000000000 --- a/ci/delete_old_binaries.py +++ /dev/null @@ -1,33 +0,0 @@ -import subprocess -import sys -import json -import datetime -import urllib.parse -import sys - -def main(): - files_by_date = {} - bucket = sys.argv[1] - days_to_keep = int(sys.argv[2]) - print(f"Looking for binaries to delete older than {days_to_keep} days") - - files_lines = execute_cli(f"b2 ls --long --versions b2://{bucket}/nightly/").split("\n") - for x in files_lines: - parts = [y for y in x.split(' ') if y] - - if parts and parts[0]: - date = datetime.datetime.strptime(parts[2], '%Y-%m-%d').replace(hour=0, minute=0, second=0, microsecond=0) - now = datetime.datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) - delta = now - date - - if delta.days > days_to_keep: - print(f'Deleting b2://{bucket}/{parts[5]}') - execute_cli(f'b2 rm b2://{bucket}/{parts[5]}') - - -def execute_cli(command): - sb = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - return sb.stdout.read().decode("utf-8"); - -if __name__ == '__main__': - sys.exit(main()) \ No newline at end of file diff --git a/ci/nightly.py b/ci/nightly.py new file mode 100644 index 000000000..7a23fd2c2 --- /dev/null +++ b/ci/nightly.py @@ -0,0 +1,140 @@ +import os +import sys +from zipfile import ZipFile +from b2sdk.v2 import InMemoryAccountInfo, B2Api +from datetime import datetime +import json + +UPLOAD_FOLDER = "nightly/" + +info = InMemoryAccountInfo() +b2_api = B2Api(info) +application_key_id = os.environ['APPID'] +application_key = os.environ['APPKEY'] +bucket_name = os.environ['BUCKET'] +days_to_keep = os.environ['DAYS_TO_KEEP'] + +def auth() -> bool: + try: + realm = b2_api.account_info.get_realm() + return True # Already authenticated + except: + pass # Not yet authenticated + + err = b2_api.authorize_account("production", application_key_id, application_key) + return err == None + +def get_bucket(): + if not auth(): sys.exit(1) + return b2_api.get_bucket_by_name(bucket_name) + +def remove_prefix(text: str, prefix: str) -> str: + return text[text.startswith(prefix) and len(prefix):] + +def create_and_upload_artifact_zip(platform: str, artifact: str) -> int: + now = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) + destination_zip_name = "odin-{}-nightly+{}.zip".format(platform, now.strftime("%Y-%m-%d")) + + source_zip_name = artifact + if not artifact.endswith(".zip"): + print(f"Creating archive {destination_zip_name} from {artifact} and uploading to {bucket_name}") + + source_zip_name = destination_zip_name + with ZipFile(source_zip_name, 'w') as z: + for root, directory, filenames in os.walk(artifact): + for file in filenames: + file_path = os.path.join(root, file) + zip_path = os.path.join("dist", os.path.relpath(file_path, artifact)) + z.write(file_path, zip_path) + + if not os.path.exists(source_zip_name): + print(f"Error: Newly created ZIP archive {source_zip_name} not found.") + return 1 + + print("Uploading {} to {}".format(source_zip_name, UPLOAD_FOLDER + destination_zip_name)) + bucket = get_bucket() + res = bucket.upload_local_file( + source_zip_name, # Local file to upload + "nightly/" + destination_zip_name, # B2 destination path + ) + return 0 + +def prune_artifacts(): + print(f"Looking for binaries to delete older than {days_to_keep} days") + + bucket = get_bucket() + for file, _ in bucket.ls(UPLOAD_FOLDER, latest_only=False): + # Timestamp is in milliseconds + date = datetime.fromtimestamp(file.upload_timestamp / 1_000.0).replace(hour=0, minute=0, second=0, microsecond=0) + now = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) + delta = now - date + + if delta.days > int(days_to_keep): + print("Deleting {}".format(file.file_name)) + file.delete() + + return 0 + +def update_nightly_json(): + print(f"Updating nightly.json with files {days_to_keep} days or newer") + + files_by_date = {} + + bucket = get_bucket() + + for file, _ in bucket.ls(UPLOAD_FOLDER, latest_only=True): + # Timestamp is in milliseconds + date = datetime.fromtimestamp(file.upload_timestamp / 1_000.0).replace(hour=0, minute=0, second=0, microsecond=0).strftime('%Y-%m-%d') + name = remove_prefix(file.file_name, UPLOAD_FOLDER) + sha1 = file.content_sha1 + size = file.size + url = bucket.get_download_url(file.file_name) + + if date not in files_by_date.keys(): + files_by_date[date] = [] + + files_by_date[date].append({ + 'name': name, + 'url': url, + 'sha1': sha1, + 'sizeInBytes': size, + }) + + now = datetime.utcnow().isoformat() + + nightly = json.dumps({ + 'last_updated' : now, + 'files': files_by_date + }, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8') + + res = bucket.upload_bytes( + nightly, # JSON bytes + "nightly.json", # B2 destination path + ) + return 0 + +if __name__ == "__main__": + if len(sys.argv) == 1: + print("Usage: {} [arguments]".format(sys.argv[0])) + print("\tartifact \n\t\tCreates and uploads a platform artifact zip.") + print("\tprune\n\t\tDeletes old artifacts from bucket") + print("\tjson\n\t\tUpdate and upload nightly.json") + sys.exit(1) + else: + command = sys.argv[1].lower() + if command == "artifact": + if len(sys.argv) != 4: + print("Usage: {} artifact ".format(sys.argv[0])) + print("Error: Expected artifact command to be given platform prefix and artifact path.\n") + sys.exit(1) + + res = create_and_upload_artifact_zip(sys.argv[2], sys.argv[3]) + sys.exit(res) + + elif command == "prune": + res = prune_artifacts() + sys.exit(res) + + elif command == "json": + res = update_nightly_json() + sys.exit(res) \ No newline at end of file diff --git a/ci/upload_create_nightly.sh b/ci/upload_create_nightly.sh deleted file mode 100755 index 8404b33ff..000000000 --- a/ci/upload_create_nightly.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -set -e - -bucket=$1 -platform=$2 -artifact=$3 - -now=$(date +'%Y-%m-%d') -filename="odin-$platform-nightly+$now.zip" - -echo "Creating archive $filename from $artifact and uploading to $bucket" - -# If this is already zipped up (done before artifact upload to keep permissions in tact), just move it. -if [ "${artifact: -4}" == ".zip" ] -then - echo "Artifact already a zip" - mkdir -p "output" - mv "$artifact" "output/$filename" -else - echo "Artifact needs to be zipped" - 7z a -bd "output/$filename" -r "$artifact" -fi - -b2 file upload "$bucket" "output/$filename" "nightly/$filename" From fcfc1cb97fedd2f25bed451c4093c64d25b3314d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 5 Jun 2024 22:26:03 +0200 Subject: [PATCH 176/270] Nightly ZIP level 9 --- ci/nightly.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/nightly.py b/ci/nightly.py index 7a23fd2c2..7bd32899d 100644 --- a/ci/nightly.py +++ b/ci/nightly.py @@ -1,6 +1,6 @@ import os import sys -from zipfile import ZipFile +from zipfile import ZipFile, ZIP_DEFLATED from b2sdk.v2 import InMemoryAccountInfo, B2Api from datetime import datetime import json @@ -40,7 +40,7 @@ def create_and_upload_artifact_zip(platform: str, artifact: str) -> int: print(f"Creating archive {destination_zip_name} from {artifact} and uploading to {bucket_name}") source_zip_name = destination_zip_name - with ZipFile(source_zip_name, 'w') as z: + with ZipFile(source_zip_name, mode='w', compression=ZIP_DEFLATED, compresslevel=9) as z: for root, directory, filenames in os.walk(artifact): for file in filenames: file_path = os.path.join(root, file) From 70592630a48509b69a0c19df6bbff02e594d2969 Mon Sep 17 00:00:00 2001 From: Colin Davidson Date: Thu, 6 Jun 2024 02:39:48 -0700 Subject: [PATCH 177/270] add support for title changes, cursor config, and dnd --- vendor/x11/xlib/xlib_const.odin | 5 +++++ vendor/x11/xlib/xlib_procs.odin | 30 ++++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/vendor/x11/xlib/xlib_const.odin b/vendor/x11/xlib/xlib_const.odin index 910940dec..0b87ab9f7 100644 --- a/vendor/x11/xlib/xlib_const.odin +++ b/vendor/x11/xlib/xlib_const.odin @@ -17,6 +17,11 @@ AllTemporary :: 0 CurrentTime :: 0 NoSymbol :: 0 +PropModeReplace :: 0 +PropModePrepend :: 1 +PropModeAppend :: 2 + +XA_ATOM :: Atom(4) XA_WM_CLASS :: Atom(67) XA_WM_CLIENT_MACHINE :: Atom(36) XA_WM_COMMAND :: Atom(34) diff --git a/vendor/x11/xlib/xlib_procs.odin b/vendor/x11/xlib/xlib_procs.odin index 735ddc9c4..20ec5bb39 100644 --- a/vendor/x11/xlib/xlib_procs.odin +++ b/vendor/x11/xlib/xlib_procs.odin @@ -6,6 +6,16 @@ foreign xlib { @(link_name="_Xdebug") _Xdebug: i32 } +foreign import xcursor "system:Xcursor" +@(default_calling_convention="c", link_prefix="X") +foreign xcursor { + cursorGetTheme :: proc(display: ^Display) -> cstring --- + cursorGetDefaultSize :: proc(display: ^Display) -> i32 --- + cursorLibraryLoadImage :: proc(name: cstring, theme: cstring, size: i32) -> rawptr --- + cursorImageLoadCursor :: proc(display: ^Display, img: rawptr) -> Cursor --- + cursorImageDestroy :: proc(img: rawptr) --- +} + /* ---- X11/Xlib.h ---------------------------------------------------------*/ @(default_calling_convention="c", link_prefix="X") @@ -20,11 +30,9 @@ foreign xlib { NoOp :: proc(display: ^Display) --- // Display macros (connection) ConnectionNumber :: proc(display: ^Display) -> i32 --- - ExtendedMaxRequestSize :: - proc(display: ^Display) -> int --- + ExtendedMaxRequestSize :: proc(display: ^Display) -> int --- MaxRequestSize :: proc(display: ^Display) -> int --- - LastKnownRequestProcessed :: - proc(display: ^Display) -> uint --- + LastKnownRequestProcessed :: proc(display: ^Display) -> uint --- NextRequest :: proc(display: ^Display) -> uint --- ProtocolVersion :: proc(display: ^Display) -> i32 --- ProtocolRevision :: proc(display: ^Display) -> i32 --- @@ -46,8 +54,7 @@ foreign xlib { DefaultRootWindow :: proc(display: ^Display) -> Window --- DefaultScreen :: proc(display: ^Display) -> i32 --- DefaultVisual :: proc(display: ^Display, screen_no: i32) -> ^Visual --- - DefaultScreenOfDisplay :: - proc(display: ^Display) -> ^Screen --- + DefaultScreenOfDisplay :: proc(display: ^Display) -> ^Screen --- // Display macros (other) RootWindow :: proc(display: ^Display, screen_no: i32) -> Window --- ScreenCount :: proc(display: ^Display) -> i32 --- @@ -1619,6 +1626,17 @@ foreign xlib { ) -> b32 --- DestroyImage :: proc(image: ^XImage) --- ResourceManagerString :: proc(display: ^Display) -> cstring --- + utf8SetWMProperties :: proc( + display: ^Display, + window: Window, + window_name: cstring, + icon_name: cstring, + argv: ^cstring, + argc: i32, + normal_hints: ^XSizeHints, + wm_hints: ^XWMHints, + class_hints: ^XClassHint, + ) --- } @(default_calling_convention="c") From 483015fe578be20eeeb731c12510bedd21a29f32 Mon Sep 17 00:00:00 2001 From: Hector Date: Thu, 6 Jun 2024 11:47:36 +0100 Subject: [PATCH 178/270] Updated SDL_CreateTexture to take `PixelFormatEnum` instead of `u32` --- vendor/sdl2/sdl_render.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/sdl2/sdl_render.odin b/vendor/sdl2/sdl_render.odin index f948b39b0..cceebf3ac 100644 --- a/vendor/sdl2/sdl_render.odin +++ b/vendor/sdl2/sdl_render.odin @@ -76,7 +76,7 @@ foreign lib { GetRenderer :: proc(window: ^Window) -> ^Renderer --- GetRendererInfo :: proc(renderer: ^Renderer, info: ^RendererInfo) -> c.int --- GetRendererOutputSize :: proc(renderer: ^Renderer, w, h: ^c.int) -> c.int --- - CreateTexture :: proc(renderer: ^Renderer, format: u32, access: TextureAccess, w, h: c.int) -> ^Texture --- + CreateTexture :: proc(renderer: ^Renderer, format: PixelFormatEnum, access: TextureAccess, w, h: c.int) -> ^Texture --- CreateTextureFromSurface :: proc(renderer: ^Renderer, surface: ^Surface) -> ^Texture --- QueryTexture :: proc(texture: ^Texture, format: ^u32, access, w, h: ^c.int) -> c.int --- SetTextureColorMod :: proc(texture: ^Texture, r, g, b: u8) -> c.int --- From 155516b897450ec265cfb53b6e32db90f15544f5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 6 Jun 2024 13:02:08 +0100 Subject: [PATCH 179/270] Fix `-ignore-warnings` --- src/error.cpp | 63 +++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/src/error.cpp b/src/error.cpp index da444e998..2556a8de4 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -407,29 +407,31 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, error_va(pos, end, fmt, va); return; } + if (global_ignore_warnings()) { + return; + } + global_error_collector.warning_count.fetch_add(1); mutex_lock(&global_error_collector.mutex); push_error_value(pos, ErrorValue_Warning); - if (!global_ignore_warnings()) { - if (pos.line == 0) { + if (pos.line == 0) { + error_out_empty(); + error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); + error_out_va(fmt, va); + error_out("\n"); + } else { + // global_error_collector.prev = pos; + if (json_errors()) { error_out_empty(); - error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); - error_out_va(fmt, va); - error_out("\n"); } else { - // global_error_collector.prev = pos; - if (json_errors()) { - error_out_empty(); - } else { - error_out_pos(pos); - } - error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); - error_out_va(fmt, va); - error_out("\n"); - show_error_on_line(pos, end); + error_out_pos(pos); } + error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); + error_out_va(fmt, va); + error_out("\n"); + show_error_on_line(pos, end); } try_pop_error_value(); mutex_unlock(&global_error_collector.mutex); @@ -544,30 +546,31 @@ gb_internal void syntax_warning_va(TokenPos const &pos, TokenPos end, char const syntax_error_va(pos, end, fmt, va); return; } + if (global_ignore_warnings()) { + return; + } mutex_lock(&global_error_collector.mutex); global_error_collector.warning_count++; push_error_value(pos, ErrorValue_Warning); - if (!global_ignore_warnings()) { - if (pos.line == 0) { + if (pos.line == 0) { + error_out_empty(); + error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); + error_out_va(fmt, va); + error_out("\n"); + } else { + // global_error_collector.prev = pos; + if (json_errors()) { error_out_empty(); - error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); - error_out_va(fmt, va); - error_out("\n"); } else { - // global_error_collector.prev = pos; - if (json_errors()) { - error_out_empty(); - } else { - error_out_pos(pos); - } - error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); - error_out_va(fmt, va); - error_out("\n"); - // show_error_on_line(pos, end); + error_out_pos(pos); } + error_out_coloured("Syntax Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); + error_out_va(fmt, va); + error_out("\n"); + // show_error_on_line(pos, end); } try_pop_error_value(); From 971229fe66cdc93135e0f7e41cdf2a0bce28cd67 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 6 Jun 2024 13:27:55 +0100 Subject: [PATCH 180/270] Fix #3686 --- src/llvm_backend_expr.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 7772ba930..36af60e46 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -504,6 +504,10 @@ gb_internal bool lb_is_matrix_simdable(Type *t) { if ((mt->Matrix.row_count & 1) ^ (mt->Matrix.column_count & 1)) { return false; } + if (mt->Matrix.is_row_major) { + // TODO(bill): make #row_major matrices work with SIMD + return false; + } if (elem->kind == Type_Basic) { switch (elem->Basic.kind) { From 0b6d73c86ed37ec9b23bcebb291644d7504dd344 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 6 Jun 2024 14:29:28 +0200 Subject: [PATCH 181/270] Add original LICENSE --- core/mem/tlsf/LICENSE | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 core/mem/tlsf/LICENSE diff --git a/core/mem/tlsf/LICENSE b/core/mem/tlsf/LICENSE new file mode 100644 index 000000000..9d668ce02 --- /dev/null +++ b/core/mem/tlsf/LICENSE @@ -0,0 +1,36 @@ +Original BSD-3 license: + +Two Level Segregated Fit memory allocator, version 3.1. +Written by Matthew Conte + http://tlsf.baisoku.org + +Based on the original documentation by Miguel Masmano: + http://www.gii.upv.es/tlsf/main/docs + +This implementation was written to the specification +of the document, therefore no GPL restrictions apply. + +Copyright (c) 2006-2016, Matthew Conte +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL MATTHEW CONTE BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file From 72f6b5479dd77a69be78faac2afaee7c3a1c2a6d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 6 Jun 2024 14:42:19 +0200 Subject: [PATCH 182/270] Fix `fls_uint` --- core/mem/tlsf/tlsf_internal.odin | 12 ++++++--- tests/core/mem/test_core_mem.odin | 41 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 tests/core/mem/test_core_mem.odin diff --git a/core/mem/tlsf/tlsf_internal.odin b/core/mem/tlsf/tlsf_internal.odin index 1dc9c5da7..9c8f5c6d8 100644 --- a/core/mem/tlsf/tlsf_internal.odin +++ b/core/mem/tlsf/tlsf_internal.odin @@ -126,10 +126,14 @@ ffs :: proc "contextless" (word: u32) -> (bit: i32) { fls :: proc "contextless" (word: u32) -> (bit: i32) { return i32(31 - intrinsics.count_leading_zeros(word)) } -@(require_results) -fls_uint :: proc "contextless" (size: uint) -> (bit: i32) { - N :: size_of(uintptr)-1 - return i32(N - intrinsics.count_leading_zeros(size)) + +when size_of(uintptr) == 8 { + @(require_results) + fls_uint :: proc "contextless" (size: uint) -> (bit: i32) { + return i32(63 - intrinsics.count_leading_zeros(size)) + } +} else { + fls_uint :: fls_u32 } @(require_results) diff --git a/tests/core/mem/test_core_mem.odin b/tests/core/mem/test_core_mem.odin new file mode 100644 index 000000000..d282ae1fd --- /dev/null +++ b/tests/core/mem/test_core_mem.odin @@ -0,0 +1,41 @@ +package test_core_mem + +import "core:mem/tlsf" +import "core:testing" + +@test +test_tlsf_bitscan :: proc(t: ^testing.T) { + Vector :: struct { + op: enum{ffs, fls, fls_uint}, + v: union{u32, uint}, + exp: i32, + } + Tests := []Vector{ + {.ffs, u32 (0x0000_0000_0000_0000), -1}, + {.ffs, u32 (0x0000_0000_0000_0000), -1}, + {.fls, u32 (0x0000_0000_0000_0000), -1}, + {.ffs, u32 (0x0000_0000_0000_0001), 0}, + {.fls, u32 (0x0000_0000_0000_0001), 0}, + {.ffs, u32 (0x0000_0000_8000_0000), 31}, + {.ffs, u32 (0x0000_0000_8000_8000), 15}, + {.fls, u32 (0x0000_0000_8000_0008), 31}, + {.fls, u32 (0x0000_0000_7FFF_FFFF), 30}, + {.fls_uint, uint(0x0000_0000_8000_0000), 31}, + {.fls_uint, uint(0x0000_0001_0000_0000), 32}, + {.fls_uint, uint(0xffff_ffff_ffff_ffff), 63}, + } + + for test in Tests { + switch test.op { + case .ffs: + res := tlsf.ffs(test.v.?) + testing.expectf(t, res == test.exp, "Expected tlsf.ffs(0x%08x) == %v, got %v", test.v, test.exp, res) + case .fls: + res := tlsf.fls(test.v.?) + testing.expectf(t, res == test.exp, "Expected tlsf.fls(0x%08x) == %v, got %v", test.v, test.exp, res) + case .fls_uint: + res := tlsf.fls_uint(test.v.?) + testing.expectf(t, res == test.exp, "Expected tlsf.fls_uint(0x%16x) == %v, got %v", test.v, test.exp, res) + } + } +} \ No newline at end of file From 28ea9425fdd1dca0179873a3fe2c2d64df2e3299 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 6 Jun 2024 14:59:19 +0200 Subject: [PATCH 183/270] Add `core:mem` tests. --- core/mem/tlsf/tlsf_internal.odin | 14 ++++++-------- tests/core/Makefile | 4 ++++ tests/core/build.bat | 5 +++++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/core/mem/tlsf/tlsf_internal.odin b/core/mem/tlsf/tlsf_internal.odin index 9c8f5c6d8..6f33e516c 100644 --- a/core/mem/tlsf/tlsf_internal.odin +++ b/core/mem/tlsf/tlsf_internal.odin @@ -124,16 +124,14 @@ ffs :: proc "contextless" (word: u32) -> (bit: i32) { @(require_results) fls :: proc "contextless" (word: u32) -> (bit: i32) { - return i32(31 - intrinsics.count_leading_zeros(word)) + N :: (size_of(u32) * 8) - 1 + return i32(N - intrinsics.count_leading_zeros(word)) } -when size_of(uintptr) == 8 { - @(require_results) - fls_uint :: proc "contextless" (size: uint) -> (bit: i32) { - return i32(63 - intrinsics.count_leading_zeros(size)) - } -} else { - fls_uint :: fls_u32 +@(require_results) +fls_uint :: proc "contextless" (size: uint) -> (bit: i32) { + N :: (size_of(uint) * 8) - 1 + return i32(N - intrinsics.count_leading_zeros(size)) } @(require_results) diff --git a/tests/core/Makefile b/tests/core/Makefile index 85f3783b4..48683250f 100644 --- a/tests/core/Makefile +++ b/tests/core/Makefile @@ -19,6 +19,7 @@ all_bsd: download_test_assets \ linalg_glsl_math_test \ match_test \ math_test \ + mem_test \ noise_test \ odin_test \ os_exit_test \ @@ -80,6 +81,9 @@ linalg_glsl_math_test: noise_test: $(ODIN) test math/noise $(COMMON) -out:test_noise +mem_test: + $(ODIN) test mem $(COMMON) -out:test_core_mem + net_test: $(ODIN) test net $(COMMON) -out:test_core_net diff --git a/tests/core/build.bat b/tests/core/build.bat index 67ac10f86..aa68975f8 100644 --- a/tests/core/build.bat +++ b/tests/core/build.bat @@ -78,6 +78,11 @@ echo Running core:math/noise tests echo --- %PATH_TO_ODIN% test math/noise %COMMON% -out:test_noise.exe || exit /b +echo --- +echo Running core:mem tests +echo --- +%PATH_TO_ODIN% test mem %COMMON% -out:test_core_mem.exe || exit /b + echo --- echo Running core:net echo --- From 9ef43fc782159893b7af139f9d9be3aec3108ecd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 6 Jun 2024 15:16:34 +0100 Subject: [PATCH 184/270] Add `@(rodata)` --- src/check_decl.cpp | 6 ++++++ src/check_stmt.cpp | 6 ++++++ src/checker.cpp | 6 ++++++ src/checker.hpp | 1 + src/entity.cpp | 1 + src/llvm_backend.cpp | 20 ++++++++++++++++++-- 6 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index f2afce59c..43947836b 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1264,6 +1264,9 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast if (ac.is_static) { error(e->token, "@(static) is not supported for global variables, nor required"); } + if (ac.rodata) { + e->Variable.is_rodata = true; + } ac.link_name = handle_link_name(ctx, e->token, ac.link_name, ac.link_prefix, ac.link_suffix); if (is_arch_wasm() && e->Variable.thread_local_model.len != 0) { @@ -1350,6 +1353,9 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast Operand o = {}; check_expr_with_type_hint(ctx, &o, init_expr, e->type); check_init_variable(ctx, e, &o, str_lit("variable declaration")); + if (e->Variable.is_rodata && o.mode != Addressing_Constant) { + error(o.expr, "Variables declared with @(rodata) must have constant initialization"); + } check_rtti_type_disallowed(e->token, e->type, "A variable declaration is using a type, %s, which has been disallowed"); } diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 2c37bced0..fc443a7b5 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -501,6 +501,9 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O return nullptr; case Addressing_Variable: + if (e && e->kind == Entity_Variable && e->Variable.is_rodata) { + error(lhs->expr, "Assignment to variable '%.*s' marked as @(rodata) is not allowed", LIT(e->token.string)); + } break; case Addressing_MapIndex: { @@ -2055,6 +2058,9 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f } } } + if (ac.rodata) { + error(e->token, "Only global variables can have @(rodata) applied"); + } if (ac.thread_local_model != "") { String name = e->token.string; if (name == "_") { diff --git a/src/checker.cpp b/src/checker.cpp index 97e685d33..8a58bb425 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -3628,6 +3628,12 @@ gb_internal DECL_ATTRIBUTE_PROC(var_decl_attribute) { } ac->is_static = true; return true; + } else if (name == "rodata") { + if (value != nullptr) { + error(elem, "'rodata' does not have any parameters"); + } + ac->rodata = true; + return true; } else if (name == "thread_local") { ExactValue ev = check_decl_attribute_value(c, value); if (ac->init_expr_list_count > 0) { diff --git a/src/checker.hpp b/src/checker.hpp index e793540e3..2ac4c8e7a 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -133,6 +133,7 @@ struct AttributeContext { bool entry_point_only : 1; bool instrumentation_enter : 1; bool instrumentation_exit : 1; + bool rodata : 1; u32 optimization_mode; // ProcedureOptimizationMode i64 foreign_import_priority_index; String extra_linker_flags; diff --git a/src/entity.cpp b/src/entity.cpp index e4fc66dac..7f484e308 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -230,6 +230,7 @@ struct Entity { bool is_foreign; bool is_export; bool is_global; + bool is_rodata; } Variable; struct { Type * type_parameter_specialization; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 03c17a8bb..5dc6d94d5 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1160,6 +1160,10 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc if (is_type_untyped_nil(init.type)) { LLVMSetInitializer(var.var.value, LLVMConstNull(global_type)); var.is_initialized = true; + + if (e->Variable.is_rodata) { + LLVMSetGlobalConstant(var.var.value, true); + } continue; } GB_PANIC("Invalid init value, got %s", expr_to_string(init_expr)); @@ -1174,6 +1178,10 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc } LLVMSetInitializer(var.var.value, init.value); var.is_initialized = true; + + if (e->Variable.is_rodata) { + LLVMSetGlobalConstant(var.var.value, true); + } continue; } } else { @@ -1206,8 +1214,9 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc var.is_initialized = true; } + + } - CheckerInfo *info = main_module->gen->info; for (Entity *e : info->init_procedures) { @@ -3210,14 +3219,21 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { lbValue init = lb_const_value(m, tav.type, v); LLVMSetInitializer(g.value, init.value); var.is_initialized = true; + if (e->kind == Entity_Variable && e->Variable.is_rodata) { + LLVMSetGlobalConstant(g.value, true); + } } } } if (!var.is_initialized && is_type_untyped_nil(tav.type)) { var.is_initialized = true; + if (e->kind == Entity_Variable && e->Variable.is_rodata) { + LLVMSetGlobalConstant(g.value, true); + } } + } else if (e->kind == Entity_Variable && e->Variable.is_rodata) { + LLVMSetGlobalConstant(g.value, true); } - array_add(&global_variables, var); lb_add_entity(m, e, g); From bea47db4953559dbbcdce1da5dbaf38d0bb8d943 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 6 Jun 2024 15:20:47 +0100 Subject: [PATCH 185/270] Allow `@(rodata)` on `@(static)` variables --- src/check_stmt.cpp | 6 +++++- src/llvm_backend_stmt.cpp | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index fc443a7b5..a1698bbfe 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -2059,7 +2059,11 @@ gb_internal void check_value_decl_stmt(CheckerContext *ctx, Ast *node, u32 mod_f } } if (ac.rodata) { - error(e->token, "Only global variables can have @(rodata) applied"); + if (ac.is_static) { + e->Variable.is_rodata = true; + } else { + error(e->token, "Only global or @(static) variables can have @(rodata) applied"); + } } if (ac.thread_local_model != "") { String name = e->token.string; diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index b18db4e45..9f28e45e0 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -1850,7 +1850,9 @@ gb_internal void lb_build_static_variables(lbProcedure *p, AstValueDecl *vd) { LLVMSetInitializer(global, LLVMConstNull(lb_type(p->module, e->type))); if (value.value != nullptr) { LLVMSetInitializer(global, value.value); - } else { + } + if (e->Variable.is_rodata) { + LLVMSetGlobalConstant(global, true); } if (e->Variable.thread_local_model != "") { LLVMSetThreadLocal(global, true); From 3a9b86628a484aa03b594599653c1cab4b916c8e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 6 Jun 2024 15:23:52 +0100 Subject: [PATCH 186/270] Add `@(rodata)` and `@(static, rodata)` where appropriate --- base/runtime/internal.odin | 4 ++-- core/math/cmplx/cmplx_trig.odin | 2 +- core/math/math.odin | 18 +++++++++--------- core/math/math_gamma.odin | 6 +++--- core/math/math_lgamma.odin | 14 +++++++------- core/math/math_sincos.odin | 2 +- core/math/rand/exp.odin | 6 +++--- core/math/rand/normal.odin | 6 +++--- core/os/os2/internal_util.odin | 2 +- core/strconv/generic_float.odin | 2 +- core/strconv/strconv.odin | 2 +- core/time/time.odin | 1 + core/unicode/tables.odin | 10 ++++++++++ 13 files changed, 43 insertions(+), 32 deletions(-) diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index 8e1b3d633..378eea256 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -483,7 +483,7 @@ quaternion256_ne :: #force_inline proc "contextless" (a, b: quaternion256) -> bo string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int) { // NOTE(bill): Duplicated here to remove dependency on package unicode/utf8 - @static accept_sizes := [256]u8{ + @(static, rodata) accept_sizes := [256]u8{ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x00-0x0f 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x10-0x1f 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // 0x20-0x2f @@ -504,7 +504,7 @@ string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int } Accept_Range :: struct {lo, hi: u8} - @static accept_ranges := [5]Accept_Range{ + @(static, rodata) accept_ranges := [5]Accept_Range{ {0x80, 0xbf}, {0xa0, 0xbf}, {0x80, 0x9f}, diff --git a/core/math/cmplx/cmplx_trig.odin b/core/math/cmplx/cmplx_trig.odin index 7ca404fab..15e757506 100644 --- a/core/math/cmplx/cmplx_trig.odin +++ b/core/math/cmplx/cmplx_trig.odin @@ -350,7 +350,7 @@ _reduce_pi_f64 :: proc "contextless" (x: f64) -> f64 #no_bounds_check { // that is, 1/PI = SUM bdpi[i]*2^(-64*i). // 19 64-bit digits give 1216 bits of precision // to handle the largest possible f64 exponent. - @static bdpi := [?]u64{ + @(static, rodata) bdpi := [?]u64{ 0x0000000000000000, 0x517cc1b727220a94, 0xfe13abe8fa9a6ee0, diff --git a/core/math/math.odin b/core/math/math.odin index 8d85c2381..3d0ab3c4e 100644 --- a/core/math/math.odin +++ b/core/math/math.odin @@ -130,10 +130,10 @@ pow10 :: proc{ @(require_results) pow10_f16 :: proc "contextless" (n: f16) -> f16 { - @static pow10_pos_tab := [?]f16{ + @(static, rodata) pow10_pos_tab := [?]f16{ 1e00, 1e01, 1e02, 1e03, 1e04, } - @static pow10_neg_tab := [?]f16{ + @(static, rodata) pow10_neg_tab := [?]f16{ 1e-00, 1e-01, 1e-02, 1e-03, 1e-04, 1e-05, 1e-06, 1e-07, } @@ -151,13 +151,13 @@ pow10_f16 :: proc "contextless" (n: f16) -> f16 { @(require_results) pow10_f32 :: proc "contextless" (n: f32) -> f32 { - @static pow10_pos_tab := [?]f32{ + @(static, rodata) pow10_pos_tab := [?]f32{ 1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29, 1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, } - @static pow10_neg_tab := [?]f32{ + @(static, rodata) pow10_neg_tab := [?]f32{ 1e-00, 1e-01, 1e-02, 1e-03, 1e-04, 1e-05, 1e-06, 1e-07, 1e-08, 1e-09, 1e-10, 1e-11, 1e-12, 1e-13, 1e-14, 1e-15, 1e-16, 1e-17, 1e-18, 1e-19, 1e-20, 1e-21, 1e-22, 1e-23, 1e-24, 1e-25, 1e-26, 1e-27, 1e-28, 1e-29, @@ -179,16 +179,16 @@ pow10_f32 :: proc "contextless" (n: f32) -> f32 { @(require_results) pow10_f64 :: proc "contextless" (n: f64) -> f64 { - @static pow10_tab := [?]f64{ + @(static, rodata) pow10_tab := [?]f64{ 1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, 1e09, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29, 1e30, 1e31, } - @static pow10_pos_tab32 := [?]f64{ + @(static, rodata) pow10_pos_tab32 := [?]f64{ 1e00, 1e32, 1e64, 1e96, 1e128, 1e160, 1e192, 1e224, 1e256, 1e288, } - @static pow10_neg_tab32 := [?]f64{ + @(static, rodata) pow10_neg_tab32 := [?]f64{ 1e-00, 1e-32, 1e-64, 1e-96, 1e-128, 1e-160, 1e-192, 1e-224, 1e-256, 1e-288, 1e-320, } @@ -1274,7 +1274,7 @@ binomial :: proc "contextless" (n, k: int) -> int { @(require_results) factorial :: proc "contextless" (n: int) -> int { when size_of(int) == size_of(i64) { - @static table := [21]int{ + @(static, rodata) table := [21]int{ 1, 1, 2, @@ -1298,7 +1298,7 @@ factorial :: proc "contextless" (n: int) -> int { 2_432_902_008_176_640_000, } } else { - @static table := [13]int{ + @(static, rodata) table := [13]int{ 1, 1, 2, diff --git a/core/math/math_gamma.odin b/core/math/math_gamma.odin index 00d4b7316..9f5a364d3 100644 --- a/core/math/math_gamma.odin +++ b/core/math/math_gamma.odin @@ -67,7 +67,7 @@ package math // masks any imprecision in the polynomial. @(private="file", require_results) stirling :: proc "contextless" (x: f64) -> (f64, f64) { - @(static) gamS := [?]f64{ + @(static, rodata) gamS := [?]f64{ +7.87311395793093628397e-04, -2.29549961613378126380e-04, -2.68132617805781232825e-03, @@ -103,7 +103,7 @@ gamma_f64 :: proc "contextless" (x: f64) -> f64 { return false } - @(static) gamP := [?]f64{ + @(static, rodata) gamP := [?]f64{ 1.60119522476751861407e-04, 1.19135147006586384913e-03, 1.04213797561761569935e-02, @@ -112,7 +112,7 @@ gamma_f64 :: proc "contextless" (x: f64) -> f64 { 4.94214826801497100753e-01, 9.99999999999999996796e-01, } - @(static) gamQ := [?]f64{ + @(static, rodata) gamQ := [?]f64{ -2.31581873324120129819e-05, +5.39605580493303397842e-04, -4.45641913851797240494e-03, diff --git a/core/math/math_lgamma.odin b/core/math/math_lgamma.odin index 0705d8564..828f17178 100644 --- a/core/math/math_lgamma.odin +++ b/core/math/math_lgamma.odin @@ -123,7 +123,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) { return -x } - @static lgamA := [?]f64{ + @(static, rodata) lgamA := [?]f64{ 0h3FB3C467E37DB0C8, 0h3FD4A34CC4A60FAD, 0h3FB13E001A5562A7, @@ -137,7 +137,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) { 0h3EFA7074428CFA52, 0h3F07858E90A45837, } - @static lgamR := [?]f64{ + @(static, rodata) lgamR := [?]f64{ 1.0, 0h3FF645A762C4AB74, 0h3FE71A1893D3DCDC, @@ -146,7 +146,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) { 0h3F497DDACA41A95B, 0h3EDEBAF7A5B38140, } - @static lgamS := [?]f64{ + @(static, rodata) lgamS := [?]f64{ 0hBFB3C467E37DB0C8, 0h3FCB848B36E20878, 0h3FD4D98F4F139F59, @@ -155,7 +155,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) { 0h3F5E26B67368F239, 0h3F00BFECDD17E945, } - @static lgamT := [?]f64{ + @(static, rodata) lgamT := [?]f64{ 0h3FDEF72BC8EE38A2, 0hBFC2E4278DC6C509, 0h3FB08B4294D5419B, @@ -172,7 +172,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) { 0hBF347F24ECC38C38, 0h3F35FD3EE8C2D3F4, } - @static lgamU := [?]f64{ + @(static, rodata) lgamU := [?]f64{ 0hBFB3C467E37DB0C8, 0h3FE4401E8B005DFF, 0h3FF7475CD119BD6F, @@ -180,7 +180,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) { 0h3FCD4EAEF6010924, 0h3F8B678BBF2BAB09, } - @static lgamV := [?]f64{ + @(static, rodata) lgamV := [?]f64{ 1.0, 0h4003A5D7C2BD619C, 0h40010725A42B18F5, @@ -188,7 +188,7 @@ lgamma_f64 :: proc "contextless" (x: f64) -> (lgamma: f64, sign: int) { 0h3FBAAE55D6537C88, 0h3F6A5ABB57D0CF61, } - @static lgamW := [?]f64{ + @(static, rodata) lgamW := [?]f64{ 0h3FDACFE390C97D69, 0h3FB555555555553B, 0hBF66C16C16B02E5C, diff --git a/core/math/math_sincos.odin b/core/math/math_sincos.odin index 578876ac5..b616f410d 100644 --- a/core/math/math_sincos.odin +++ b/core/math/math_sincos.odin @@ -234,7 +234,7 @@ _trig_reduce_f64 :: proc "contextless" (x: f64) -> (j: u64, z: f64) #no_bounds_c // that is, 4/pi = Sum bd_pi4[i]*2^(-64*i) // 19 64-bit digits and the leading one bit give 1217 bits // of precision to handle the largest possible f64 exponent. - @static bd_pi4 := [?]u64{ + @(static, rodata) bd_pi4 := [?]u64{ 0x0000000000000001, 0x45f306dc9c882a53, 0xf84eafa3ea69bb81, diff --git a/core/math/rand/exp.odin b/core/math/rand/exp.odin index 719debe75..ebc849b2f 100644 --- a/core/math/rand/exp.odin +++ b/core/math/rand/exp.odin @@ -19,7 +19,7 @@ import "core:math" exp_float64 :: proc(r: ^Rand = nil) -> f64 { re :: 7.69711747013104972 - @(static) + @(static, rodata) ke := [256]u32{ 0xe290a139, 0x0, 0x9beadebc, 0xc377ac71, 0xd4ddb990, 0xde893fb8, 0xe4a8e87c, 0xe8dff16a, 0xebf2deab, 0xee49a6e8, @@ -74,7 +74,7 @@ exp_float64 :: proc(r: ^Rand = nil) -> f64 { 0xf7b577d2, 0xf69c650c, 0xf51530f0, 0xf2cb0e3c, 0xeeefb15d, 0xe6da6ecf, } - @(static) + @(static, rodata) we := [256]f32{ 2.0249555e-09, 1.486674e-11, 2.4409617e-11, 3.1968806e-11, 3.844677e-11, 4.4228204e-11, 4.9516443e-11, 5.443359e-11, @@ -141,7 +141,7 @@ exp_float64 :: proc(r: ^Rand = nil) -> f64 { 1.2393786e-09, 1.276585e-09, 1.3193139e-09, 1.3695435e-09, 1.4305498e-09, 1.508365e-09, 1.6160854e-09, 1.7921248e-09, } - @(static) + @(static, rodata) fe := [256]f32{ 1, 0.9381437, 0.90046996, 0.87170434, 0.8477855, 0.8269933, 0.8084217, 0.7915276, 0.77595687, 0.7614634, 0.7478686, diff --git a/core/math/rand/normal.odin b/core/math/rand/normal.odin index f96163fe9..c8681db80 100644 --- a/core/math/rand/normal.odin +++ b/core/math/rand/normal.odin @@ -21,7 +21,7 @@ import "core:math" norm_float64 :: proc(r: ^Rand = nil) -> f64 { rn :: 3.442619855899 - @(static) + @(static, rodata) kn := [128]u32{ 0x76ad2212, 0x00000000, 0x600f1b53, 0x6ce447a6, 0x725b46a2, 0x7560051d, 0x774921eb, 0x789a25bd, 0x799045c3, 0x7a4bce5d, @@ -50,7 +50,7 @@ norm_float64 :: proc(r: ^Rand = nil) -> f64 { 0x7da61a1e, 0x7d72a0fb, 0x7d30e097, 0x7cd9b4ab, 0x7c600f1a, 0x7ba90bdc, 0x7a722176, 0x77d664e5, } - @(static) + @(static, rodata) wn := [128]f32{ 1.7290405e-09, 1.2680929e-10, 1.6897518e-10, 1.9862688e-10, 2.2232431e-10, 2.4244937e-10, 2.601613e-10, 2.7611988e-10, @@ -85,7 +85,7 @@ norm_float64 :: proc(r: ^Rand = nil) -> f64 { 1.2601323e-09, 1.2857697e-09, 1.3146202e-09, 1.347784e-09, 1.3870636e-09, 1.4357403e-09, 1.5008659e-09, 1.6030948e-09, } - @(static) + @(static, rodata) fn := [128]f32{ 1.00000000, 0.9635997, 0.9362827, 0.9130436, 0.89228165, 0.87324303, 0.8555006, 0.8387836, 0.8229072, 0.8077383, diff --git a/core/os/os2/internal_util.odin b/core/os/os2/internal_util.odin index 59d845350..e26cf7439 100644 --- a/core/os/os2/internal_util.odin +++ b/core/os/os2/internal_util.odin @@ -111,7 +111,7 @@ next_random :: proc(r: ^[2]u64) -> u64 { @(require_results) random_string :: proc(buf: []byte) -> string { - @static digits := "0123456789" + @(static, rodata) digits := "0123456789" u := next_random(&random_string_seed) diff --git a/core/strconv/generic_float.odin b/core/strconv/generic_float.odin index 6dc11c0be..b049f0fe1 100644 --- a/core/strconv/generic_float.odin +++ b/core/strconv/generic_float.odin @@ -375,7 +375,7 @@ decimal_to_float_bits :: proc(d: ^decimal.Decimal, info: ^Float_Info) -> (b: u64 return } - @static power_table := [?]int{1, 3, 6, 9, 13, 16, 19, 23, 26} + @(static, rodata) power_table := [?]int{1, 3, 6, 9, 13, 16, 19, 23, 26} exp = 0 for d.decimal_point > 0 { diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index 11590fa1b..902f1cdc5 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -1095,7 +1095,7 @@ parse_f64_prefix :: proc(str: string) -> (value: f64, nr: int, ok: bool) { } trunc_block: if !trunc { - @static pow10 := [?]f64{ + @(static, rodata) pow10 := [?]f64{ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, diff --git a/core/time/time.odin b/core/time/time.odin index 4807af840..4575b36f7 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -389,6 +389,7 @@ is_leap_year :: proc "contextless" (year: int) -> (leap: bool) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) } +@(rodata) days_before := [?]i32{ 0, 31, diff --git a/core/unicode/tables.odin b/core/unicode/tables.odin index f43827413..dfa5caaa2 100644 --- a/core/unicode/tables.odin +++ b/core/unicode/tables.odin @@ -12,6 +12,7 @@ package unicode @(private) pLo :: pLl | pLu // a letter that is neither upper nor lower case. @(private) pLmask :: pLo +@(rodata) char_properties := [MAX_LATIN1+1]u8{ 0x00 = pC, // '\x00' 0x01 = pC, // '\x01' @@ -272,6 +273,7 @@ char_properties := [MAX_LATIN1+1]u8{ } +@(rodata) alpha_ranges := [?]i32{ 0x00d8, 0x00f6, 0x00f8, 0x01f5, @@ -427,6 +429,7 @@ alpha_ranges := [?]i32{ 0xffda, 0xffdc, } +@(rodata) alpha_singlets := [?]i32{ 0x00aa, 0x00b5, @@ -462,6 +465,7 @@ alpha_singlets := [?]i32{ 0xfe74, } +@(rodata) space_ranges := [?]i32{ 0x0009, 0x000d, // tab and newline 0x0020, 0x0020, // space @@ -477,6 +481,7 @@ space_ranges := [?]i32{ 0xfeff, 0xfeff, } +@(rodata) unicode_spaces := [?]i32{ 0x0009, // tab 0x000a, // LF @@ -494,6 +499,7 @@ unicode_spaces := [?]i32{ 0xfeff, // unknown } +@(rodata) to_upper_ranges := [?]i32{ 0x0061, 0x007a, 468, // a-z A-Z 0x00e0, 0x00f6, 468, @@ -532,6 +538,7 @@ to_upper_ranges := [?]i32{ 0xff41, 0xff5a, 468, } +@(rodata) to_upper_singlets := [?]i32{ 0x00ff, 621, 0x0101, 499, @@ -875,6 +882,7 @@ to_upper_singlets := [?]i32{ 0x1ff3, 509, } +@(rodata) to_lower_ranges := [?]i32{ 0x0041, 0x005a, 532, // A-Z a-z 0x00c0, 0x00d6, 532, // - - @@ -914,6 +922,7 @@ to_lower_ranges := [?]i32{ 0xff21, 0xff3a, 532, // - - } +@(rodata) to_lower_singlets := [?]i32{ 0x0100, 501, 0x0102, 501, @@ -1250,6 +1259,7 @@ to_lower_singlets := [?]i32{ 0x1ffc, 491, } +@(rodata) to_title_singlets := [?]i32{ 0x01c4, 501, 0x01c6, 499, From 678fdae966e0e4b959a917f11d276b60fb78772f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 6 Jun 2024 16:32:18 +0200 Subject: [PATCH 187/270] Rebased. --- core/image/bmp/bmp.odin | 652 ++++++++++++++++++++++++++ core/image/bmp/bmp_js.odin | 4 + core/image/bmp/bmp_os.odin | 19 + core/image/common.odin | 126 +++++ tests/core/image/test_core_image.odin | 652 +++++++++++++++++++++++--- 5 files changed, 1382 insertions(+), 71 deletions(-) create mode 100644 core/image/bmp/bmp.odin create mode 100644 core/image/bmp/bmp_js.odin create mode 100644 core/image/bmp/bmp_os.odin diff --git a/core/image/bmp/bmp.odin b/core/image/bmp/bmp.odin new file mode 100644 index 000000000..d9ecb55e5 --- /dev/null +++ b/core/image/bmp/bmp.odin @@ -0,0 +1,652 @@ +// package bmp implements a Microsoft BMP image reader +package core_image_bmp + +import "core:image" +import "core:bytes" +import "core:compress" +import "core:mem" +import "base:intrinsics" +import "base:runtime" +@(require) import "core:fmt" + +Error :: image.Error +Image :: image.Image +Options :: image.Options + +RGB_Pixel :: image.RGB_Pixel +RGBA_Pixel :: image.RGBA_Pixel + +FILE_HEADER_SIZE :: 14 +INFO_STUB_SIZE :: FILE_HEADER_SIZE + size_of(image.BMP_Version) + +load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + ctx := &compress.Context_Memory_Input{ + input_data = data, + } + + img, err = load_from_context(ctx, options, allocator) + return img, err +} + +@(optimization_mode="speed") +load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + context.allocator = allocator + options := options + + // For compress.read_slice(), until that's rewritten to not use temp allocator + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + if .info in options { + options |= {.return_metadata, .do_not_decompress_image} + options -= {.info} + } + + if .return_header in options && .return_metadata in options { + options -= {.return_header} + } + + info_buf: [size_of(image.BMP_Header)]u8 + + // Read file header (14) + info size (4) + stub_data := compress.read_slice(ctx, INFO_STUB_SIZE) or_return + copy(info_buf[:], stub_data[:]) + stub_info := transmute(image.BMP_Header)info_buf + + if stub_info.magic != .Bitmap { + for v in image.BMP_Magic { + if stub_info.magic == v { + return img, .Unsupported_OS2_File + } + } + return img, .Invalid_Signature + } + + info: image.BMP_Header + switch stub_info.info_size { + case .OS2_v1: + // Read the remainder of the header + os2_data := compress.read_data(ctx, image.OS2_Header) or_return + + info = transmute(image.BMP_Header)info_buf + info.width = i32le(os2_data.width) + info.height = i32le(os2_data.height) + info.planes = os2_data.planes + info.bpp = os2_data.bpp + + switch info.bpp { + case 1, 4, 8, 24: + case: + return img, .Unsupported_BPP + } + + case .ABBR_16 ..= .V5: + // Sizes include V3, V4, V5 and OS2v2 outright, but can also handle truncated headers. + // Sometimes called BITMAPV2INFOHEADER or BITMAPV3INFOHEADER. + // Let's just try to process it. + + to_read := int(stub_info.info_size) - size_of(image.BMP_Version) + info_data := compress.read_slice(ctx, to_read) or_return + copy(info_buf[INFO_STUB_SIZE:], info_data[:]) + + // Update info struct with the rest of the data we read + info = transmute(image.BMP_Header)info_buf + + case: + return img, .Unsupported_BMP_Version + } + + /* TODO(Jeroen): Add a "strict" option to catch these non-issues that violate spec? + if info.planes != 1 { + return img, .Invalid_Planes_Value + } + */ + + if img == nil { + img = new(Image) + } + img.which = .BMP + + img.metadata = new_clone(image.BMP_Info{ + info = info, + }) + + img.width = abs(int(info.width)) + img.height = abs(int(info.height)) + img.channels = 3 + img.depth = 8 + + if img.width == 0 || img.height == 0 { + return img, .Invalid_Image_Dimensions + } + + total_pixels := abs(img.width * img.height) + if total_pixels > image.MAX_DIMENSIONS { + return img, .Image_Dimensions_Too_Large + } + + // TODO(Jeroen): Handle RGBA. + switch info.compression { + case .Bit_Fields, .Alpha_Bit_Fields: + switch info.bpp { + case 16, 32: + make_output(img, allocator) or_return + decode_rgb(ctx, img, info, allocator) or_return + case: + if is_os2(info.info_size) { + return img, .Unsupported_Compression + } + return img, .Unsupported_BPP + } + case .RGB: + make_output(img, allocator) or_return + decode_rgb(ctx, img, info, allocator) or_return + case .RLE4, .RLE8: + make_output(img, allocator) or_return + decode_rle(ctx, img, info, allocator) or_return + case .CMYK, .CMYK_RLE4, .CMYK_RLE8: fallthrough + case .PNG, .JPEG: fallthrough + case: return img, .Unsupported_Compression + } + + // Flipped vertically + if info.height < 0 { + pixels := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:]) + for y in 0.. (res: bool) { + #partial switch version { + case .OS2_v1, .OS2_v2: return true + case: return false + } +} + +make_output :: proc(img: ^Image, allocator := context.allocator) -> (err: Error) { + assert(img != nil) + bytes_needed := img.channels * img.height * img.width + img.pixels.buf = make([dynamic]u8, bytes_needed, allocator) + if len(img.pixels.buf) != bytes_needed { + return .Unable_To_Allocate_Or_Resize + } + return +} + +write :: proc(img: ^Image, x, y: int, pix: RGB_Pixel) -> (err: Error) { + if y >= img.height || x >= img.width { + return .Corrupt + } + out := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:]) + assert(img.height >= 1 && img.width >= 1) + out[(img.height - y - 1) * img.width + x] = pix + return +} + +Bitmask :: struct { + mask: [4]u32le `fmt:"b"`, + shift: [4]u32le, + bits: [4]u32le, +} + +read_or_make_bit_masks :: proc(ctx: ^$C, info: image.BMP_Header) -> (res: Bitmask, read: int, err: Error) { + ctz :: intrinsics.count_trailing_zeros + c1s :: intrinsics.count_ones + + #partial switch info.compression { + case .RGB: + switch info.bpp { + case 16: + return { + mask = {31 << 10, 31 << 5, 31, 0}, + shift = { 10, 5, 0, 0}, + bits = { 5, 5, 5, 0}, + }, int(4 * info.colors_used), nil + + case 32: + return { + mask = {255 << 16, 255 << 8, 255, 255 << 24}, + shift = { 16, 8, 0, 24}, + bits = { 8, 8, 8, 8}, + }, int(4 * info.colors_used), nil + + case: return {}, 0, .Unsupported_BPP + } + case .Bit_Fields, .Alpha_Bit_Fields: + bf := info.masks + alpha_mask := false + bit_count: u32le + + #partial switch info.info_size { + case .ABBR_52 ..= .V5: + // All possible BMP header sizes 52+ bytes long, includes V4 + V5 + // Bit fields were read as part of the header + // V3 header is 40 bytes. We need 56 at a minimum for RGBA bit fields in the next section. + if info.info_size >= .ABBR_56 { + alpha_mask = true + } + + case .V3: + // Version 3 doesn't have a bit field embedded, but can still have a 3 or 4 color bit field. + // Because it wasn't read as part of the header, we need to read it now. + + if info.compression == .Alpha_Bit_Fields { + bf = compress.read_data(ctx, [4]u32le) or_return + alpha_mask = true + read = 16 + } else { + bf.xyz = compress.read_data(ctx, [3]u32le) or_return + read = 12 + } + + case: + // Bit fields are unhandled for this BMP version + return {}, 0, .Bitfield_Version_Unhandled + } + + if alpha_mask { + res = { + mask = {bf.r, bf.g, bf.b, bf.a}, + shift = {ctz(bf.r), ctz(bf.g), ctz(bf.b), ctz(bf.a)}, + bits = {c1s(bf.r), c1s(bf.g), c1s(bf.b), c1s(bf.a)}, + } + + bit_count = res.bits.r + res.bits.g + res.bits.b + res.bits.a + } else { + res = { + mask = {bf.r, bf.g, bf.b, 0}, + shift = {ctz(bf.r), ctz(bf.g), ctz(bf.b), 0}, + bits = {c1s(bf.r), c1s(bf.g), c1s(bf.b), 0}, + } + + bit_count = res.bits.r + res.bits.g + res.bits.b + } + + if bit_count > u32le(info.bpp) { + err = .Bitfield_Sum_Exceeds_BPP + } + + overlapped := res.mask.r | res.mask.g | res.mask.b | res.mask.a + if c1s(overlapped) < bit_count { + err = .Bitfield_Overlapped + } + return res, read, err + + case: + return {}, 0, .Unsupported_Compression + } + return +} + +scale :: proc(val: $T, mask, shift, bits: u32le) -> (res: u8) { + if bits == 0 { return 0 } // Guard against malformed bit fields + v := (u32le(val) & mask) >> shift + mask_in := u32le(1 << bits) - 1 + return u8(v * 255 / mask_in) +} + +decode_rgb :: proc(ctx: ^$C, img: ^Image, info: image.BMP_Header, allocator := context.allocator) -> (err: Error) { + pixel_offset := int(info.pixel_offset) + pixel_offset -= int(info.info_size) + FILE_HEADER_SIZE + + palette: [256]RGBA_Pixel + + // Palette size is info.colors_used if populated. If not it's min(1 << bpp, offset to the pixels / channel count) + colors_used := min(256, 1 << info.bpp if info.colors_used == 0 else info.colors_used) + max_colors := pixel_offset / 3 if info.info_size == .OS2_v1 else pixel_offset / 4 + colors_used = min(colors_used, u32le(max_colors)) + + switch info.bpp { + case 1: + if info.info_size == .OS2_v1 { + // 2 x RGB palette of instead of variable RGBA palette + for i in 0..> shift) & 0x01 + write(img, x, y, palette[p].bgr) or_return + } + } + + case 2: // Non-standard on modern Windows, but was allowed on WinCE + for i in 0..> u8(shift)) & 0x03 + write(img, x, y, palette[p].bgr) or_return + } + } + + case 4: + if info.info_size == .OS2_v1 { + // 16 x RGB palette of instead of variable RGBA palette + for i in 0..> 4 if x & 1 == 0 else data[x / 2] + write(img, x, y, palette[p & 0x0f].bgr) or_return + } + } + + case 8: + if info.info_size == .OS2_v1 { + // 256 x RGB palette of instead of variable RGBA palette + for i in 0.. (err: Error) { + pixel_offset := int(info.pixel_offset) + pixel_offset -= int(info.info_size) + FILE_HEADER_SIZE + + bytes_needed := size_of(RGB_Pixel) * img.height * img.width + if resize(&img.pixels.buf, bytes_needed) != nil { + return .Unable_To_Allocate_Or_Resize + } + out := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:]) + assert(len(out) == img.height * img.width) + + palette: [256]RGBA_Pixel + + switch info.bpp { + case 4: + colors_used := info.colors_used if info.colors_used > 0 else 16 + colors_used = min(colors_used, 16) + + for i in 0.. 0 { + for count in 0..> 0) & 0x0f].bgr) + } else { + write(img, x, y, palette[(data[index + 1] >> 4) & 0x0f].bgr) + } + x += 1 + } + index += 2 + } else { + switch data[index + 1] { + case 0: // EOL + x = 0; y += 1 + index += 2 + case 1: // EOB + return + case 2: // MOVE + x += int(data[index + 2]) + y += int(data[index + 3]) + index += 4 + case: // Literals + run_length := int(data[index + 1]) + aligned := (align4(run_length) >> 1) + 2 + + if index + aligned >= len(data) { + return .Corrupt + } + + for count in 0..> 4 + } + write(img, x, y, palette[val].bgr) + x += 1 + } + index += aligned + } + } + } + + case 8: + colors_used := info.colors_used if info.colors_used > 0 else 256 + colors_used = min(colors_used, 256) + + for i in 0.. 0 { + for _ in 0..= len(data) { + return .Corrupt + } + for count in 0.. (stride: int) { + stride = width + if width & 1 != 0 { + stride += 2 - (width & 1) + } + return +} + +align4 :: proc(width: int) -> (stride: int) { + stride = width + if width & 3 != 0 { + stride += 4 - (width & 3) + } + return +} + +skip_space :: proc(ctx: ^$C, bytes_to_skip: int) -> (err: Error) { + if bytes_to_skip < 0 { + return .Corrupt + } + for _ in 0.. (img: ^Image, err: Error) { + context.allocator = allocator + + data, ok := os.read_entire_file(filename) + defer delete(data) + + if ok { + return load_from_bytes(data, options) + } else { + return nil, .Unable_To_Read_File + } +} \ No newline at end of file diff --git a/core/image/common.odin b/core/image/common.odin index b576a9521..fe75371b3 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -12,6 +12,7 @@ package image import "core:bytes" import "core:mem" +import "core:io" import "core:compress" import "base:runtime" @@ -62,6 +63,7 @@ Image_Metadata :: union #shared_nil { ^PNG_Info, ^QOI_Info, ^TGA_Info, + ^BMP_Info, } @@ -159,11 +161,13 @@ Error :: union #shared_nil { Netpbm_Error, PNG_Error, QOI_Error, + BMP_Error, compress.Error, compress.General_Error, compress.Deflate_Error, compress.ZLIB_Error, + io.Error, runtime.Allocator_Error, } @@ -196,6 +200,128 @@ General_Image_Error :: enum { Unable_To_Allocate_Or_Resize, } +/* + BMP-specific +*/ +BMP_Error :: enum { + None = 0, + Invalid_File_Size, + Unsupported_BMP_Version, + Unsupported_OS2_File, + Unsupported_Compression, + Unsupported_BPP, + Invalid_Stride, + Invalid_Color_Count, + Implausible_File_Size, + Bitfield_Version_Unhandled, // We don't (yet) handle bit fields for this BMP version. + Bitfield_Sum_Exceeds_BPP, // Total mask bit count > bpp + Bitfield_Overlapped, // Channel masks overlap +} + +// img.metadata is wrapped in a struct in case we need to add to it later +// without putting it in BMP_Header +BMP_Info :: struct { + info: BMP_Header, +} + +BMP_Magic :: enum u16le { + Bitmap = 0x4d42, // 'BM' + OS2_Bitmap_Array = 0x4142, // 'BA' + OS2_Icon = 0x4349, // 'IC', + OS2_Color_Icon = 0x4943, // 'CI' + OS2_Pointer = 0x5450, // 'PT' + OS2_Color_Pointer = 0x5043, // 'CP' +} + +// See: http://justsolve.archiveteam.org/wiki/BMP#Well-known_versions +BMP_Version :: enum u32le { + OS2_v1 = 12, // BITMAPCOREHEADER (Windows V2 / OS/2 version 1.0) + OS2_v2 = 64, // BITMAPCOREHEADER2 (OS/2 version 2.x) + V3 = 40, // BITMAPINFOHEADER + V4 = 108, // BITMAPV4HEADER + V5 = 124, // BITMAPV5HEADER + + ABBR_16 = 16, // Abbreviated + ABBR_24 = 24, // .. + ABBR_48 = 48, // .. + ABBR_52 = 52, // .. + ABBR_56 = 56, // .. +} + +BMP_Header :: struct #packed { + // File header + magic: BMP_Magic, + size: u32le, + _res1: u16le, // Reserved; must be zero + _res2: u16le, // Reserved; must be zero + pixel_offset: u32le, // Offset in bytes, from the beginning of BMP_Header to the pixel data + // V3 + info_size: BMP_Version, + width: i32le, + height: i32le, + planes: u16le, + bpp: u16le, + compression: BMP_Compression, + image_size: u32le, + pels_per_meter: [2]u32le, + colors_used: u32le, + colors_important: u32le, // OS2_v2 is equal up to here + // V4 + masks: [4]u32le `fmt:"32b"`, + colorspace: BMP_Logical_Color_Space, + endpoints: BMP_CIEXYZTRIPLE, + gamma: [3]BMP_GAMMA16_16, + // V5 + intent: BMP_Gamut_Mapping_Intent, + profile_data: u32le, + profile_size: u32le, + reserved: u32le, +} +#assert(size_of(BMP_Header) == 138) + +OS2_Header :: struct #packed { + // BITMAPCOREHEADER minus info_size field + width: i16le, + height: i16le, + planes: u16le, + bpp: u16le, +} +#assert(size_of(OS2_Header) == 8) + +BMP_Compression :: enum u32le { + RGB = 0x0000, + RLE8 = 0x0001, + RLE4 = 0x0002, + Bit_Fields = 0x0003, // If Windows + Huffman1D = 0x0003, // If OS2v2 + JPEG = 0x0004, // If Windows + RLE24 = 0x0004, // If OS2v2 + PNG = 0x0005, + Alpha_Bit_Fields = 0x0006, + CMYK = 0x000B, + CMYK_RLE8 = 0x000C, + CMYK_RLE4 = 0x000D, +} + +BMP_Logical_Color_Space :: enum u32le { + CALIBRATED_RGB = 0x00000000, + sRGB = 0x73524742, // 'sRGB' + WINDOWS_COLOR_SPACE = 0x57696E20, // 'Win ' +} + +BMP_FXPT2DOT30 :: u32le +BMP_CIEXYZ :: [3]BMP_FXPT2DOT30 +BMP_CIEXYZTRIPLE :: [3]BMP_CIEXYZ +BMP_GAMMA16_16 :: [2]u16le + +BMP_Gamut_Mapping_Intent :: enum u32le { + INVALID = 0x00000000, // If not V5, this field will just be zero-initialized and not valid. + ABS_COLORIMETRIC = 0x00000008, + BUSINESS = 0x00000001, + GRAPHICS = 0x00000002, + IMAGES = 0x00000004, +} + /* Netpbm-specific definitions */ diff --git a/tests/core/image/test_core_image.odin b/tests/core/image/test_core_image.odin index 7fc04ce6b..0ce15715c 100644 --- a/tests/core/image/test_core_image.odin +++ b/tests/core/image/test_core_image.odin @@ -1,11 +1,11 @@ /* - Copyright 2021 Jeroen van Rijn . + Copyright 2021-2024 Jeroen van Rijn . Made available under Odin's BSD-3 license. List of contributors: Jeroen van Rijn: Initial implementation. - A test suite for PNG + QOI. + A test suite for PNG, TGA, NetPBM, QOI and BMP. */ package test_core_image @@ -13,6 +13,7 @@ import "core:testing" import "core:compress" import "core:image" +import "core:image/bmp" import pbm "core:image/netpbm" import "core:image/png" import "core:image/qoi" @@ -24,16 +25,17 @@ import "core:strings" import "core:mem" import "core:time" -TEST_SUITE_PATH :: ODIN_ROOT + "tests/core/assets/PNG/" +TEST_SUITE_PATH_PNG :: ODIN_ROOT + "tests/core/assets/PNG" +TEST_SUITE_PATH_BMP :: ODIN_ROOT + "tests/core/assets/BMP" I_Error :: image.Error -PNG_Test :: struct { +Test :: struct { file: string, tests: []struct { options: image.Options, expected_error: image.Error, - dims: PNG_Dims, + dims: Dims, hash: u32, }, } @@ -46,19 +48,18 @@ Blend_BG_Keep :: image.Options{.blend_background, .alpha_add_if_missing} Return_Metadata :: image.Options{.return_metadata} No_Channel_Expansion :: image.Options{.do_not_expand_channels, .return_metadata} -PNG_Dims :: struct { +Dims :: struct { width: int, height: int, channels: int, depth: int, } -Basic_PNG_Tests := []PNG_Test{ +Basic_PNG_Tests := []Test{ /* Basic format tests: http://www.schaik.com/pngsuite/pngsuite_bas_png.html */ - { "basn0g01", // Black and white. { @@ -166,7 +167,7 @@ Basic_PNG_Tests := []PNG_Test{ }, } -Interlaced_PNG_Tests := []PNG_Test{ +Interlaced_PNG_Tests := []Test{ /* Interlaced format tests: http://www.schaik.com/pngsuite/pngsuite_int_png.html @@ -284,9 +285,9 @@ Interlaced_PNG_Tests := []PNG_Test{ }, } -Odd_Sized_PNG_Tests := []PNG_Test{ +Odd_Sized_PNG_Tests := []Test{ /* -" PngSuite", // Odd sizes / PNG-files: + "PngSuite", // Odd sizes / PNG-files: http://www.schaik.com/pngsuite/pngsuite_siz_png.html This tests curious sizes with and without interlacing. @@ -510,7 +511,7 @@ Odd_Sized_PNG_Tests := []PNG_Test{ }, } -PNG_bKGD_Tests := []PNG_Test{ +PNG_bKGD_Tests := []Test{ /* " PngSuite", // Background colors / PNG-files: http://www.schaik.com/pngsuite/pngsuite_bck_png.html @@ -597,7 +598,7 @@ PNG_bKGD_Tests := []PNG_Test{ }, } -PNG_tRNS_Tests := []PNG_Test{ +PNG_tRNS_Tests := []Test{ /* PngSuite - Transparency: http://www.schaik.com/pngsuite/pngsuite_trn_png.html @@ -759,7 +760,7 @@ PNG_tRNS_Tests := []PNG_Test{ }, } -PNG_Filter_Tests := []PNG_Test{ +PNG_Filter_Tests := []Test{ /* PngSuite - Image filtering: @@ -838,7 +839,7 @@ PNG_Filter_Tests := []PNG_Test{ }, } -PNG_Varied_IDAT_Tests := []PNG_Test{ +PNG_Varied_IDAT_Tests := []Test{ /* PngSuite - Chunk ordering: @@ -897,7 +898,7 @@ PNG_Varied_IDAT_Tests := []PNG_Test{ }, } -PNG_ZLIB_Levels_Tests := []PNG_Test{ +PNG_ZLIB_Levels_Tests := []Test{ /* PngSuite - Zlib compression: @@ -938,7 +939,7 @@ PNG_ZLIB_Levels_Tests := []PNG_Test{ }, } -PNG_sPAL_Tests := []PNG_Test{ +PNG_sPAL_Tests := []Test{ /* PngSuite - Additional palettes: @@ -985,7 +986,7 @@ PNG_sPAL_Tests := []PNG_Test{ }, } -PNG_Ancillary_Tests := []PNG_Test{ +PNG_Ancillary_Tests := []Test{ /* PngSuite" - Ancillary chunks: @@ -1153,7 +1154,7 @@ PNG_Ancillary_Tests := []PNG_Test{ } -Corrupt_PNG_Tests := []PNG_Test{ +Corrupt_PNG_Tests := []Test{ /* PngSuite - Corrupted files / PNG-files: @@ -1249,7 +1250,7 @@ Corrupt_PNG_Tests := []PNG_Test{ } -No_Postprocesing_Tests := []PNG_Test{ +No_Postprocesing_Tests := []Test{ /* These are some custom tests where we skip expanding to RGB(A). */ @@ -1273,8 +1274,6 @@ No_Postprocesing_Tests := []PNG_Test{ }, } - - Text_Title :: "PngSuite" Text_Software :: "Created on a NeXTstation color using \"pnmtopng\"." Text_Descrption :: "A compilation of a set of images created to test the\nvarious color-types of the PNG format. Included are\nblack&white, color, paletted, with alpha channel, with\ntransparency formats. All bit-depths allowed according\nto the spec are present." @@ -1453,9 +1452,9 @@ png_test_no_postproc :: proc(t: ^testing.T) { run_png_suite(t, No_Postprocesing_Tests) } -run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { +run_png_suite :: proc(t: ^testing.T, suite: []Test) { for file in suite { - test_file := strings.concatenate({TEST_SUITE_PATH, file.file, ".png"}) + test_file := strings.concatenate({TEST_SUITE_PATH_PNG, "/", file.file, ".png"}, context.allocator) defer delete(test_file) img: ^png.Image @@ -1468,20 +1467,19 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { img, err = png.load(test_file, test.options) passed := (test.expected_error == nil && err == nil) || (test.expected_error == err) - testing.expectf(t, passed, "%v failed with %v.", test_file, err) + testing.expectf(t, passed, "%q failed to load with error %v.", file.file, err) if err == nil { // No point in running the other tests if it didn't load. pixels := bytes.buffer_to_bytes(&img.pixels) // This struct compare fails at -opt:2 if PNG_Dims is not #packed. - dims := PNG_Dims{img.width, img.height, img.channels, img.depth} + dims := Dims{img.width, img.height, img.channels, img.depth} dims_pass := test.dims == dims - - testing.expectf(t, dims_pass, "%v has %v, expected: %v.", file.file, dims, test.dims) + testing.expectf(t, dims_pass, "%v has %v, expected: %v", file.file, dims, test.dims) passed &= dims_pass png_hash := hash.crc32(pixels) - testing.expectf(t, test.hash == png_hash, "%v test %v hash is %08x, expected %08x with %v.", file.file, count, png_hash, test.hash, test.options) + testing.expectf(t, test.hash == png_hash, "%v test %v hash is %08x, expected %08x with %v", file.file, count, png_hash, test.hash, test.options) passed &= test.hash == png_hash @@ -1492,16 +1490,16 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { defer bytes.buffer_destroy(&qoi_buffer) qoi_save_err := qoi.save(&qoi_buffer, img) - testing.expectf(t, qoi_save_err == nil, "%v test %v QOI save failed with %v.", file.file, count, qoi_save_err) + testing.expectf(t, qoi_save_err == nil, "%v test %v QOI save failed with %v", file.file, count, qoi_save_err) if qoi_save_err == nil { qoi_img, qoi_load_err := qoi.load(qoi_buffer.buf[:]) defer qoi.destroy(qoi_img) - testing.expectf(t, qoi_load_err == nil, "%v test %v QOI load failed with %v.", file.file, count, qoi_load_err) + testing.expectf(t, qoi_load_err == nil, "%v test %v QOI load failed with %v", file.file, count, qoi_load_err) qoi_hash := hash.crc32(qoi_img.pixels.buf[:]) - testing.expectf(t, qoi_hash == png_hash, "%v test %v QOI load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, qoi_hash, png_hash, test.options) + testing.expectf(t, qoi_hash == png_hash, "%v test %v QOI load hash is %08x, expected it match PNG's %08x with %v", file.file, count, qoi_hash, png_hash, test.options) } } @@ -1511,15 +1509,15 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { defer bytes.buffer_destroy(&tga_buffer) tga_save_err := tga.save(&tga_buffer, img) - testing.expectf(t, tga_save_err == nil, "%v test %v TGA save failed with %v.", file.file, count, tga_save_err) + testing.expectf(t, tga_save_err == nil, "%v test %v TGA save failed with %v", file.file, count, tga_save_err) if tga_save_err == nil { tga_img, tga_load_err := tga.load(tga_buffer.buf[:]) defer tga.destroy(tga_img) - testing.expectf(t, tga_load_err == nil, "%v test %v TGA load failed with %v.", file.file, count, tga_load_err) + testing.expectf(t, tga_load_err == nil, "%v test %v TGA load failed with %v", file.file, count, tga_load_err) tga_hash := hash.crc32(tga_img.pixels.buf[:]) - testing.expectf(t, tga_hash == png_hash, "%v test %v TGA load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, tga_hash, png_hash, test.options) + testing.expectf(t, tga_hash == png_hash, "%v test %v TGA load hash is %08x, expected it match PNG's %08x with %v", file.file, count, tga_hash, png_hash, test.options) } } @@ -1528,18 +1526,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { pbm_buf, pbm_save_err := pbm.save_to_buffer(img) defer delete(pbm_buf) - testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v.", file.file, count, pbm_save_err) + testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v", file.file, count, pbm_save_err) if pbm_save_err == nil { // Try to load it again. pbm_img, pbm_load_err := pbm.load(pbm_buf) defer pbm.destroy(pbm_img) - testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v.", file.file, count, pbm_load_err) + testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v", file.file, count, pbm_load_err) if pbm_load_err == nil { pbm_hash := hash.crc32(pbm_img.pixels.buf[:]) - testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options) + testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v", file.file, count, pbm_hash, png_hash, test.options) } } } @@ -1553,18 +1551,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { pbm_buf, pbm_save_err := pbm.save_to_buffer(img, pbm_info) defer delete(pbm_buf) - testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v.", file.file, count, pbm_save_err) + testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v", file.file, count, pbm_save_err) if pbm_save_err == nil { // Try to load it again. pbm_img, pbm_load_err := pbm.load(pbm_buf) defer pbm.destroy(pbm_img) - testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v.", file.file, count, pbm_load_err) + testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v", file.file, count, pbm_load_err) if pbm_load_err == nil { pbm_hash := hash.crc32(pbm_img.pixels.buf[:]) - testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options) + testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v", file.file, count, pbm_hash, png_hash, test.options) } } } @@ -1653,7 +1651,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { case "pp0n2c16", "pp0n6a08": gamma, gamma_ok := png.gamma(c) expected_gamma := f32(1.0) - testing.expectf(t, gamma == expected_gamma && gamma_ok, "%v test %v gAMA is %v, expected %v.", file.file, count, gamma, expected_gamma) + testing.expectf(t, gamma == expected_gamma && gamma_ok, "%v test %v gAMA is %v, expected %v", file.file, count, gamma, expected_gamma) } case .PLTE: switch(file.file) { @@ -1661,7 +1659,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { plte, plte_ok := png.plte(c) expected_plte_len := u16(216) - testing.expectf(t, expected_plte_len == plte.used && plte_ok, "%v test %v PLTE length is %v, expected %v.", file.file, count, plte.used, expected_plte_len) + testing.expectf(t, expected_plte_len == plte.used && plte_ok, "%v test %v PLTE length is %v, expected %v", file.file, count, plte.used, expected_plte_len) } case .sPLT: switch(file.file) { @@ -1669,10 +1667,10 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { splt, splt_ok := png.splt(c) expected_splt_len := u16(216) - testing.expectf(t, expected_splt_len == splt.used && splt_ok, "%v test %v sPLT length is %v, expected %v.", file.file, count, splt.used, expected_splt_len) + testing.expectf(t, expected_splt_len == splt.used && splt_ok, "%v test %v sPLT length is %v, expected %v", file.file, count, splt.used, expected_splt_len) expected_splt_name := "six-cube" - testing.expectf(t, expected_splt_name == splt.name && splt_ok, "%v test %v sPLT name is %v, expected %v.", file.file, count, splt.name, expected_splt_name) + testing.expectf(t, expected_splt_name == splt.name && splt_ok, "%v test %v sPLT name is %v, expected %v", file.file, count, splt.name, expected_splt_name) png.splt_destroy(splt) } @@ -1686,31 +1684,31 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { g = png.CIE_1931{x = 0.3000, y = 0.6000}, b = png.CIE_1931{x = 0.1500, y = 0.0600}, } - testing.expectf(t, expected_chrm == chrm && chrm_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, chrm, expected_chrm) + testing.expectf(t, expected_chrm == chrm && chrm_ok, "%v test %v cHRM is %v, expected %v", file.file, count, chrm, expected_chrm) } case .pHYs: phys, phys_ok := png.phys(c) switch (file.file) { case "cdfn2c08": expected_phys := png.pHYs{ppu_x = 1, ppu_y = 4, unit = .Unknown} - testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys) case "cdhn2c08": expected_phys := png.pHYs{ppu_x = 4, ppu_y = 1, unit = .Unknown} - testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys) case "cdsn2c08": expected_phys := png.pHYs{ppu_x = 1, ppu_y = 1, unit = .Unknown} - testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys) case "cdun2c08": expected_phys := png.pHYs{ppu_x = 1000, ppu_y = 1000, unit = .Meter} - testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys) + testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys) } case .hIST: hist, hist_ok := png.hist(c) switch (file.file) { case "ch1n3p04": - testing.expectf(t, hist.used == 15 && hist_ok, "%v test %v hIST has %v entries, expected %v.", file.file, count, hist.used, 15) + testing.expectf(t, hist.used == 15 && hist_ok, "%v test %v hIST has %v entries, expected %v", file.file, count, hist.used, 15) case "ch2n3p08": - testing.expectf(t, hist.used == 256 && hist_ok, "%v test %v hIST has %v entries, expected %v.", file.file, count, hist.used, 256) + testing.expectf(t, hist.used == 256 && hist_ok, "%v test %v hIST has %v entries, expected %v", file.file, count, hist.used, 256) } case .tIME: png_time, png_time_ok := png.time(c) @@ -1731,8 +1729,8 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { expected_core = time.Time{_nsec = 946684799000000000} } - testing.expectf(t, png_time == expected_time && png_time_ok, "%v test %v tIME was %v, expected %v.", file.file, count, png_time, expected_time) - testing.expectf(t, core_time == expected_core && core_time_ok, "%v test %v tIME->core:time is %v, expected %v.", file.file, count, core_time, expected_core) + testing.expectf(t, png_time == expected_time && png_time_ok, "%v test %v tIME was %v, expected %v", file.file, count, png_time, expected_time) + testing.expectf(t, core_time == expected_core && core_time_ok, "%v test %v tIME->core:time is %v, expected %v", file.file, count, core_time, expected_core) case .sBIT: sbit, sbit_ok := png.sbit(c) expected_sbit: [4]u8 @@ -1753,7 +1751,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { case "cdfn2c08", "cdhn2c08", "cdsn2c08", "cdun2c08", "ch1n3p04", "basn3p04": expected_sbit = [4]u8{ 4, 4, 4, 0} } - testing.expectf(t, sbit == expected_sbit && sbit_ok, "%v test %v sBIT was %v, expected %v.", file.file, count, sbit, expected_sbit) + testing.expectf(t, sbit == expected_sbit && sbit_ok, "%v test %v sBIT was %v, expected %v", file.file, count, sbit, expected_sbit) case .tEXt, .zTXt: text, text_ok := png.text(c) defer png.text_destroy(text) @@ -1765,7 +1763,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test_text := Expected_Text[file.file][text.keyword].text - testing.expectf(t, text.text == test_text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text.text, test_text) + testing.expectf(t, text.text == test_text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text.text, test_text) } } } @@ -1778,44 +1776,44 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) } } case "ctfn0g04": // international UTF-8, finnish if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) } } case "ctgn0g04": // international UTF-8, greek if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) } } case "cthn0g04": // international UTF-8, hindi if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) } } case "ctjn0g04": // international UTF-8, japanese if file.file in Expected_Text { if text.keyword in Expected_Text[file.file] { test := Expected_Text[file.file][text.keyword] - testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) - testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test) + testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) + testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test) } } } @@ -1823,7 +1821,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { if file.file == "exif2c08" { // chunk with jpeg exif data exif, exif_ok := png.exif(c) testing.expectf(t, exif.byte_order == .big_endian && exif_ok, "%v test %v eXIf byte order '%v', expected 'big_endian'.", file.file, count, exif.byte_order) - testing.expectf(t, len(exif.data) == 978 && exif_ok, "%v test %v eXIf data length '%v', expected '%v'.", file.file, len(exif.data), 978) + testing.expectf(t, len(exif.data) == 978 && exif_ok, "%v test %v eXIf data length '%v', expected '%v'", file.file, len(exif.data), 978) } } } @@ -1833,4 +1831,516 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) { png.destroy(img) } } + return +} + +/* + Basic format tests: + https://entropymine.com/jason/bmpsuite/bmpsuite/html/bmpsuite.html - Version 2.8; 2023-11-28 + + The BMP Suite image generator itself is GPL, and isn't included, nor did it have its code referenced. + We do thank the author for the well-researched test suite, which we are free to include: + + "Image files generated by this program are not covered by this license, and are + in the public domain (except for the embedded ICC profiles)." + + The files with embedded ICC profiles aren't part of Odin's test assets. We don't support BMP metadata. + We don't support all "possibly correct" images, and thus only ship a subset of these from the BMP Suite. +*/ +Basic_BMP_Tests := []Test{ + { + "pal1", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "pal1wb", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "pal1bg", { + {Default, nil, {127, 64, 3, 8}, 0x_9e91_174a}, + }, + }, + { + "pal4", { + {Default, nil, {127, 64, 3, 8}, 0x_288e_4371}, + }, + }, + { + "pal4gs", { + {Default, nil, {127, 64, 3, 8}, 0x_452d_a01a}, + }, + }, + { + "pal4rle", { + {Default, nil, {127, 64, 3, 8}, 0x_288e_4371}, + }, + }, + { + "pal8", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8-0", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8gs", { + {Default, nil, {127, 64, 3, 8}, 0x_09c2_7834}, + }, + }, + { + "pal8rle", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8w126", { + {Default, nil, {126, 63, 3, 8}, 0x_bb66_4cda}, + }, + }, + { + "pal8w125", { + {Default, nil, {125, 62, 3, 8}, 0x_3ab8_f7c5}, + }, + }, + { + "pal8w124", { + {Default, nil, {124, 61, 3, 8}, 0x_b53e_e6c8}, + }, + }, + { + "pal8topdown", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8nonsquare", { + {Default, nil, {127, 32, 3, 8}, 0x_8409_c689}, + }, + }, + { + "pal8v4", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8v5", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "rgb16", { + {Default, nil, {127, 64, 3, 8}, 0x_8b6f_81a2}, + }, + }, + { + "rgb16bfdef", { + {Default, nil, {127, 64, 3, 8}, 0x_8b6f_81a2}, + }, + }, + { + "rgb16-565", { + {Default, nil, {127, 64, 3, 8}, 0x_8c73_a2ff}, + }, + }, + { + "rgb16-565pal", { + {Default, nil, {127, 64, 3, 8}, 0x_8c73_a2ff}, + }, + }, + { + "rgb24", { + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, + { + "rgb24pal", { + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, + { + "rgb32", { + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, + { + "rgb32bf", { + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, + { + "rgb32bfdef", { + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, +} + +OS2_Tests := []Test{ + { + "pal8os2", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8os2-sz", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8os2-hs", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8os2sp", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8os2v2", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8os2v2-16", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8os2v2-sz", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8os2v2-40sz", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2. + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, +} + +// BMP files that aren't 100% to spec. Some we support, some we don't. +Questionable_BMP_Tests := []Test{ + { + "pal1p1", { // Spec says 1-bit image has 2 palette entries. This one has 1. + {Default, nil, {127, 64, 3, 8}, 0x_2b54_2560}, + }, + }, + { + "pal2", { // 2-bit. Allowed on Windows CE. Irfanview doesn't support it. + {Default, nil, {127, 64, 3, 8}, 0x_0da2_7594}, + }, + }, + { + "pal2color", { // 2-bit, with color palette. + {Default, nil, {127, 64, 3, 8}, 0x_f0d8_c5d6}, + }, + }, + { + "pal8offs", { // 300 palette entries (yes, only 256 can be used) + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal8oversizepal", { // Some padding between palette and image data + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "pal4rletrns", { // Using palette tricks to skip pixels + {Default, nil, {127, 64, 3, 8}, 0x_eed4_e744}, + }, + }, + { + "pal4rlecut", { // Using palette tricks to skip pixels + {Default, nil, {127, 64, 3, 8}, 0x_473fbc7d}, + }, + }, + { + "pal8rletrns", { // Using palette tricks to skip pixels + {Default, nil, {127, 64, 3, 8}, 0x_fe1f_e560}, + }, + }, + { + "pal8rlecut", { // Using palette tricks to skip pixels + {Default, nil, {127, 64, 3, 8}, 0x_bd04_3619}, + }, + }, + { + "rgb16faketrns", { // Using palette tricks to skip pixels + {Default, nil, {127, 64, 3, 8}, 0x_8b6f_81a2}, + }, + }, + { + "rgb16-231", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_7393_a163}, + }, + }, + { + "rgb16-3103", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_3b66_2189}, + }, + }, + { + "rgba16-4444", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_b785_1f9f}, + }, + }, + { + "rgba16-5551", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_8b6f_81a2}, + }, + }, + { + "rgba16-1924", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_f038_2bed}, + }, + }, + { + "rgb32-xbgr", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, + { + "rgb32fakealpha", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, + { + "rgb32-111110", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_b2c7_a8ff}, + }, + }, + { + "rgb32-7187", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_b93a_4291}, + }, + }, + { + "rgba32-1", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_7b67_823d}, + }, + }, + { + "rgba32-2", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_7b67_823d}, + }, + }, + { + "rgba32-1010102", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_aa42_0b16}, + }, + }, + { + "rgba32-81284", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_28a2_4c16}, + }, + }, + { + "rgba32-61754", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_4aae_26ed}, + }, + }, + { + "rgba32abf", { // Custom bit fields + {Default, nil, {127, 64, 3, 8}, 0x_7b67_823d}, + }, + }, + { + "rgb32h52", { // Truncated header (RGB bit fields included) + {Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a}, + }, + }, + { + "rgba32h56", { // Truncated header (RGBA bit fields included) + {Default, nil, {127, 64, 3, 8}, 0x_7b67_823d}, + }, + }, +} + +// Unsupported BMP features, or malformed images. +Unsupported_BMP_Tests := []Test{ + { + "ba-bm", { // An OS/2 Bitmap array. We don't support this BA format. + {Default, .Unsupported_OS2_File, {127, 32, 3, 8}, 0x_0000_0000}, + }, + }, + { + "pal1huffmsb", { // An OS/2 file with Huffman 1D compression + {Default, .Unsupported_Compression, {127, 32, 3, 8}, 0x_0000_0000}, + }, + }, + { + "rgb24rle24", { // An OS/2 file with RLE24 compression + {Default, .Unsupported_Compression, {127, 64, 3, 8}, 0x_0000_0000}, + }, + }, + { + "rgba64", { // An OS/2 file with RLE24 compression + {Default, .Unsupported_BPP, {127, 64, 3, 8}, 0x_0000_0000}, + }, + }, +} + +// Malformed / malicious files +Known_Bad_BMP_Tests := []Test{ + { + "badbitcount", { + {Default, .Unsupported_BPP, {127, 64, 3, 8}, 0x_3ce81fae}, + }, + }, + { + "badbitssize", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "baddens1", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "baddens2", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "badfilesize", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "badheadersize", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "badpalettesize", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "badplanes", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "badrle", { + {Default, nil, {127, 64, 3, 8}, 0x_1457_aae4}, + }, + }, + { + "badrle4", { + {Default, nil, {127, 64, 3, 8}, 0x_6764_d2ac}, + }, + }, + { + "badrle4bis", { + {Default, nil, {127, 64, 3, 8}, 0x_935d_bb37}, + }, + }, + { + "badrle4ter", { + {Default, nil, {127, 64, 3, 8}, 0x_f2ba_5b08}, + }, + }, + { + "badrlebis", { + {Default, nil, {127, 64, 3, 8}, 0x_07e2_d730}, + }, + }, + { + "badrleter", { + {Default, nil, {127, 64, 3, 8}, 0x_a874_2742}, + }, + }, + { + "badwidth", { + {Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae}, + }, + }, + { + "pal8badindex", { + {Default, nil, {127, 64, 3, 8}, 0x_0450_0d02}, + }, + }, + { + "reallybig", { + {Default, .Image_Dimensions_Too_Large, {3000000, 2000000, 1, 24}, 0x_0000_0000}, + }, + }, + { + "rgb16-880", { + {Default, nil, {127, 64, 3, 8}, 0x_f1c2_0c73}, + }, + }, + { + "rletopdown", { + {Default, nil, {127, 64, 3, 8}, 0x_3845_4155}, + }, + }, + { + "shortfile", { + {Default, .Short_Buffer, {127, 64, 1, 1}, 0x_0000_0000}, + }, + }, +} + +@test +bmp_test_basic :: proc(t: ^testing.T) { + run_bmp_suite(t, Basic_BMP_Tests) +} + +@test +bmp_test_os2 :: proc(t: ^testing.T) { + run_bmp_suite(t, OS2_Tests) +} + +@test +bmp_test_questionable :: proc(t: ^testing.T) { + run_bmp_suite(t, Questionable_BMP_Tests) +} + +@test +bmp_test_unsupported :: proc(t: ^testing.T) { + run_bmp_suite(t, Unsupported_BMP_Tests) +} + +@test +bmp_test_known_bad :: proc(t: ^testing.T) { + run_bmp_suite(t, Known_Bad_BMP_Tests) +} + +run_bmp_suite :: proc(t: ^testing.T, suite: []Test) { + for file in suite { + test_file := strings.concatenate({TEST_SUITE_PATH_BMP, "/", file.file, ".bmp"}, context.allocator) + defer delete(test_file) + + for test in file.tests { + img, err := bmp.load(test_file, test.options) + + passed := (test.expected_error == nil && err == nil) || (test.expected_error == err) + testing.expectf(t, passed, "%q failed to load with error %v.", file.file, err) + + if err == nil { // No point in running the other tests if it didn't load. + qoi_file := strings.concatenate({TEST_SUITE_PATH_BMP, "/", file.file, ".qoi"}, context.allocator) + defer delete(qoi_file) + + qoi.save(qoi_file, img) + pixels := bytes.buffer_to_bytes(&img.pixels) + + dims := Dims{img.width, img.height, img.channels, img.depth} + testing.expectf(t, test.dims == dims, "%v has %v, expected: %v.", file.file, dims, test.dims) + + img_hash := hash.crc32(pixels) + testing.expectf(t, test.hash == img_hash, "%v test #1's hash is %08x, expected %08x with %v.", file.file, img_hash, test.hash, test.options) + } + bmp.destroy(img) + } + } + return } \ No newline at end of file From 4a290f47ad771d2f992e14162b5fce27336424fe Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 6 Jun 2024 16:55:03 +0200 Subject: [PATCH 188/270] Re-add BMP test suite download. --- tests/core/download_assets.py | 93 ++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/tests/core/download_assets.py b/tests/core/download_assets.py index 7874b7e91..8e124954f 100644 --- a/tests/core/download_assets.py +++ b/tests/core/download_assets.py @@ -7,7 +7,7 @@ import zipfile import hashlib import hmac -TEST_SUITES = ['PNG', 'XML'] +TEST_SUITES = ['PNG', 'XML', 'BMP'] DOWNLOAD_BASE_PATH = "assets/{}" ASSETS_BASE_URL = "https://raw.githubusercontent.com/odin-lang/test-assets/master/{}/{}" HMAC_KEY = "https://odin-lang.org" @@ -192,6 +192,94 @@ HMAC_DIGESTS = { 'z06n2c08.png': "94268c1998de1f4304d24219e31175def7375cc26e2bbfc7d1ac20465a42fae49bcc8ff7626873138b537588e8bce21b6d5e1373efaade1f83cae455334074aa", 'z09n2c08.png': "3cbb1bb58d78ecc9dd5568a8e9093ba020b63449ef3ab102f98fac4220fc9619feaa873336a25f3c1ad99cfb3e5d32bcfe52d966bc8640d1d5ba4e061741743e", + 'ba-bm.bmp': "2f76d46b1b9bea62e08e7fc5306452a495616cb7af7a0cbb79237ed457b083418d5859c9e6cfd0d9fbf1fe24495319b6f206135f36f2bd19330de01a8eaf20c8", + 'badbitcount.bmp': "2d37e22aa2e659416c950815841e5a402f2e9c21eb677390fc026eefaeb5be64345a7ef0fac2965a2cae8abe78c1e12086a7d93d8e62cc8659b35168c82f6d5f", + 'badbitssize.bmp': "f59cc30827bcb56f7e946dcffcaab22a5e197f2e3884cf80a2e596f5653f5203b3927674d9d5190486239964e65228f4e3f359cdd2f7d061b09846f5f26bfaa9", + 'baddens1.bmp': "aa84bebc41b3d50329269da9ee61fd7e1518ffd0e8f733af6872323bc46ace6ed1c9931a65a367d97b8b2cb2aa772ccd94fd3def0a79fd1c0baf185d669c386f", + 'baddens2.bmp': "5c254a8cde716fae77ebf20294a404383fd6afc705d783c5418762e7c4138aa621625bc6d08a8946ee3f1e8c40c767681a39806735bb3b3026fee5eb91d8fadc", + 'badfilesize.bmp': "9019b6853a91f69bd246f9b28da47007aec871c0e46fea7cd6ab5c30460a6938a1b09da8fa7ba8895650e37ce14a79d4183e9f2401eb510f60455410e2266eb5", + 'badheadersize.bmp': "90412d7c3bff7336d5e0c7ae899d8a53b82235072034f00783fb2403479447cd2959644f7ec70ae0988f99cc49b63356c8710b808ddd2280e19dca484f34074e", + 'badpalettesize.bmp': "d914a89f7b78fcdd6ab4433c176355755687b65c3cfc23db57de9d04447c440fa31d993db184940c1dc09b37e8e044324d8237877d3d1b1ad5657c4929d8435a", + 'badplanes.bmp': "46f583d4a43ef0c9964765b9d8820369955f0568a4eae0bf215434f508e8e03457bd759b73c344c2f88de7f33fc5379517ce3cf5b2e5a16ebc20c05df73aa723", + 'badrle.bmp': "a64e1551fd60159ff469ce25e1f5b4575dc462684f4ff66c7ea69b2990c7c9d2547b72237020e2d001f69dfd31f1ac45e0a9630d0ddd11c77584881f3e25609e", + 'badrle4.bmp': "2bd22418010b1ac3eac50932ed06e578411ac2741bfa50a9edd1b360686efa28c74df8b14d92e05b711eeb88a5e826256c6a5cf5a0176a29369fb92b336efb93", + 'badrle4bis.bmp': "d7a24ab095e1ca5e888dd1bcb732b19bb1983f787c64c1eb5a273da0f58c4b8cd137197df9ac47572a74c3026aab5af1f08551a2121af37b8941cffa71df1951", + 'badrle4ter.bmp': "825cc5361378d44524205b117825f95228c4d093d39ac2fc2ab755be743df78784529f2019418deca31059f3e46889a66658e7424b4f896668ee4cfa281574bc", + 'badrlebis.bmp': "f41acfd4f989302bb5ec42a2e759a56f71a5ecac5a814842e32542742ca015464f8579ebeec0e7e9cea45e2aafe51456cfe18b48b509bc3704f992bcc9d321af", + 'badrleter.bmp': "a8f3e0b0668fc4f43353028d5fca87d6cac6ff0c917c4e7a61c624918360ff598ec9eaa32f5c6a070da9bf6e90c58426f5a901fdab9dfb0a4fdca0c72ba67de4", + 'badwidth.bmp': "68f192a55b8de66f8e13fe316647262a5e4641365eb77d4987c84ab1eae35b7cba20827277cd569583543819de70ec75f383367f72cd229e48743ad1e45bfa9e", + 'pal1.bmp': "0194c9b501ac7e043fab78746e6f142e0c880917d0fd6dbb7215765b8fc1ce4403ad85146c555665ba6e37d3b47edad5e687b9260e7a61a27d8a059bc81bb525", + 'pal1bg.bmp': "3aafc29122bd6e97d88d740be1f61cb9febe8373d19ae6d731f4af776c868dd489260287bf0cf1c960f9d9afcbc7448e83e45435d3e42e913823c0f5c2a80d9f", + 'pal1huffmsb.bmp': "4e122f602c3556f4f5ab45f9e13a617d8210d81f587d08cbd6c2110dc6231573aec92a6344aeb4734c00d3dcf380130f53a887002756811d8edd6bc5aabbafc0", + 'pal1p1.bmp': "33e2b2b1c1bed43ba64888d7739eb830c7789857352513de08b6e35718ac0e421afcdae0e7bab97c25d1ad972eb4f09e2c6556c416d4d7367c545330c4123df0", + 'pal1wb.bmp': "bc583ad4eaae40f5d2e3a6280aeb3c62ee11b2cf05ba7c8386f9578587e29b66819293992bdcd31c2750c21cd9bf97daa603ce1051fbfdd40fadbc1860156853", + 'pal2.bmp': "7b560ba972cf58ca1ed01910fa4f630ca74e657d46d134e2ac0df733eb5773d0a1788e745d5240efa18f182bd8dce22c7ac7cee6f99ddc946a27b65297762764", + 'pal2color.bmp': "b868a8aaa22fac3aa86bbd5270eb5ffee06959461be8177880829d838be0391d9617d11d73fab1643520a92364dc333c25c0510bb2628c8fb945719518d2675f", + 'pal4.bmp': "53a39fdb86630c828d9003a1e95dbd59c47524c4ec044d8ce72e1b643166b4c2b6ec06ab5191cb25d17be2fcb18bd7a9e0b7ec169722e6d89b725609a15b1df1", + 'pal4gs.bmp': "ab4c2078943afdf19bcc02b1ebbe5a69cfa93d1152f7882db6176c39b917191a2760fbb2127e5207b0bfb3dafd711593a6aed61d312807605913062aa1ce9c2f", + 'pal4rle.bmp': "c86c86280b75a252ccf484e4bba2df45d3747dc1e4879795e925613959a0c451e2fc4890532e8aef9911e38e45e7d6a8baf29d57e573d26c20923a5823700443", + 'pal4rlecut.bmp': "f38d485dbb8e67bdeaefba181f9a05556a986ed3f834edca723c088e813764bb2b42240d4fbb938a1775370b79b9ea2f14277ffe9c7247c1e0e77766fec27189", + 'pal4rletrns.bmp': "b81e7fed38854d201a3199ce50ca05e92ca287c860797142857ac20b4a2f28952b058e21687c0fae60712f5784cd2c950ce70148ba1316efe31d4f3fc4006817", + 'pal8-0.bmp': "f39a4f1827c52bb620d975f8c72f5e95f90ac6c65ae0a6776ff1ad95808c090de17cbd182188a85157396fd9649ea4b5d84bb7c9175ab49ce2845da214c16bff", + 'pal8.bmp': "be27e55a866cbb655fdd917435cd6a5b62c20ae0d6ef7c1533c5a01dd9a893f058cc4ba2d902ab9315380009808e06b7f180116c9b790587cf62aa770c7a4a07", + 'pal8badindex.bmp': "bd5fc036985ae705182915a560dee2e5dfb3bd8b50932337b9085e190259c66e6bae5fbc813a261d352a60dcb0755798bdc251d6c2a0b638a7e337ba58811811", + 'pal8gs.bmp': "228f944b3e45359f62a2839d4e7b94d7f3a1074ad9e25661fdb9e8fff4c15581c85a7bb0ac75c92b95c7537ececc9d80b835cfe55bc7560a513118224a9ed36f", + 'pal8nonsquare.bmp': "b8adc9b03880975b232849ea1e8f87705937929d743df3d35420902b32557065354ab71d0d8176646bf0ad72c583c884cfcd1511017260ffca8c41d5a358a3eb", + 'pal8offs.bmp': "c92f1e1835d753fd8484be5198b2b8a2660d5e54117f6c4fc6d2ebc8d1def72a8d09cd820b1b8dcee15740b47151d62b8b7aca0b843e696252e28226b51361cf", + 'pal8os2-hs.bmp': "477c04048787eb412f192e7fe47ae96f14d7995391e78b10cc4c365f8c762f60c54cad7ef9d1705a78bd490a578fb346ee0a383c3a3fdf790558a12589eb04eb", + 'pal8os2-sz.bmp': "fd0eeb733be9b39f492d0f67dd28fc67207149e41691c206d4de4c693b5dea9458b88699a781383e7050a3b343259659aae64fec0616c98f3f8555cbf5c9e46c", + 'pal8os2.bmp': "cdab3ed7bc9f38d89117332a21418b3c916a99a8d8fb6b7ce456d54288c96152af12c0380293b04e96594a7867b83be5c99913d224c9750c7d38295924e0735a", + 'pal8os2sp.bmp': "f6e595a6db992ab7d1f79442d31f39f648061e7de13e51b07933283df065ce405c0208e6101ac916e4eb0613e412116f008510021a2d17543aa7f0a32349c96f", + 'pal8os2v2-16.bmp': "f52877d434218aa6b772a7aa0aaba4c2ae6ce35ecfa6876943bb350fdf9554f1f763a8d5bb054362fb8f9848eb71ce14a371f4a76da4b9475cdcee4f286109a4", + 'pal8os2v2-40sz.bmp': "9481691ada527df1f529316d44b5857c6a840c5dafa7e9795c9cb92dac02c6cc35739d3f6ce33d4ab6ff6bcd6b949741e89dc8c42cf52ad4546ff58cd3b5b66a", + 'pal8os2v2-sz.bmp': "99cd2836f90591cd27b0c8696ecff1e7a1debcef284bbe5d21e68759270c1bfe1f32ee8f576c49f3e64d8f4e4d9096574f3c8c79bfdae0545689da18364de3e7", + 'pal8os2v2.bmp': "7859b265956c7d369db7a0a357ce09bcda74e98954de88f454cae5e7cb021222146687a7770ce0cc2c58f1439c7c21c45c0c27464944e73913e1c88afc836c8a", + 'pal8oversizepal.bmp': "e778864e0669a33fce27c0ccd5b6460b572a5db01975e8d56acec8a9447e1c58d6051ad3516cfa96a39f4eb7f2576154188ea62ec187bcf4ae323883499383c0", + 'pal8rle.bmp': "88942a1cd2e36d1e0f0e2748a888034057919c7ec0f8d9b2664deb1daf1a6e45ed3e722dff5d810f413d6fc182e700a16d6563dd25f67dc6d135d751cd736dea", + 'pal8rlecut.bmp': "cda9fa274cde590aeaca81516e0465684cfae84e934eb983301801e978e6e2e9c590d22af992d9912e51bb9c2761945276bdbe0b6c47f3a021514414e1f3f455", + 'pal8rletrns.bmp': "0b2d5618dc9c81caa72c070070a4245dd9cd3de5d344b76ce9c15d0eeb72e2675efc264201f8709dfcffd234df09e76d6f328f16f2ad873ba846f870cadfa486", + 'pal8topdown.bmp': "d470a2b7556fa88eac820427cb900f59a121732cdb4a7f3530ed457798139c946a884a34ab79d822feb84c2ca6f4d9a65f6e792994eafc3a189948b9e4543546", + 'pal8v4.bmp': "0382610e32c49d5091a096cb48a54ebbf44d9ba1def96e2f30826fd3ddf249f5aed70ca5b74c484b6cdc3924f4d4bfed2f5194ad0bcf1d99bfaa3a619e299d86", + 'pal8v5.bmp': "50fadaa93aac2a377b565c4dc852fd4602538863b913cb43155f5ad7cf79928127ca28b33e5a3b0230076ea4a6e339e3bf57f019333f42c4e9f003a8f2376325", + 'pal8w124.bmp': "e54a901b9badda655cad828d226b381831aea7e36aec8729147e9e95a9f2b21a9d74d93756e908e812902a01197f1379fe7e35498dbafed02e27c853a24097b7", + 'pal8w125.bmp': "d7a04d45ef5b3830da071ca598f1e2a413c46834968b2db7518985cf8d8c7380842145899e133e71355b6b7d040ee9e97adec1e928ce4739282e0533058467c0", + 'pal8w126.bmp': "4b93845a98797679423c669c541a248b4cdfee80736f01cec29d8b40584bf55a27835c80656a2bf5c7ad3ed211c1f7d3c7d5831a6726904b39f10043a76b658d", + 'reallybig.bmp': "babbf0335bac63fd2e95a89210c61ae6bbaaeeab5f07974034e76b4dc2a5c755f77501e3d056479357445aac442e2807d7170ec44067bab8fd35742f0e7b8440", + 'rgb16-231.bmp': "611a87cb5d29f16ef71971714a3b0e3863a6df51fff16ce4d4df8ee028442f9ce03669fb5d7a6a838a12a75d8a887b56b5a2e44a3ad62f4ef3fc2e238c33f6a1", + 'rgb16-3103.bmp': "7fdff66f4d94341da522b4e40586b3b8c327be9778e461bca1600e938bfbaa872b484192b35cd84d9430ca20aa922ec0301567a74fb777c808400819db90b09d", + 'rgb16-565.bmp': "777883f64b9ae80d77bf16c6d062082b7a4702f8260c183680afee6ec26e48681bcca75f0f81c470be1ac8fcb55620b3af5ce31d9e114b582dfd82300a3d6654", + 'rgb16-565pal.bmp': "57e9dcf159415b3574a1b343a484391b0859ab2f480e22157f2a84bc188fde141a48826f960c6f30b1d1f17ef6503ec3afc883a2f25ff09dd50c437244f9ae7f", + 'rgb16-880.bmp': "8d61183623002da4f7a0a66b42aa58a120e3a91578bb0c4a5e2c5ba7d08b875d43a22f2b5b3a449d3caf4cc303cb05111dd1d5169953f288493b7ea3c2423d24", + 'rgb16.bmp': "1c0fe56661d4998edd76efedda520a441171d42ae4dad95b350e3b61deda984c3a3255392481fe1903e5e751357da3f35164935e323377f015774280036ba39e", + 'rgb16bfdef.bmp': "ed55d086e27ba472076df418be0046b740944958afeb84d05aa2bbe578dec27ced122ffefb6d549e1d07e05eb608979b3ac9b1bd809f8237cf0984ffdaa24716", + 'rgb16faketrns.bmp': "9cd4a8e05fe125649e360715851ef912e78a56d30e0ea1b1cfb6eaafd386437d45de9c1e1a845dd8d63ff5a414832355b8ae0e2a96d72a42a7205e8a2742d37c", + 'rgb24.bmp': "4f0ce2978bbfea948798b2fdcc4bdbe8983a6c94d1b7326f39daa6059368e08ebf239260984b64eeb0948f7c8089a523e74b7fa6b0437f9205d8af8891340389", + 'rgb24largepal.bmp': "b377aee1594c5d9fc806a70bc62ee83cf8d1852b4a2b18fd3e9409a31aa3b5a4cf5e3b4af2cbdebcef2b5861b7985a248239684a72072437c50151adc524e9df", + 'rgb24pal.bmp': "f40bb6e01f6ecb3d55aa992bf1d1e2988ea5eb11e3e58a0c59a4fea2448de26f231f45e9f378b7ee1bdd529ec57a1de38ea536e397f5e1ac6998921e066ab427", + 'rgb24png.bmp': "c60890bbd79c12472205665959eb6e2dd2103671571f80117b9e963f897cffca103181374a4797f53c7768af01a705e830a0de4dd8fab7241d24c17bee4a4dbe", + 'rgb24rle24.bmp': "ea0ff3f512dd04176d14b43dfbee73ac7f1913aa1b77587e187e271781c7dacec880cec73850c4127ea9f8dd885f069e281f159bb5232e93cbb2d1ee9cb50438", + 'rgb32-111110.bmp': "732884e300d4edbcf31556a038947beefc0c5e749131a66d2d7aa1d4ec8c8eba07833133038a03bbe4c4fa61a805a5df1f797b5853339ee6a8140478f5f70a76", + 'rgb32-7187.bmp': "4c55aab2e4ecf63dc30b04e5685c5d9fba7354ca0e1327d7c4b15d6da10ac66ca1cea6f0303f9c5f046da3bcd2566275384e0e7bb14fcc5196ec39ca90fac194", + 'rgb32-xbgr.bmp': "1e9f240eaec6ac2833f8c719f1fb53cc7551809936620e871ccacfab26402e1afc6503b9f707e4ec25f15529e3ce6433c7f999d5714af31dfb856eb67e772f64", + 'rgb32.bmp': "32033dbf9462e5321b1182ba739624ed535aa4d33b775ffeeaf09d2d4cb663e4c3505e8c05489d940f754dde4b50a2e0b0688b21d06755e717e6e511b0947525", + 'rgb32bf.bmp': "7243c215827a9b4a1d7d52d67fb04ffb43b0b176518fbdab43d413e2a0c18883b106797f1acd85ba68d494ec939b0caab8789564670d058caf0e1175ce7983fb", + 'rgb32bfdef.bmp': "a81433babb67ce714285346a77bfccd19cf6203ac1d8245288855aff20cf38146a783f4a7eac221db63d1ee31345da1329e945b432f0e7bcf279ea88ff5bb302", + 'rgb32fakealpha.bmp': "abecaf1b5bfad322de7aec897efe7aa6525f2a77a0af86cc0a0a366ed1650da703cf4b7b117a7ba34f21d03a8a0045e5821248cdefa00b0c78e01d434b55e746", + 'rgb32h52.bmp': "707d734393c83384bc75720330075ec9ffefc69167343259ebf95f9393948364a40f33712619f962e7056483b73334584570962c16da212cd5291f764b3f2cd1", + 'rgba16-1924.bmp': "3e41a5d8d951bac580c780370ca21e0783de8154f4081106bc58d1185bb2815fc5b7f08f2a1c75cd205fc52c888e9d07c91856651603a2d756c9cfc392585598", + 'rgba16-4444.bmp': "a6662186b23bd434a7e019d2a71cd95f53a47b64a1afea4c27ae1120685d041a9ff98800a43a9d8f332682670585bdb2fa77ff77b6def65139fe725323f91561", + 'rgba16-5551.bmp': "a7d9f8ae7f8349cd6df651ab9d814411939fa2a235506ddfdd0df5a8f8abcf75552c32481ea035ff29e683bdcd34da68eb23730857e0716b79af51d69a60757b", + 'rgba32-1.bmp': "3958d18d2a7f32ada69cb11e0b4397821225a5c40acc7b6d36ff28ee4565e150cc508971278a2ddf8948aaff86f66ec6a0c24513db44962d81b79c4239b3e612", + 'rgba32-1010102.bmp': "59a28db5563caf954d31b20a1d1cc59366fcfd258b7ba2949f7281978460a3d94bedcc314c089243dd7463bb18d36a9473355158a7d903912cb25b98eab6b068", + 'rgba32-2.bmp': "9b7e5965ff9888f011540936ab6b3022edf9f6c5d7e541d6882cb81820cf1d68326d65695a6f0d01999ac10a496a504440906aa45e413da593405563c54c1a05", + 'rgba32-61754.bmp': "784ae0a32b19fa925e0c86dbff3bd38d80904d0fa7dc3b03e9d4f707d42b1604c1f54229e901ccc249cab8c2976d58b1e16980157d9bf3dbc4e035e2b2fd1779", + 'rgba32-81284.bmp': "fcfca645017c0d15d44b08598a90d238d063763fd06db665d9a8e36ef5099ce0bf4d440e615c6d6b1bf99f38230d4848318bfa1e6d9bfdd6dfd521cc633ba110", + 'rgba32abf.bmp': "2883d676966d298d235196f102e044e52ef18f3cb5bb0dd84738c679f0a1901181483ca2df1cccf6e4b3b4e98be39e81de69c9a58f0d70bc3ebb0fcea80daa0c", + 'rgba32h56.bmp': "507d0caf29ccb011c83c0c069c21105ea1d58e06b92204f9c612f26102123a7680eae53fef023c701952d903e11b61f8aa07618c381ea08f6808c523f5a84546", + 'rgba64.bmp': "d01f14f649c1c33e3809508cc6f089dd2ab0a538baf833a91042f2e54eca3f8e409908e15fa8763b059d7fa022cf5c074d9f5720eed5293a4c922e131c2eae68", + 'rletopdown.bmp': "37500893aad0b40656aa80fd5c7c5f9b35d033018b8070d8b1d7baeb34c90f90462288b13295204b90aa3e5c9be797d22a328e3714ab259334e879a09a3de175", + 'shortfile.bmp': "be3ffade7999304f00f9b7d152b5b27811ad1166d0fd43004392467a28f44b6a4ec02a23c0296bacd4f02f8041cd824b9ca6c9fc31fed27e36e572113bb47d73", + 'unicode.xml': "e0cdc94f07fdbb15eea811ed2ae6dcf494a83d197dafe6580c740270feb0d8f5f7146d4a7d4c2d2ea25f8bd9678bc986123484b39399819a6b7262687959d1ae", } @@ -233,6 +321,7 @@ def try_download_and_unpack_zip(suite): hmac_digest = hmac.new(HMAC_KEY.encode(), file_data, HMAC_HASH).hexdigest() print("{} *{}".format(hmac_digest, file.filename)) + if not hmac.compare_digest(hmac_digest, HMAC_DIGESTS[file.filename]): print("FAIL! Expected: {}".format(HMAC_DIGESTS[file.filename])) return 4 @@ -263,4 +352,4 @@ def main(): return 0 if __name__ == '__main__': - sys.exit(main()) + sys.exit(main()) \ No newline at end of file From 039bb8794aae3b88cc0c14ac1b3f17c28bac0184 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 6 Jun 2024 17:59:12 +0100 Subject: [PATCH 189/270] Improve `matrix_align_of` logic when it has invalid inputs. --- src/types.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types.cpp b/src/types.cpp index 618e5bd8a..45aa26894 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1491,10 +1491,10 @@ gb_internal i64 matrix_align_of(Type *t, struct TypePath *tp) { i64 total_expected_size = row_count*t->Matrix.column_count*elem_size; // i64 min_alignment = prev_pow2(elem_align * row_count); i64 min_alignment = prev_pow2(total_expected_size); - while ((total_expected_size % min_alignment) != 0) { + while (total_expected_size != 0 && (total_expected_size % min_alignment) != 0) { min_alignment >>= 1; } - GB_ASSERT(min_alignment >= elem_align); + min_alignment = gb_max(min_alignment, elem_align); i64 align = gb_min(min_alignment, build_context.max_simd_align); return align; From e2eb3cdd8afb8e105414ba5459d0b6382b609d89 Mon Sep 17 00:00:00 2001 From: Laytan Date: Thu, 6 Jun 2024 19:15:02 +0200 Subject: [PATCH 190/270] fix linking on weird linuxes --- src/build_settings.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 52e6fd7ff..251dd06dd 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -2035,6 +2035,9 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta bc->link_flags = str_lit("/machine:x86 "); break; } + } else if (bc->metrics.os == TargetOs_darwin) { + bc->link_flags = concatenate3_strings(permanent_allocator(), + str_lit("-target "), bc->metrics.target_triplet, str_lit(" ")); } else if (is_arch_wasm()) { gbString link_flags = gb_string_make(heap_allocator(), " "); // link_flags = gb_string_appendc(link_flags, "--export-all "); @@ -2052,8 +2055,13 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta // Disallow on wasm bc->use_separate_modules = false; } else { - bc->link_flags = concatenate3_strings(permanent_allocator(), - str_lit("-target "), bc->metrics.target_triplet, str_lit(" ")); + // NOTE: for targets other than darwin, we don't specify a `-target` link flag. + // This is because we don't support cross-linking and clang is better at figuring + // out what the actual target for linking is, + // for example, on x86/alpine/musl it HAS to be `x86_64-alpine-linux-musl` to link correctly. + // + // Note that codegen will still target the triplet we specify, but the intricate details of + // a target shouldn't matter as much to codegen (if it does at all) as it does to linking. } // NOTE: needs to be done after adding the -target flag to the linker flags so the linker From ff37a7435cbb794bdf4244f94e0abe820c8a8288 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 6 Jun 2024 21:03:35 +0100 Subject: [PATCH 191/270] Add parentheses around the foreign import paths --- vendor/raylib/raylib.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index 6204f0bde..ebb1cdd27 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -118,8 +118,8 @@ when ODIN_OS == .Windows { } else when ODIN_OS == .Darwin { foreign import lib { "macos" + - "-arm64" when ODIN_ARCH == .arm64 else "" + - "/libraylib" + ".500.dylib" when RAYLIB_SHARED else ".a", + ("-arm64" when ODIN_ARCH == .arm64 else "") + + "/libraylib" + (".500.dylib" when RAYLIB_SHARED else ".a"), "system:Cocoa.framework", "system:OpenGL.framework", "system:IOKit.framework", From 566119ff83b72a473f9dc8630876e414f2d44b8a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Thu, 6 Jun 2024 23:52:32 +0200 Subject: [PATCH 192/270] Add saving of 24 and 32-bit images to BMP format. --- core/image/bmp/bmp.odin | 96 +++++++++++++++++++++++++++++++++++++- core/image/bmp/bmp_os.odin | 15 ++++++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/core/image/bmp/bmp.odin b/core/image/bmp/bmp.odin index d9ecb55e5..64fc1d5a8 100644 --- a/core/image/bmp/bmp.odin +++ b/core/image/bmp/bmp.odin @@ -7,7 +7,6 @@ import "core:compress" import "core:mem" import "base:intrinsics" import "base:runtime" -@(require) import "core:fmt" Error :: image.Error Image :: image.Image @@ -19,6 +18,101 @@ RGBA_Pixel :: image.RGBA_Pixel FILE_HEADER_SIZE :: 14 INFO_STUB_SIZE :: FILE_HEADER_SIZE + size_of(image.BMP_Version) +save_to_buffer :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + if img == nil { + return .Invalid_Input_Image + } + + if output == nil { + return .Invalid_Output + } + + pixels := img.width * img.height + if pixels == 0 || pixels > image.MAX_DIMENSIONS { + return .Invalid_Input_Image + } + + // While the BMP spec (and our loader) support more fanciful image types, + // `bmp.save` supports only 3 and 4 channel images with a bit depth of 8. + if img.depth != 8 || img.channels < 3 || img.channels > 4 { + return .Invalid_Input_Image + } + + if img.channels * pixels != len(img.pixels.buf) { + return .Invalid_Input_Image + } + + // Calculate and allocate size. + header_size := u32le(image.BMP_Version.V3) + total_header_size := header_size + 14 // file header = 14 + pixel_count_bytes := u32le(align4(img.width * img.channels) * img.height) + + header := image.BMP_Header{ + // File header + magic = .Bitmap, + size = total_header_size + pixel_count_bytes, + _res1 = 0, + _res2 = 0, + pixel_offset = total_header_size, + // V3 + info_size = .V3, + width = i32le(img.width), + height = i32le(img.height), + planes = 1, + bpp = u16le(8 * img.channels), + compression = .RGB, + image_size = pixel_count_bytes, + pels_per_meter = {2835, 2835}, // 72 DPI + colors_used = 0, + colors_important = 0, + } + written := 0 + + if resize(&output.buf, int(header.size)) != nil { + return .Unable_To_Allocate_Or_Resize + } + + header_bytes := transmute([size_of(image.BMP_Header)]u8)header + written += int(total_header_size) + copy(output.buf[:], header_bytes[:written]) + + switch img.channels { + case 3: + row_bytes := img.width * img.channels + row_padded := align4(row_bytes) + pixels := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:]) + for y in 0.. (img: ^Image, err: Error) { ctx := &compress.Context_Memory_Input{ input_data = data, diff --git a/core/image/bmp/bmp_os.odin b/core/image/bmp/bmp_os.odin index 2245db4fd..d20abc685 100644 --- a/core/image/bmp/bmp_os.odin +++ b/core/image/bmp/bmp_os.odin @@ -2,6 +2,7 @@ package core_image_bmp import "core:os" +import "core:bytes" load :: proc{load_from_file, load_from_bytes, load_from_context} @@ -16,4 +17,18 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont } else { return nil, .Unable_To_Read_File } +} + +save :: proc{save_to_buffer, save_to_file} + +save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator + + out := &bytes.Buffer{} + defer bytes.buffer_destroy(out) + + save_to_buffer(out, img, options) or_return + write_ok := os.write_entire_file(output, out.buf[:]) + + return nil if write_ok else .Unable_To_Write_File } \ No newline at end of file From ed060819f34bd6b5b9f1f30136a7929faa2b36b4 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 7 Jun 2024 00:14:15 +0200 Subject: [PATCH 193/270] Test roundtripping BMP --- tests/core/image/test_core_image.odin | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/tests/core/image/test_core_image.odin b/tests/core/image/test_core_image.odin index 0ce15715c..495950c80 100644 --- a/tests/core/image/test_core_image.odin +++ b/tests/core/image/test_core_image.odin @@ -2327,10 +2327,6 @@ run_bmp_suite :: proc(t: ^testing.T, suite: []Test) { testing.expectf(t, passed, "%q failed to load with error %v.", file.file, err) if err == nil { // No point in running the other tests if it didn't load. - qoi_file := strings.concatenate({TEST_SUITE_PATH_BMP, "/", file.file, ".qoi"}, context.allocator) - defer delete(qoi_file) - - qoi.save(qoi_file, img) pixels := bytes.buffer_to_bytes(&img.pixels) dims := Dims{img.width, img.height, img.channels, img.depth} @@ -2338,6 +2334,26 @@ run_bmp_suite :: proc(t: ^testing.T, suite: []Test) { img_hash := hash.crc32(pixels) testing.expectf(t, test.hash == img_hash, "%v test #1's hash is %08x, expected %08x with %v.", file.file, img_hash, test.hash, test.options) + + // Save to BMP file in memory + buf: bytes.Buffer + save_err := bmp.save(&buf, img) + testing.expectf(t, save_err == nil, "expected saving to BMP in memory not to raise error, got %v", save_err) + + // Reload BMP from memory + reload_img, reload_err := bmp.load(buf.buf[:]) + testing.expectf(t, reload_err == nil, "expected reloading BMP from memory not to raise error, got %v", reload_err) + + testing.expect(t, img.width == reload_img.width && img.height == reload_img.height, "expected saved BMP to have the same dimensions") + testing.expect(t, img.channels == reload_img.channels && img.depth == reload_img.depth, "expected saved BMP to have the same dimensions") + + reload_pixels := bytes.buffer_to_bytes(&reload_img.pixels) + reload_hash := hash.crc32(reload_pixels) + + testing.expectf(t, img_hash == reload_hash, "expected saved BMP to have the same pixel hash (%08x), got %08x", img_hash, reload_hash) + + bytes.buffer_destroy(&buf) + bmp.destroy(reload_img) } bmp.destroy(img) } From 7044a7d77650e922a66f3bfe99711f3ed370e1ba Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 6 Jun 2024 23:55:48 +0100 Subject: [PATCH 194/270] Try to fix a possible race condition with polymorphic record parameters --- src/check_builtin.cpp | 31 ++++++++----------------------- src/check_expr.cpp | 11 +++-------- src/check_type.cpp | 30 +++++++++++++----------------- src/checker.cpp | 4 ++++ src/types.cpp | 18 ++++++++++++++++++ 5 files changed, 46 insertions(+), 48 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 7e3bcb7ee..eef925d94 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -5912,15 +5912,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (operand->mode != Addressing_Type) { error(operand->expr, "Expected a record type for '%.*s'", LIT(builtin_name)); } else { - Type *bt = base_type(operand->type); - if (bt->kind == Type_Struct) { - if (bt->Struct.polymorphic_params != nullptr) { - operand->value = exact_value_i64(bt->Struct.polymorphic_params->Tuple.variables.count); - } - } else if (bt->kind == Type_Union) { - if (bt->Union.polymorphic_params != nullptr) { - operand->value = exact_value_i64(bt->Union.polymorphic_params->Tuple.variables.count); - } + TypeTuple *tuple = get_record_polymorphic_params(operand->type); + if (tuple) { + operand->value = exact_value_i64(tuple->variables.count); } else { error(operand->expr, "Expected a record type for '%.*s'", LIT(builtin_name)); } @@ -5952,20 +5946,11 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As Entity *param = nullptr; i64 count = 0; - Type *bt = base_type(operand->type); - if (bt->kind == Type_Struct) { - if (bt->Struct.polymorphic_params != nullptr) { - count = bt->Struct.polymorphic_params->Tuple.variables.count; - if (index < count) { - param = bt->Struct.polymorphic_params->Tuple.variables[cast(isize)index]; - } - } - } else if (bt->kind == Type_Union) { - if (bt->Union.polymorphic_params != nullptr) { - count = bt->Union.polymorphic_params->Tuple.variables.count; - if (index < count) { - param = bt->Union.polymorphic_params->Tuple.variables[cast(isize)index]; - } + TypeTuple *tuple = get_record_polymorphic_params(operand->type); + if (tuple) { + count = tuple->variables.count; + if (index < count) { + param = tuple->variables[cast(isize)index]; } } else { error(operand->expr, "Expected a specialized polymorphic record type for '%.*s'", LIT(builtin_name)); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index d2d01deda..ad546858c 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7331,14 +7331,9 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O gbString s = gb_string_make_reserve(heap_allocator(), e->token.string.len+3); s = gb_string_append_fmt(s, "%.*s(", LIT(e->token.string)); - Type *params = nullptr; - switch (bt->kind) { - case Type_Struct: params = bt->Struct.polymorphic_params; break; - case Type_Union: params = bt->Union.polymorphic_params; break; - } - - if (params != nullptr) for_array(i, params->Tuple.variables) { - Entity *v = params->Tuple.variables[i]; + TypeTuple *tuple = get_record_polymorphic_params(e->type); + if (tuple != nullptr) for_array(i, tuple->variables) { + Entity *v = tuple->variables[i]; String name = v->token.string; if (i > 0) { s = gb_string_append_fmt(s, ", "); diff --git a/src/check_type.cpp b/src/check_type.cpp index 7ed657bee..e0dea19cb 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -564,19 +564,7 @@ gb_internal bool check_record_poly_operand_specialization(CheckerContext *ctx, T gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types, isize param_count, Array const &ordered_operands) { for (Entity *e : found_gen_types->types) { Type *t = base_type(e->type); - TypeTuple *tuple = nullptr; - switch (t->kind) { - case Type_Struct: - if (t->Struct.polymorphic_params) { - tuple = &t->Struct.polymorphic_params->Tuple; - } - break; - case Type_Union: - if (t->Union.polymorphic_params) { - tuple = &t->Union.polymorphic_params->Tuple; - } - break; - } + TypeTuple *tuple = get_record_polymorphic_params(t); GB_ASSERT_MSG(tuple != nullptr, "%s :: %s", type_to_string(e->type), type_to_string(t)); GB_ASSERT(param_count == tuple->variables.count); @@ -663,6 +651,8 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * &struct_type->Struct.is_polymorphic, node, poly_operands ); + wait_signal_set(&struct_type->Struct.polymorphic_wait_signal); + struct_type->Struct.is_poly_specialized = check_record_poly_operand_specialization(ctx, struct_type, poly_operands, &struct_type->Struct.is_polymorphic); if (original_type_for_poly) { GB_ASSERT(named_type != nullptr); @@ -712,6 +702,8 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no &union_type->Union.is_polymorphic, node, poly_operands ); + wait_signal_set(&union_type->Union.polymorphic_wait_signal); + union_type->Union.is_poly_specialized = check_record_poly_operand_specialization(ctx, union_type, poly_operands, &union_type->Union.is_polymorphic); if (original_type_for_poly) { GB_ASSERT(named_type != nullptr); @@ -1453,12 +1445,14 @@ gb_internal bool check_type_specialization_to(CheckerContext *ctx, Type *special return true; } + wait_for_record_polymorphic_params(s); + wait_for_record_polymorphic_params(t); if (t->Struct.polymorphic_parent == s->Struct.polymorphic_parent && s->Struct.polymorphic_params != nullptr && t->Struct.polymorphic_params != nullptr) { - TypeTuple *s_tuple = &s->Struct.polymorphic_params->Tuple; - TypeTuple *t_tuple = &t->Struct.polymorphic_params->Tuple; + TypeTuple *s_tuple = get_record_polymorphic_params(s); + TypeTuple *t_tuple = get_record_polymorphic_params(t); GB_ASSERT(t_tuple->variables.count == s_tuple->variables.count); for_array(i, s_tuple->variables) { Entity *s_e = s_tuple->variables[i]; @@ -1506,12 +1500,14 @@ gb_internal bool check_type_specialization_to(CheckerContext *ctx, Type *special return true; } + wait_for_record_polymorphic_params(s); + wait_for_record_polymorphic_params(t); if (t->Union.polymorphic_parent == s->Union.polymorphic_parent && s->Union.polymorphic_params != nullptr && t->Union.polymorphic_params != nullptr) { - TypeTuple *s_tuple = &s->Union.polymorphic_params->Tuple; - TypeTuple *t_tuple = &t->Union.polymorphic_params->Tuple; + TypeTuple *s_tuple = get_record_polymorphic_params(s); + TypeTuple *t_tuple = get_record_polymorphic_params(t); GB_ASSERT(t_tuple->variables.count == s_tuple->variables.count); for_array(i, s_tuple->variables) { Entity *s_e = s_tuple->variables[i]; diff --git a/src/checker.cpp b/src/checker.cpp index 8a58bb425..e90509c1f 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2031,6 +2031,7 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { } else { add_type_info_type_internal(c, t_type_info_ptr); } + wait_for_record_polymorphic_params(bt); add_type_info_type_internal(c, bt->Union.polymorphic_params); for_array(i, bt->Union.variants) { add_type_info_type_internal(c, bt->Union.variants[i]); @@ -2063,6 +2064,7 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { } } } + wait_for_record_polymorphic_params(bt); add_type_info_type_internal(c, bt->Struct.polymorphic_params); for_array(i, bt->Struct.fields) { Entity *f = bt->Struct.fields[i]; @@ -2292,6 +2294,7 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { } else { add_min_dep_type_info(c, t_type_info_ptr); } + wait_for_record_polymorphic_params(bt); add_min_dep_type_info(c, bt->Union.polymorphic_params); for_array(i, bt->Union.variants) { add_min_dep_type_info(c, bt->Union.variants[i]); @@ -2321,6 +2324,7 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { } } } + wait_for_record_polymorphic_params(bt); add_min_dep_type_info(c, bt->Struct.polymorphic_params); for_array(i, bt->Struct.fields) { Entity *f = bt->Struct.fields[i]; diff --git a/src/types.cpp b/src/types.cpp index 45aa26894..4ceba5244 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -140,6 +140,7 @@ struct TypeStruct { i64 custom_field_align; Type * polymorphic_params; // Type_Tuple Type * polymorphic_parent; + Wait_Signal polymorphic_wait_signal; Type * soa_elem; i32 soa_count; @@ -167,6 +168,7 @@ struct TypeUnion { i64 custom_align; Type * polymorphic_params; // Type_Tuple Type * polymorphic_parent; + Wait_Signal polymorphic_wait_signal; i16 tag_size; bool is_polymorphic; @@ -1093,6 +1095,7 @@ gb_internal Type *alloc_type_struct() { gb_internal Type *alloc_type_struct_complete() { Type *t = alloc_type(Type_Struct); wait_signal_set(&t->Struct.fields_wait_signal); + wait_signal_set(&t->Struct.polymorphic_wait_signal); return t; } @@ -2136,15 +2139,30 @@ gb_internal bool is_type_polymorphic_record_unspecialized(Type *t) { return false; } +gb_internal void wait_for_record_polymorphic_params(Type *t) { + t = base_type(t); + switch (t->kind) { + case Type_Struct: + wait_signal_until_available(&t->Struct.polymorphic_wait_signal); + break; + case Type_Union: + wait_signal_until_available(&t->Union.polymorphic_wait_signal); + break; + } +} + + gb_internal TypeTuple *get_record_polymorphic_params(Type *t) { t = base_type(t); switch (t->kind) { case Type_Struct: + wait_signal_until_available(&t->Struct.polymorphic_wait_signal); if (t->Struct.polymorphic_params) { return &t->Struct.polymorphic_params->Tuple; } break; case Type_Union: + wait_signal_until_available(&t->Union.polymorphic_wait_signal); if (t->Union.polymorphic_params) { return &t->Union.polymorphic_params->Tuple; } From 68781f8dd365692aee55099d3e14cb83a115764c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 7 Jun 2024 00:11:00 +0100 Subject: [PATCH 195/270] Remove unnecessary Wait_Signal checks --- src/check_type.cpp | 4 ---- src/checker.cpp | 4 ---- src/types.cpp | 12 ------------ 3 files changed, 20 deletions(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index e0dea19cb..17f7813d5 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1445,8 +1445,6 @@ gb_internal bool check_type_specialization_to(CheckerContext *ctx, Type *special return true; } - wait_for_record_polymorphic_params(s); - wait_for_record_polymorphic_params(t); if (t->Struct.polymorphic_parent == s->Struct.polymorphic_parent && s->Struct.polymorphic_params != nullptr && t->Struct.polymorphic_params != nullptr) { @@ -1500,8 +1498,6 @@ gb_internal bool check_type_specialization_to(CheckerContext *ctx, Type *special return true; } - wait_for_record_polymorphic_params(s); - wait_for_record_polymorphic_params(t); if (t->Union.polymorphic_parent == s->Union.polymorphic_parent && s->Union.polymorphic_params != nullptr && t->Union.polymorphic_params != nullptr) { diff --git a/src/checker.cpp b/src/checker.cpp index e90509c1f..8a58bb425 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2031,7 +2031,6 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { } else { add_type_info_type_internal(c, t_type_info_ptr); } - wait_for_record_polymorphic_params(bt); add_type_info_type_internal(c, bt->Union.polymorphic_params); for_array(i, bt->Union.variants) { add_type_info_type_internal(c, bt->Union.variants[i]); @@ -2064,7 +2063,6 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { } } } - wait_for_record_polymorphic_params(bt); add_type_info_type_internal(c, bt->Struct.polymorphic_params); for_array(i, bt->Struct.fields) { Entity *f = bt->Struct.fields[i]; @@ -2294,7 +2292,6 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { } else { add_min_dep_type_info(c, t_type_info_ptr); } - wait_for_record_polymorphic_params(bt); add_min_dep_type_info(c, bt->Union.polymorphic_params); for_array(i, bt->Union.variants) { add_min_dep_type_info(c, bt->Union.variants[i]); @@ -2324,7 +2321,6 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { } } } - wait_for_record_polymorphic_params(bt); add_min_dep_type_info(c, bt->Struct.polymorphic_params); for_array(i, bt->Struct.fields) { Entity *f = bt->Struct.fields[i]; diff --git a/src/types.cpp b/src/types.cpp index 4ceba5244..97e8267a3 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2139,18 +2139,6 @@ gb_internal bool is_type_polymorphic_record_unspecialized(Type *t) { return false; } -gb_internal void wait_for_record_polymorphic_params(Type *t) { - t = base_type(t); - switch (t->kind) { - case Type_Struct: - wait_signal_until_available(&t->Struct.polymorphic_wait_signal); - break; - case Type_Union: - wait_signal_until_available(&t->Union.polymorphic_wait_signal); - break; - } -} - gb_internal TypeTuple *get_record_polymorphic_params(Type *t) { t = base_type(t); From ee93d7c05e563512ea41b00cc1347eb59427c500 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 7 Jun 2024 15:13:09 +0200 Subject: [PATCH 196/270] add error message for unknown test log level It would previously just be a compilation error about a missing return statement. --- core/testing/runner.odin | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/testing/runner.odin b/core/testing/runner.odin index c82aa1fda..328186c35 100644 --- a/core/testing/runner.odin +++ b/core/testing/runner.odin @@ -53,6 +53,9 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level { else when LOG_LEVEL == "warning" { return .Warning } else when LOG_LEVEL == "error" { return .Error } else when LOG_LEVEL == "fatal" { return .Fatal } + else { + #panic("Unknown `ODIN_TEST_LOG_LEVEL`: \"" + LOG_LEVEL + "\", possible levels are: \"debug\", \"info\", \"warning\", \"error\", or \"fatal\".") + } } } From 0a528777e87b228d0e0db9f9bde33b365d9ef90b Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 7 Jun 2024 15:31:01 +0200 Subject: [PATCH 197/270] utilize `odin test -all-packages` instead of (make/bat) scripts for running tests --- .github/workflows/ci.yml | 225 +++++++----------------- tests/benchmark/Makefile | 14 -- tests/benchmark/all.odin | 4 + tests/benchmark/build.bat | 13 -- tests/core/Makefile | 115 ------------ tests/core/build.bat | 124 ------------- tests/core/download_assets.py | 4 +- tests/core/image/build.bat | 2 - tests/core/net/test_core_net.odin | 3 +- tests/core/normal.odin | 39 ++++ tests/core/os/test_core_os_exit.odin | 10 -- tests/core/speed.odin | 5 + tests/internal/Makefile | 24 --- tests/internal/build.bat | 9 - tests/internal/test_128.odin | 2 +- tests/internal/test_asan.odin | 2 +- tests/internal/test_map.odin | 4 +- tests/internal/test_pow.odin | 4 +- tests/internal/test_rtti.odin | 4 +- tests/internal/test_string_compare.odin | 4 +- tests/issues/run.bat | 1 - tests/issues/run.sh | 1 - tests/vendor/Makefile | 10 -- tests/vendor/all.odin | 3 + tests/vendor/build.bat | 8 - tests/vendor/glfw/test_vendor_glfw.odin | 1 + 26 files changed, 130 insertions(+), 505 deletions(-) delete mode 100644 tests/benchmark/Makefile create mode 100644 tests/benchmark/all.odin delete mode 100644 tests/benchmark/build.bat delete mode 100644 tests/core/Makefile delete mode 100644 tests/core/build.bat delete mode 100644 tests/core/image/build.bat create mode 100644 tests/core/normal.odin delete mode 100644 tests/core/os/test_core_os_exit.odin create mode 100644 tests/core/speed.odin delete mode 100644 tests/internal/Makefile delete mode 100644 tests/internal/build.bat delete mode 100644 tests/vendor/Makefile create mode 100644 tests/vendor/all.odin delete mode 100644 tests/vendor/build.bat diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2884fb301..0aa6a7277 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,184 +29,99 @@ jobs: gmake release ./odin version ./odin report + gmake -C vendor/stb/src + gmake -C vendor/cgltf/src + gmake -C vendor/miniaudio/src ./odin check examples/all -vet -strict-style -target:netbsd_amd64 ./odin check examples/all -vet -strict-style -target:netbsd_arm64 - (cd tests/core; gmake all_bsd) - (cd tests/internal; gmake all_bsd) + ./odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false + ./odin test tests/core/speed.odin -file -all-packages -o:speed -define:ODIN_TEST_FANCY=false + ./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false + ./odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false (cd tests/issues; ./run.sh) - (cd tests/benchmark; gmake all) - build_linux: - name: Ubuntu Build, Check, and Test - runs-on: ubuntu-latest + ci: + strategy: + fail-fast: false + matrix: + # MacOS 13 runs on Intel, 14 runs on ARM + os: [ubuntu-latest, macos-13, macos-14] + runs-on: ${{ matrix.os }} + name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel' || 'Ubuntu') }} Build, Check, and Test + timeout-minutes: 15 steps: - uses: actions/checkout@v1 - - name: Download LLVM + + - name: Download LLVM (Linux) + if: matrix.os == 'ubuntu-latest' run: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh sudo ./llvm.sh 17 echo "/usr/lib/llvm-17/bin" >> $GITHUB_PATH - - name: build odin - run: ./build_odin.sh release - - name: Odin version - run: ./odin version - timeout-minutes: 1 - - name: Odin report - run: ./odin report - timeout-minutes: 1 - - name: Compile needed Vendor - run: | - make -C $(./odin root)/vendor/stb/src - make -C $(./odin root)/vendor/cgltf/src - make -C $(./odin root)/vendor/miniaudio/src - timeout-minutes: 10 - - name: Odin check - run: ./odin check examples/demo -vet - timeout-minutes: 10 - - name: Odin run - run: ./odin run examples/demo - timeout-minutes: 10 - - name: Odin run -debug - run: ./odin run examples/demo -debug - timeout-minutes: 10 - - name: Odin check examples/all - run: ./odin check examples/all -strict-style - timeout-minutes: 10 - - name: Core library tests - run: | - cd tests/core - make - timeout-minutes: 10 - - name: Vendor library tests - run: | - cd tests/vendor - make - timeout-minutes: 10 - - name: Odin internals tests - run: | - cd tests/internal - make - timeout-minutes: 10 - - name: Odin core library benchmarks - run: | - cd tests/benchmark - make - timeout-minutes: 10 - - name: Odin check examples/all for Linux i386 - run: ./odin check examples/all -vet -strict-style -target:linux_i386 - timeout-minutes: 10 - - name: Odin check examples/all for Linux arm64 - run: ./odin check examples/all -vet -strict-style -target:linux_arm64 - timeout-minutes: 10 - - name: Odin check examples/all for FreeBSD amd64 - run: ./odin check examples/all -vet -strict-style -target:freebsd_amd64 - timeout-minutes: 10 - - name: Odin check examples/all for OpenBSD amd64 - run: ./odin check examples/all -vet -strict-style -target:openbsd_amd64 - timeout-minutes: 10 - build_macOS: - name: MacOS Build, Check, and Test - runs-on: macos-13 - steps: - - uses: actions/checkout@v1 - - name: Download LLVM, and setup PATH + + - name: Download LLVM (MacOS Intel) + if: matrix.os == 'macos-13' run: | brew install llvm@17 echo "/usr/local/opt/llvm@17/bin" >> $GITHUB_PATH - - name: build odin - run: ./build_odin.sh release - - name: Odin version - run: ./odin version - timeout-minutes: 1 - - name: Odin report - run: ./odin report - timeout-minutes: 1 - - name: Compile needed Vendor - run: | - make -C $(./odin root)/vendor/stb/src - make -C $(./odin root)/vendor/cgltf/src - make -C $(./odin root)/vendor/miniaudio/src - timeout-minutes: 10 - - name: Odin check - run: ./odin check examples/demo -vet - timeout-minutes: 10 - - name: Odin run - run: ./odin run examples/demo - timeout-minutes: 10 - - name: Odin run -debug - run: ./odin run examples/demo -debug - timeout-minutes: 10 - - name: Odin check examples/all - run: ./odin check examples/all -strict-style - timeout-minutes: 10 - - name: Core library tests - run: | - cd tests/core - make - timeout-minutes: 10 - - name: Odin internals tests - run: | - cd tests/internal - make - timeout-minutes: 10 - - name: Odin core library benchmarks - run: | - cd tests/benchmark - make - timeout-minutes: 10 - build_macOS_arm: - name: MacOS ARM Build, Check, and Test - runs-on: macos-14 # This is an arm/m1 runner. - steps: - - uses: actions/checkout@v1 - - name: Download LLVM and setup PATH + + - name: Download LLVM (MacOS ARM) + if: matrix.os == 'macos-14' run: | brew install llvm@17 echo "/opt/homebrew/opt/llvm@17/bin" >> $GITHUB_PATH - - name: build odin + + - name: Build Odin run: ./build_odin.sh release - name: Odin version run: ./odin version - timeout-minutes: 1 - name: Odin report run: ./odin report - timeout-minutes: 1 - name: Compile needed Vendor run: | make -C $(./odin root)/vendor/stb/src make -C $(./odin root)/vendor/cgltf/src make -C $(./odin root)/vendor/miniaudio/src - timeout-minutes: 10 - name: Odin check run: ./odin check examples/demo -vet - timeout-minutes: 10 - name: Odin run run: ./odin run examples/demo - timeout-minutes: 10 - name: Odin run -debug run: ./odin run examples/demo -debug - timeout-minutes: 10 - name: Odin check examples/all run: ./odin check examples/all -strict-style - timeout-minutes: 10 - - name: Core library tests + - name: Normal Core library tests + run: ./odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false + - name: Optimized Core library tests + run: ./odin test tests/core/speed.odin -o:speed -file -all-packages -define:ODIN_TEST_FANCY=false + - name: Vendor library tests + run: ./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false + - name: Internals tests + run: ./odin test tests/internal -all-packages -define:ODIN_TEST_FANCY=false + - name: Core library benchmarks + run: ./odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false + - name: GitHub Issue tests run: | - cd tests/core - make - timeout-minutes: 10 - - name: Odin internals tests - run: | - cd tests/internal - make - timeout-minutes: 10 - - name: Odin core library benchmarks - run: | - cd tests/benchmark - make - timeout-minutes: 10 + cd tests/issues + ./run.sh + + - name: Odin check examples/all for Linux i386 + run: ./odin check examples/all -vet -strict-style -target:linux_i386 + if: matrix.os == 'ubuntu-latest' + - name: Odin check examples/all for Linux arm64 + run: ./odin check examples/all -vet -strict-style -target:linux_arm64 + if: matrix.os == 'ubuntu-latest' + - name: Odin check examples/all for FreeBSD amd64 + run: ./odin check examples/all -vet -strict-style -target:freebsd_amd64 + if: matrix.os == 'ubuntu-latest' + - name: Odin check examples/all for OpenBSD amd64 + run: ./odin check examples/all -vet -strict-style -target:openbsd_amd64 + if: matrix.os == 'ubuntu-latest' + build_windows: name: Windows Build, Check, and Test runs-on: windows-2022 + timeout-minutes: 15 steps: - uses: actions/checkout@v1 - name: build Odin @@ -216,79 +131,67 @@ jobs: ./build.bat 1 - name: Odin version run: ./odin version - timeout-minutes: 1 - name: Odin report run: ./odin report - timeout-minutes: 1 - name: Odin check shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat odin check examples/demo -vet - timeout-minutes: 10 - name: Odin run shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat odin run examples/demo - timeout-minutes: 10 - name: Odin run -debug shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat odin run examples/demo -debug - timeout-minutes: 10 - name: Odin check examples/all shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat odin check examples/all -strict-style - timeout-minutes: 10 - name: Core library tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - cd tests\core - call build.bat - timeout-minutes: 10 + odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false + - name: Optimized core library tests + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat + odin test tests/core/speed.odin -o:speed -file -all-packages -define:ODIN_TEST_FANCY=false - name: Core library benchmarks shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - cd tests\benchmark - call build.bat - timeout-minutes: 10 + odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false - name: Vendor library tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - cd tests\vendor - call build.bat - timeout-minutes: 10 + odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false - name: Odin internals tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat - cd tests\internal - call build.bat - timeout-minutes: 10 + odin test tests/internal -all-packages -define:ODIN_TEST_FANCY=false - name: Odin documentation tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat cd tests\documentation call build.bat - timeout-minutes: 10 - name: core:math/big tests shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat cd tests\core\math\big call build.bat - timeout-minutes: 10 - name: Odin check examples/all for Windows 32bits shell: cmd run: | call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat odin check examples/all -strict-style -target:windows_i386 - timeout-minutes: 10 diff --git a/tests/benchmark/Makefile b/tests/benchmark/Makefile deleted file mode 100644 index 840174dbb..000000000 --- a/tests/benchmark/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -ODIN=../../odin -COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false - -all: crypto_bench \ - hash_bench - -crypto_bench: - $(ODIN) test crypto $(COMMON) -o:speed -out:bench_crypto - -hash_bench: - $(ODIN) test hash $(COMMON) -o:speed -out:bench_hash - -clean: - rm bench_* \ No newline at end of file diff --git a/tests/benchmark/all.odin b/tests/benchmark/all.odin new file mode 100644 index 000000000..d1b7662e2 --- /dev/null +++ b/tests/benchmark/all.odin @@ -0,0 +1,4 @@ +package benchmarks + +@(require) import "crypto" +@(require) import "hash" diff --git a/tests/benchmark/build.bat b/tests/benchmark/build.bat deleted file mode 100644 index 30b030066..000000000 --- a/tests/benchmark/build.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off -set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false -set PATH_TO_ODIN==..\..\odin - -echo --- -echo Running core:crypto benchmarks -echo --- -%PATH_TO_ODIN% test crypto %COMMON% -o:speed -out:bench_crypto.exe || exit /b - -echo --- -echo Running core:hash benchmarks -echo --- -%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:bench_hash.exe || exit /b diff --git a/tests/core/Makefile b/tests/core/Makefile deleted file mode 100644 index 79af9c3c7..000000000 --- a/tests/core/Makefile +++ /dev/null @@ -1,115 +0,0 @@ -ODIN=../../odin -PYTHON=$(shell which python3) -COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false - -all: all_bsd \ - net_test - -all_bsd: download_test_assets \ - c_libc_test \ - compress_test \ - container_test \ - crypto_test \ - encoding_test \ - filepath_test \ - fmt_test \ - hash_test \ - i18n_test \ - image_test \ - linalg_glsl_math_test \ - match_test \ - math_test \ - noise_test \ - odin_test \ - os_exit_test \ - reflect_test \ - runtime_test \ - slice_test \ - strconv_test \ - strings_test \ - thread_test \ - time_test - -download_test_assets: - $(PYTHON) download_assets.py - -c_libc_test: - $(ODIN) test c/libc $(COMMON) -out:test_core_libc - -compress_test: - $(ODIN) test compress $(COMMON) -out:test_core_compress - -container_test: - $(ODIN) test container $(COMMON) -out:test_core_container - -crypto_test: - $(ODIN) test crypto $(COMMON) -o:speed -out:test_crypto - -encoding_test: - $(ODIN) test encoding/base64 $(COMMON) -out:test_base64 - $(ODIN) test encoding/cbor $(COMMON) -out:test_cbor - $(ODIN) test encoding/hex $(COMMON) -out:test_hex - $(ODIN) test encoding/hxa $(COMMON) -out:test_hxa - $(ODIN) test encoding/json $(COMMON) -out:test_json - $(ODIN) test encoding/varint $(COMMON) -out:test_varint - $(ODIN) test encoding/xml $(COMMON) -out:test_xml - -filepath_test: - $(ODIN) test path/filepath $(COMMON) -out:test_core_filepath - -fmt_test: - $(ODIN) test fmt $(COMMON) -out:test_core_fmt - -hash_test: - $(ODIN) test hash $(COMMON) -o:speed -out:test_hash - -image_test: - $(ODIN) test image $(COMMON) -out:test_core_image - -i18n_test: - $(ODIN) test text/i18n $(COMMON) -out:test_core_i18n - -match_test: - $(ODIN) test text/match $(COMMON) -out:test_core_match - -math_test: - $(ODIN) test math $(COMMON) -out:test_core_math - -linalg_glsl_math_test: - $(ODIN) test math/linalg/glsl $(COMMON) -out:test_linalg_glsl_math - -noise_test: - $(ODIN) test math/noise $(COMMON) -out:test_noise - -net_test: - $(ODIN) test net $(COMMON) -out:test_core_net - -os_exit_test: - $(ODIN) run os/test_core_os_exit.odin -file -out:test_core_os_exit && exit 1 || exit 0 - -odin_test: - $(ODIN) test odin $(COMMON) -out:test_core_odin - -reflect_test: - $(ODIN) test reflect $(COMMON) -out:test_core_reflect - -runtime_test: - $(ODIN) test runtime $(COMMON) -out:test_core_runtime - -slice_test: - $(ODIN) test slice $(COMMON) -out:test_core_slice - -strconv_test: - $(ODIN) test strconv $(COMMON) -out:test_core_strconv - -strings_test: - $(ODIN) test strings $(COMMON) -out:test_core_strings - -thread_test: - $(ODIN) test thread $(COMMON) -out:test_core_thread - -time_test: - $(ODIN) test time $(COMMON) -out:test_core_time - -clean: - rm test_* \ No newline at end of file diff --git a/tests/core/build.bat b/tests/core/build.bat deleted file mode 100644 index 18506408a..000000000 --- a/tests/core/build.bat +++ /dev/null @@ -1,124 +0,0 @@ -@echo off -set COMMON=-no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false -set PATH_TO_ODIN==..\..\odin -python3 download_assets.py -echo --- -echo Running core:c/libc tests -echo --- -%PATH_TO_ODIN% test c\libc %COMMON% -out:test_libc.exe || exit /b - -echo --- -echo Running core:compress tests -echo --- -%PATH_TO_ODIN% test compress %COMMON% -out:test_core_compress.exe || exit /b - -echo --- -echo Running core:container tests -echo --- -%PATH_TO_ODIN% test container %COMMON% -out:test_core_container.exe || exit /b - -echo --- -echo Running core:crypto tests -echo --- -%PATH_TO_ODIN% test crypto %COMMON% -o:speed -out:test_crypto.exe || exit /b - -echo --- -echo Running core:encoding tests -echo --- -%PATH_TO_ODIN% test encoding/base64 %COMMON% -out:test_base64.exe || exit /b -%PATH_TO_ODIN% test encoding/cbor %COMMON% -out:test_cbor.exe || exit /b -%PATH_TO_ODIN% test encoding/hex %COMMON% -out:test_hex.exe || exit /b -%PATH_TO_ODIN% test encoding/hxa %COMMON% -out:test_hxa.exe || exit /b -%PATH_TO_ODIN% test encoding/json %COMMON% -out:test_json.exe || exit /b -%PATH_TO_ODIN% test encoding/varint %COMMON% -out:test_varint.exe || exit /b -%PATH_TO_ODIN% test encoding/xml %COMMON% -out:test_xml.exe || exit /b - -echo --- -echo Running core:path/filepath tests -echo --- -%PATH_TO_ODIN% test path/filepath %COMMON% -out:test_core_filepath.exe || exit /b - -echo --- -echo Running core:fmt tests -echo --- -%PATH_TO_ODIN% test fmt %COMMON% -out:test_core_fmt.exe || exit /b - -echo --- -echo Running core:hash tests -echo --- -%PATH_TO_ODIN% test hash %COMMON% -o:speed -out:test_core_hash.exe || exit /b - -echo --- -echo Running core:image tests -echo --- -%PATH_TO_ODIN% test image %COMMON% -out:test_core_image.exe || exit /b - -echo --- -echo Running core:text/i18n tests -echo --- -%PATH_TO_ODIN% test text\i18n %COMMON% -out:test_core_i18n.exe || exit /b - -echo --- -echo Running text:match tests -echo --- -%PATH_TO_ODIN% test text/match %COMMON% -out:test_core_match.exe || exit /b - -echo --- -echo Running core:math tests -echo --- -%PATH_TO_ODIN% test math %COMMON% -out:test_core_math.exe || exit /b - -echo --- -echo Running core:math/linalg/glsl tests -echo --- -%PATH_TO_ODIN% test math/linalg/glsl %COMMON% -out:test_linalg_glsl.exe || exit /b - -echo --- -echo Running core:math/noise tests -echo --- -%PATH_TO_ODIN% test math/noise %COMMON% -out:test_noise.exe || exit /b - -echo --- -echo Running core:net -echo --- -%PATH_TO_ODIN% test net %COMMON% -out:test_core_net.exe || exit /b - -echo --- -echo Running core:odin tests -echo --- -%PATH_TO_ODIN% test odin %COMMON% -o:size -out:test_core_odin.exe || exit /b - -echo --- -echo Running core:reflect tests -echo --- -%PATH_TO_ODIN% test reflect %COMMON% -out:test_core_reflect.exe || exit /b - -echo --- -echo Running core:runtime tests -echo --- -%PATH_TO_ODIN% test runtime %COMMON% -out:test_core_runtime.exe || exit /b - -echo --- -echo Running core:slice tests -echo --- -%PATH_TO_ODIN% test slice %COMMON% -out:test_core_slice.exe || exit /b - -echo --- -echo Running core:strconv tests -echo --- -%PATH_TO_ODIN% test strconv %COMMON% -out:test_core_strconv.exe || exit /b - -echo --- -echo Running core:strings tests -echo --- -%PATH_TO_ODIN% test strings %COMMON% -out:test_core_strings.exe || exit /b - -echo --- -echo Running core:thread tests -echo --- -%PATH_TO_ODIN% test thread %COMMON% -out:test_core_thread.exe || exit /b - -echo --- -echo Running core:time tests -echo --- -%PATH_TO_ODIN% test time %COMMON% -out:test_core_time.exe || exit /b \ No newline at end of file diff --git a/tests/core/download_assets.py b/tests/core/download_assets.py index 8e124954f..fc4a71cdc 100644 --- a/tests/core/download_assets.py +++ b/tests/core/download_assets.py @@ -8,7 +8,7 @@ import hashlib import hmac TEST_SUITES = ['PNG', 'XML', 'BMP'] -DOWNLOAD_BASE_PATH = "assets/{}" +DOWNLOAD_BASE_PATH = sys.argv[1] + "/{}" ASSETS_BASE_URL = "https://raw.githubusercontent.com/odin-lang/test-assets/master/{}/{}" HMAC_KEY = "https://odin-lang.org" HMAC_HASH = hashlib.sha3_512 @@ -352,4 +352,4 @@ def main(): return 0 if __name__ == '__main__': - sys.exit(main()) \ No newline at end of file + sys.exit(main()) diff --git a/tests/core/image/build.bat b/tests/core/image/build.bat deleted file mode 100644 index 5a07971b8..000000000 --- a/tests/core/image/build.bat +++ /dev/null @@ -1,2 +0,0 @@ -@echo off -odin test . -define:ODIN_TEST_TRACK_MEMORY=true -define:ODIN_TEST_PROGRESS_WIDTH=12 -vet -strict-style diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index 3cbb7fa34..6aada88a6 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -8,6 +8,7 @@ A test suite for `core:net` */ +//+build !netbsd package test_core_net import "core:testing" @@ -552,4 +553,4 @@ binstr_to_address :: proc(t: ^testing.T, binstr: string) -> (address: net.Addres return nil } panic("Invalid test case") -} \ No newline at end of file +} diff --git a/tests/core/normal.odin b/tests/core/normal.odin new file mode 100644 index 000000000..f23763a59 --- /dev/null +++ b/tests/core/normal.odin @@ -0,0 +1,39 @@ +package tests_core + +import rlibc "core:c/libc" + +@(init) +download_assets :: proc() { + if rlibc.system("python3 " + ODIN_ROOT + "tests/core/download_assets.py " + ODIN_ROOT + "tests/core/assets") != 0 { + panic("downloading test assets failed!") + } +} + +@(require) import "c/libc" +@(require) import "compress" +@(require) import "container" +@(require) import "encoding/base64" +@(require) import "encoding/cbor" +@(require) import "encoding/hex" +@(require) import "encoding/hxa" +@(require) import "encoding/json" +@(require) import "encoding/varint" +@(require) import "encoding/xml" +@(require) import "fmt" +@(require) import "image" +@(require) import "math" +@(require) import "math/big" +@(require) import "math/linalg/glsl" +@(require) import "math/noise" +@(require) import "net" +@(require) import "odin" +@(require) import "path/filepath" +@(require) import "reflect" +@(require) import "runtime" +@(require) import "slice" +@(require) import "strconv" +@(require) import "strings" +@(require) import "text/i18n" +@(require) import "text/match" +@(require) import "thread" +@(require) import "time" diff --git a/tests/core/os/test_core_os_exit.odin b/tests/core/os/test_core_os_exit.odin deleted file mode 100644 index 2ab274f5e..000000000 --- a/tests/core/os/test_core_os_exit.odin +++ /dev/null @@ -1,10 +0,0 @@ -// Tests that Odin run returns exit code of built executable on Unix -// Needs exit status to be inverted to return 0 on success, e.g. -// $(./odin run tests/core/os/test_core_os_exit.odin && exit 1 || exit 0) -package test_core_os_exit - -import "core:os" - -main :: proc() { - os.exit(1) -} diff --git a/tests/core/speed.odin b/tests/core/speed.odin new file mode 100644 index 000000000..555d30f5e --- /dev/null +++ b/tests/core/speed.odin @@ -0,0 +1,5 @@ +// Tests intended to be ran with optimizations on +package tests_core + +@(require) import "crypto" +@(require) import "hash" diff --git a/tests/internal/Makefile b/tests/internal/Makefile deleted file mode 100644 index 307badb41..000000000 --- a/tests/internal/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -ODIN=../../odin -COMMON=-file -vet -strict-style -o:minimal - -all: all_bsd asan_test - -all_bsd: rtti_test map_test pow_test 128_test string_compare_test - -rtti_test: - $(ODIN) test test_rtti.odin $(COMMON) - -map_test: - $(ODIN) test test_map.odin $(COMMON) - -pow_test: - $(ODIN) test test_pow.odin $(COMMON) - -128_test: - $(ODIN) test test_128.odin $(COMMON) - -string_compare_test: - $(ODIN) test test_string_compare.odin $(COMMON) - -asan_test: - $(ODIN) test test_asan.odin $(COMMON) -sanitize:address -debug diff --git a/tests/internal/build.bat b/tests/internal/build.bat deleted file mode 100644 index edaa2684b..000000000 --- a/tests/internal/build.bat +++ /dev/null @@ -1,9 +0,0 @@ -@echo off -set PATH_TO_ODIN==..\..\odin -set COMMON=-file -vet -strict-style -o:minimal -%PATH_TO_ODIN% test test_rtti.odin %COMMON% || exit /b -%PATH_TO_ODIN% test test_map.odin %COMMON% || exit /b -%PATH_TO_ODIN% test test_pow.odin %COMMON% || exit /b -%PATH_TO_ODIN% test test_asan.odin %COMMON% || exit /b -%PATH_TO_ODIN% test test_128.odin %COMMON% || exit /b -%PATH_TO_ODIN% test test_string_compare.odin %COMMON% || exit /b \ No newline at end of file diff --git a/tests/internal/test_128.odin b/tests/internal/test_128.odin index 1aa7487b6..7b7d655e8 100644 --- a/tests/internal/test_128.odin +++ b/tests/internal/test_128.odin @@ -1,4 +1,4 @@ -package test_128 +package test_internal import "core:testing" diff --git a/tests/internal/test_asan.odin b/tests/internal/test_asan.odin index 66f3c6547..1ac599acf 100644 --- a/tests/internal/test_asan.odin +++ b/tests/internal/test_asan.odin @@ -1,5 +1,5 @@ // Intended to contain code that would trigger asan easily if the abi was set up badly. -package test_asan +package test_internal import "core:testing" diff --git a/tests/internal/test_map.odin b/tests/internal/test_map.odin index fa13244ac..a9a8cf5d4 100644 --- a/tests/internal/test_map.odin +++ b/tests/internal/test_map.odin @@ -1,4 +1,4 @@ -package test_internal_map +package test_internal import "core:log" import "base:intrinsics" @@ -309,4 +309,4 @@ set_delete_random_key_value :: proc(t: ^testing.T) { } seed_incr += 1 } -} \ No newline at end of file +} diff --git a/tests/internal/test_pow.odin b/tests/internal/test_pow.odin index 609087fb2..d1939ace5 100644 --- a/tests/internal/test_pow.odin +++ b/tests/internal/test_pow.odin @@ -1,4 +1,4 @@ -package test_internal_math_pow +package test_internal @(require) import "core:log" import "core:math" @@ -42,4 +42,4 @@ pow_test :: proc(t: ^testing.T) { testing.expectf(t, _v1 == _v2, "Expected math.pow2_f16(%d) == math.pow(2, %d) (= %04x), got %04x", exp, exp, _v1, _v2) } } -} \ No newline at end of file +} diff --git a/tests/internal/test_rtti.odin b/tests/internal/test_rtti.odin index ec5508208..e72c107b2 100644 --- a/tests/internal/test_rtti.odin +++ b/tests/internal/test_rtti.odin @@ -1,4 +1,4 @@ -package test_internal_rtti +package test_internal import "core:fmt" import "core:testing" @@ -43,4 +43,4 @@ rtti_test :: proc(t: ^testing.T) { l_s := fmt.tprintf("%s", l_buggy) testing.expectf(t, g_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", g_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, g_s) testing.expectf(t, l_s == EXPECTED_REPR, "Expected fmt.tprintf(\"%%s\", l_s)) to return \"%v\", got \"%v\"", EXPECTED_REPR, l_s) -} \ No newline at end of file +} diff --git a/tests/internal/test_string_compare.odin b/tests/internal/test_string_compare.odin index 63b62cc96..32a93ddf5 100644 --- a/tests/internal/test_string_compare.odin +++ b/tests/internal/test_string_compare.odin @@ -1,4 +1,4 @@ -package test_internal_string_compare +package test_internal import "core:testing" @@ -54,4 +54,4 @@ string_compare :: proc(t: ^testing.T) { } } } -} \ No newline at end of file +} diff --git a/tests/issues/run.bat b/tests/issues/run.bat index f3e3daba9..299e08791 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -10,7 +10,6 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style ..\..\..\odin test ..\test_issue_829.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_1592.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_2056.odin %COMMON% || exit /b -..\..\..\odin test ..\test_issue_2087.odin %COMMON% || exit /b ..\..\..\odin build ..\test_issue_2113.odin %COMMON% -debug || exit /b ..\..\..\odin test ..\test_issue_2466.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_2615.odin %COMMON% || exit /b diff --git a/tests/issues/run.sh b/tests/issues/run.sh index a5280485e..e17cb4c73 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -13,7 +13,6 @@ set -x $ODIN test ../test_issue_829.odin $COMMON $ODIN test ../test_issue_1592.odin $COMMON $ODIN test ../test_issue_2056.odin $COMMON -$ODIN test ../test_issue_2087.odin $COMMON $ODIN build ../test_issue_2113.odin $COMMON -debug $ODIN test ../test_issue_2466.odin $COMMON $ODIN test ../test_issue_2615.odin $COMMON diff --git a/tests/vendor/Makefile b/tests/vendor/Makefile deleted file mode 100644 index 7d6b84978..000000000 --- a/tests/vendor/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -ODIN=../../odin -ODINFLAGS= - -OS=$(shell uname) - -ifeq ($(OS), OpenBSD) - ODINFLAGS:=$(ODINFLAGS) -extra-linker-flags:-L/usr/local/lib -endif - -all: diff --git a/tests/vendor/all.odin b/tests/vendor/all.odin new file mode 100644 index 000000000..1ce56e786 --- /dev/null +++ b/tests/vendor/all.odin @@ -0,0 +1,3 @@ +package tests_vendor + +@(require) import "glfw" diff --git a/tests/vendor/build.bat b/tests/vendor/build.bat deleted file mode 100644 index 84ab2f1ef..000000000 --- a/tests/vendor/build.bat +++ /dev/null @@ -1,8 +0,0 @@ -@echo off -set COMMON=-show-timings -no-bounds-check -vet -strict-style -define:ODIN_TEST_FANCY=false -set PATH_TO_ODIN==..\..\odin - -echo --- -echo Running vendor:glfw tests -echo --- -%PATH_TO_ODIN% test glfw %COMMON% -out:vendor_glfw.exe || exit /b \ No newline at end of file diff --git a/tests/vendor/glfw/test_vendor_glfw.odin b/tests/vendor/glfw/test_vendor_glfw.odin index 4254ebb09..8a7fb0d0a 100644 --- a/tests/vendor/glfw/test_vendor_glfw.odin +++ b/tests/vendor/glfw/test_vendor_glfw.odin @@ -1,3 +1,4 @@ +//+build darwin, windows package test_vendor_glfw import "core:testing" From 9122c20d4b4e1d9fbd8833d47ceec164170a73f5 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 7 Jun 2024 15:57:09 +0200 Subject: [PATCH 198/270] update actions/checkout --- .github/workflows/ci.yml | 10 +++++----- .github/workflows/nightly.yml | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0aa6a7277..66c848a8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: name: ${{ matrix.os == 'macos-14' && 'MacOS ARM' || (matrix.os == 'macos-13' && 'MacOS Intel' || 'Ubuntu') }} Build, Check, and Test timeout-minutes: 15 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Download LLVM (Linux) if: matrix.os == 'ubuntu-latest' @@ -79,9 +79,9 @@ jobs: run: ./odin report - name: Compile needed Vendor run: | - make -C $(./odin root)/vendor/stb/src - make -C $(./odin root)/vendor/cgltf/src - make -C $(./odin root)/vendor/miniaudio/src + make -C vendor/stb/src + make -C vendor/cgltf/src + make -C vendor/miniaudio/src - name: Odin check run: ./odin check examples/demo -vet - name: Odin run @@ -123,7 +123,7 @@ jobs: runs-on: windows-2022 timeout-minutes: 15 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: build Odin shell: cmd run: | diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 8d8392f95..0857e99ad 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -11,7 +11,7 @@ jobs: if: github.repository == 'odin-lang/Odin' runs-on: windows-2022 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: build Odin shell: cmd run: | @@ -45,7 +45,7 @@ jobs: if: github.repository == 'odin-lang/Odin' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: (Linux) Download LLVM run: | wget https://apt.llvm.org/llvm.sh @@ -79,7 +79,7 @@ jobs: if: github.repository == 'odin-lang/Odin' runs-on: macos-13 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Download LLVM and setup PATH run: | brew install llvm@17 dylibbundler @@ -113,7 +113,7 @@ jobs: if: github.repository == 'odin-lang/Odin' runs-on: macos-14 # ARM machine steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: Download LLVM and setup PATH run: | brew install llvm@17 dylibbundler @@ -146,7 +146,7 @@ jobs: runs-on: [ubuntu-latest] needs: [build_windows, build_macos, build_macos_arm, build_ubuntu] steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - uses: actions/setup-python@v2 with: python-version: '3.8.x' @@ -193,4 +193,4 @@ jobs: python3 ci/nightly.py artifact macos-amd64 macos_artifacts/dist.zip python3 ci/nightly.py artifact macos-arm64 macos_arm_artifacts/dist.zip python3 ci/nightly.py prune - python3 ci/nightly.py json \ No newline at end of file + python3 ci/nightly.py json From 29250f2657f644e766dba09dcffdf594f54554f5 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 7 Jun 2024 16:33:38 +0200 Subject: [PATCH 199/270] fix regression in test_issue_2395 --- src/check_type.cpp | 2 +- tests/issues/run.sh | 5 ++--- tests/issues/test_issue_2395.odin | 2 -- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index 17f7813d5..c56c8a739 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -776,7 +776,7 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no } } if (variants.count < 2) { - error(ut->align, "A union with #no_nil must have at least 2 variants"); + error(node, "A union with #no_nil must have at least 2 variants"); } break; } diff --git a/tests/issues/run.sh b/tests/issues/run.sh index e17cb4c73..20259ac63 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -6,8 +6,6 @@ pushd build ODIN=../../../odin COMMON="-define:ODIN_TEST_FANCY=false -file -vet -strict-style" -NO_NIL_ERR="Error: " - set -x $ODIN test ../test_issue_829.odin $COMMON @@ -18,10 +16,11 @@ $ODIN test ../test_issue_2466.odin $COMMON $ODIN test ../test_issue_2615.odin $COMMON $ODIN test ../test_issue_2637.odin $COMMON $ODIN test ../test_issue_2666.odin $COMMON -if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "$NO_NIL_ERR") -eq 2 ]] ; then +if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "must have at least 2 variants") -eq 2 ]] ; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" + exit 1 fi set +x diff --git a/tests/issues/test_issue_2395.odin b/tests/issues/test_issue_2395.odin index 48e1ee516..bbbcb3aea 100644 --- a/tests/issues/test_issue_2395.odin +++ b/tests/issues/test_issue_2395.odin @@ -5,8 +5,6 @@ // exactly 2 errors from the invalid unions package test_issues -import "core:testing" - ValidUnion :: union($T: typeid) #no_nil { T, f32, From e627fcb0e66421d52526d844c01065ba0e043c5e Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 7 Jun 2024 16:41:26 +0200 Subject: [PATCH 200/270] fix not printing `Error:` when terminal has no color support --- src/error.cpp | 6 ++---- tests/issues/run.sh | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/error.cpp b/src/error.cpp index 2556a8de4..eed69b779 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -390,8 +390,6 @@ gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va error_out_empty(); } else { error_out_pos(pos); - } - if (has_ansi_terminal_colours()) { error_out_coloured("Error: ", TerminalStyle_Normal, TerminalColour_Red); } error_out_va(fmt, va); @@ -427,8 +425,8 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, error_out_empty(); } else { error_out_pos(pos); + error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); } - error_out_coloured("Warning: ", TerminalStyle_Normal, TerminalColour_Yellow); error_out_va(fmt, va); error_out("\n"); show_error_on_line(pos, end); @@ -841,4 +839,4 @@ gb_internal void print_all_errors(void) { gb_file_write(f, res, gb_string_length(res)); errors_already_printed = true; -} \ No newline at end of file +} diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 20259ac63..8b4c1e7f2 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -16,7 +16,7 @@ $ODIN test ../test_issue_2466.odin $COMMON $ODIN test ../test_issue_2615.odin $COMMON $ODIN test ../test_issue_2637.odin $COMMON $ODIN test ../test_issue_2666.odin $COMMON -if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "must have at least 2 variants") -eq 2 ]] ; then +if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]] ; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" From 072825ac5a1652de349eae20ccae0e67c23a843b Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 7 Jun 2024 17:40:34 +0200 Subject: [PATCH 201/270] add MacOS 14.5 to 'core:sys/info' and 'odin report' --- core/sys/info/platform_darwin.odin | 1 + src/bug_report.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/core/sys/info/platform_darwin.odin b/core/sys/info/platform_darwin.odin index 122dd42ee..0cae0aa98 100644 --- a/core/sys/info/platform_darwin.odin +++ b/core/sys/info/platform_darwin.odin @@ -527,6 +527,7 @@ macos_release_map: map[string]Darwin_To_Release = { "23D60" = {{23, 3, 0}, "macOS", {"Sonoma", {14, 3, 1}}}, "23E214" = {{23, 4, 0}, "macOS", {"Sonoma", {14, 4, 0}}}, "23E224" = {{23, 4, 0}, "macOS", {"Sonoma", {14, 4, 1}}}, + "23F79" = {{23, 5, 0}, "macOS", {"Sonoma", {14, 5, 0}}}, } @(private) diff --git a/src/bug_report.cpp b/src/bug_report.cpp index 1f754ce7c..dab8c4391 100644 --- a/src/bug_report.cpp +++ b/src/bug_report.cpp @@ -910,6 +910,7 @@ gb_internal void report_os_info() { {"23D60", {23, 3, 0}, "macOS", {"Sonoma", {14, 3, 1}}}, {"23E214", {23, 4, 0}, "macOS", {"Sonoma", {14, 4, 0}}}, {"23E224", {23, 4, 0}, "macOS", {"Sonoma", {14, 4, 1}}}, + {"23F79", {23, 5, 0}, "macOS", {"Sonoma", {14, 5, 0}}}, }; From b35e72c82b53be5ae16b1a0923ae948183a32a8d Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 7 Jun 2024 20:26:31 +0200 Subject: [PATCH 202/270] core/sync: fix wrong timeout calculation, `time.Duration` is ns already --- core/sync/futex_darwin.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sync/futex_darwin.odin b/core/sync/futex_darwin.odin index 6ea177d1b..fca9aadfe 100644 --- a/core/sync/futex_darwin.odin +++ b/core/sync/futex_darwin.odin @@ -52,7 +52,7 @@ _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expected: u32, durati } } else { - timeout_ns := u32(duration) * 1000 + timeout_ns := u32(duration) s := __ulock_wait(UL_COMPARE_AND_WAIT | ULF_NO_ERRNO, f, u64(expected), timeout_ns) if s >= 0 { return true From 00dfff7ee06d8a691ba754232243689e4bfd8381 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 7 Jun 2024 20:28:09 +0200 Subject: [PATCH 203/270] core/thread: fix a deadlock situation on unix --- core/thread/thread_unix.odin | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/core/thread/thread_unix.odin b/core/thread/thread_unix.odin index acc0e05cb..5291917da 100644 --- a/core/thread/thread_unix.odin +++ b/core/thread/thread_unix.odin @@ -2,11 +2,11 @@ // +private package thread -import "base:intrinsics" import "core:sync" import "core:sys/unix" +import "core:time" -CAS :: intrinsics.atomic_compare_exchange_strong +CAS :: sync.atomic_compare_exchange_strong // NOTE(tetra): Aligned here because of core/unix/pthread_linux.odin/pthread_t. // Also see core/sys/darwin/mach_darwin.odin/semaphore_t. @@ -32,11 +32,13 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { t.id = sync.current_thread_id() - for (.Started not_in t.flags) { - sync.wait(&t.cond, &t.mutex) + for (.Started not_in sync.atomic_load(&t.flags)) { + // HACK: use a timeout so in the event that the condition is signalled at THIS comment's exact point + // (after checking flags, before starting the wait) it gets itself out of that deadlock after a ms. + sync.wait_with_timeout(&t.cond, &t.mutex, time.Millisecond) } - if .Joined in t.flags { + if .Joined in sync.atomic_load(&t.flags) { return nil } @@ -60,11 +62,11 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { t.procedure(t) } - intrinsics.atomic_store(&t.flags, t.flags + { .Done }) + sync.atomic_or(&t.flags, { .Done }) sync.unlock(&t.mutex) - if .Self_Cleanup in t.flags { + if .Self_Cleanup in sync.atomic_load(&t.flags) { t.unix_thread = {} // NOTE(ftphikari): It doesn't matter which context 'free' received, right? context = {} @@ -122,13 +124,12 @@ _create :: proc(procedure: Thread_Proc, priority: Thread_Priority) -> ^Thread { } _start :: proc(t: ^Thread) { - // sync.guard(&t.mutex) - t.flags += { .Started } + sync.atomic_or(&t.flags, { .Started }) sync.signal(&t.cond) } _is_done :: proc(t: ^Thread) -> bool { - return .Done in intrinsics.atomic_load(&t.flags) + return .Done in sync.atomic_load(&t.flags) } _join :: proc(t: ^Thread) { @@ -139,7 +140,7 @@ _join :: proc(t: ^Thread) { } // Preserve other flags besides `.Joined`, like `.Started`. - unjoined := intrinsics.atomic_load(&t.flags) - {.Joined} + unjoined := sync.atomic_load(&t.flags) - {.Joined} joined := unjoined + {.Joined} // Try to set `t.flags` from unjoined to joined. If it returns joined, From 61c481bd8187925f3ceaf2269d8a479a91e8b763 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Fri, 7 Jun 2024 15:27:43 -0400 Subject: [PATCH 204/270] Fix `%p` pointers not printing leading `0x` --- core/fmt/fmt.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index f43683d11..f9113a7a7 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -1495,7 +1495,7 @@ fmt_pointer :: proc(fi: ^Info, p: rawptr, verb: rune) { u := u64(uintptr(p)) switch verb { case 'p', 'v', 'w': - if !fi.hash && verb == 'v' { + if !fi.hash { io.write_string(fi.writer, "0x", &fi.n) } _fmt_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER) From b65589d036da3c45e5ebbdafc007f308ccb09e40 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:01:03 -0400 Subject: [PATCH 205/270] Add `%p` tests for `fmt` --- tests/core/fmt/test_core_fmt.odin | 39 ++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/core/fmt/test_core_fmt.odin b/tests/core/fmt/test_core_fmt.odin index 5053e788f..80ab6fb6e 100644 --- a/tests/core/fmt/test_core_fmt.odin +++ b/tests/core/fmt/test_core_fmt.odin @@ -221,8 +221,45 @@ test_fmt_python_syntax :: proc(t: ^testing.T) { check(t, "%!(MISSING CLOSE BRACE)%!(EXTRA 1)", "{0", 1 ) } +@(test) +test_pointers :: proc(t: ^testing.T) { + S :: struct { i: int } + a: rawptr + b: ^int + c: ^S + d: ^S = cast(^S)cast(uintptr)0xFFFF + + check(t, "0x0", "%p", a) + check(t, "0x0", "%p", b) + check(t, "0x0", "%p", c) + check(t, "0xFFFF", "%p", d) + + check(t, "0x0", "%#p", a) + check(t, "0x0", "%#p", b) + check(t, "0x0", "%#p", c) + check(t, "0xFFFF", "%#p", d) + + check(t, "0x0", "%v", a) + check(t, "0x0", "%v", b) + check(t, "", "%v", c) + + check(t, "0x0", "%#v", a) + check(t, "0x0", "%#v", b) + check(t, "", "%#v", c) + + check(t, "0x0000", "%4p", a) + check(t, "0x0000", "%4p", b) + check(t, "0x0000", "%4p", c) + check(t, "0xFFFF", "%4p", d) + + check(t, "0x0000", "%#4p", a) + check(t, "0x0000", "%#4p", b) + check(t, "0x0000", "%#4p", c) + check(t, "0xFFFF", "%#4p", d) +} + @(private) check :: proc(t: ^testing.T, exp: string, format: string, args: ..any) { got := fmt.tprintf(format, ..args) testing.expectf(t, got == exp, "(%q, %v): %q != %q", format, args, got, exp) -} \ No newline at end of file +} From affd48c791251dc1c57939fff0168e7e601e7e38 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:08:37 -0400 Subject: [PATCH 206/270] Add `#caller_location` to `check` in `fmt` tests This makes it much easier to track down which line failed. --- tests/core/fmt/test_core_fmt.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/fmt/test_core_fmt.odin b/tests/core/fmt/test_core_fmt.odin index 80ab6fb6e..507e0f433 100644 --- a/tests/core/fmt/test_core_fmt.odin +++ b/tests/core/fmt/test_core_fmt.odin @@ -259,7 +259,7 @@ test_pointers :: proc(t: ^testing.T) { } @(private) -check :: proc(t: ^testing.T, exp: string, format: string, args: ..any) { +check :: proc(t: ^testing.T, exp: string, format: string, args: ..any, loc := #caller_location) { got := fmt.tprintf(format, ..args) - testing.expectf(t, got == exp, "(%q, %v): %q != %q", format, args, got, exp) + testing.expectf(t, got == exp, "(%q, %v): %q != %q", format, args, got, exp, loc = loc) } From 323a6e0728b019d5e0aca30a24ed317bdc9354d2 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 7 Jun 2024 22:19:01 +0200 Subject: [PATCH 207/270] Nuke ancient comment. --- tests/core/image/test_core_image.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/core/image/test_core_image.odin b/tests/core/image/test_core_image.odin index 495950c80..857022b81 100644 --- a/tests/core/image/test_core_image.odin +++ b/tests/core/image/test_core_image.odin @@ -1472,7 +1472,6 @@ run_png_suite :: proc(t: ^testing.T, suite: []Test) { if err == nil { // No point in running the other tests if it didn't load. pixels := bytes.buffer_to_bytes(&img.pixels) - // This struct compare fails at -opt:2 if PNG_Dims is not #packed. dims := Dims{img.width, img.height, img.channels, img.depth} dims_pass := test.dims == dims testing.expectf(t, dims_pass, "%v has %v, expected: %v", file.file, dims, test.dims) From 371749d474ac510c8dcee210af79a00162bda212 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 8 Jun 2024 14:48:04 +0100 Subject: [PATCH 208/270] Make certain procedures `"contextless"` for microui --- vendor/microui/microui.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vendor/microui/microui.odin b/vendor/microui/microui.odin index e545b742b..08a96acf2 100644 --- a/vendor/microui/microui.odin +++ b/vendor/microui/microui.odin @@ -622,7 +622,7 @@ push_command :: proc(ctx: ^Context, $Type: typeid, extra_size := 0) -> ^Type { return cmd } -next_command :: proc(ctx: ^Context, pcmd: ^^Command) -> bool { +next_command :: proc "contextless" (ctx: ^Context, pcmd: ^^Command) -> bool { cmd := pcmd^ defer pcmd^ = cmd if cmd != nil { @@ -630,7 +630,7 @@ next_command :: proc(ctx: ^Context, pcmd: ^^Command) -> bool { } else { cmd = (^Command)(&ctx.command_list.items[0]) } - invalid_command :: #force_inline proc(ctx: ^Context) -> ^Command { + invalid_command :: #force_inline proc "contextless" (ctx: ^Context) -> ^Command { return (^Command)(&ctx.command_list.items[ctx.command_list.idx]) } for cmd != invalid_command(ctx) { @@ -643,7 +643,7 @@ next_command :: proc(ctx: ^Context, pcmd: ^^Command) -> bool { return false } -next_command_iterator :: proc(ctx: ^Context, pcm: ^^Command) -> (Command_Variant, bool) { +next_command_iterator :: proc "contextless" (ctx: ^Context, pcm: ^^Command) -> (Command_Variant, bool) { if next_command(ctx, pcm) { return pcm^.variant, true } From 35e57fdef8826685a2aeaebe9b2532cf0a44df8c Mon Sep 17 00:00:00 2001 From: John Leidegren Date: Sat, 8 Jun 2024 15:58:34 +0200 Subject: [PATCH 209/270] Fixed error handling in read_dir on Windows --- core/os/dir_windows.odin | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin index 491507313..df02f54e7 100644 --- a/core/os/dir_windows.odin +++ b/core/os/dir_windows.odin @@ -87,8 +87,12 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F find_data := &win32.WIN32_FIND_DATAW{} find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data) + if find_handle == win32.INVALID_HANDLE_VALUE { + e = Errno(win32.GetLastError()) + return + } defer win32.FindClose(find_handle) - for n != 0 && find_handle != nil { + for n != 0 { fi: File_Info fi = find_data_to_file_info(path, find_data) if fi.name != "" { From 7e994b6d216b091b90c47eba1b834bc7c690535c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 8 Jun 2024 15:42:19 +0100 Subject: [PATCH 210/270] Remove empty line preventing a suggestion from happening --- src/check_stmt.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index a1698bbfe..65bac36be 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1255,8 +1255,6 @@ gb_internal void check_switch_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags error_line("\t%.*s\n", LIT(f->token.string)); } } - error_line("\n"); - error_line("\tSuggestion: Was '#partial switch' wanted?\n"); } } From de7d3e248741b584ab2afb54322a8bb3e612a3f0 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sat, 8 Jun 2024 17:00:38 +0200 Subject: [PATCH 211/270] Update dir_windows.odin --- core/os/dir_windows.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/os/dir_windows.odin b/core/os/dir_windows.odin index df02f54e7..9ca78948e 100644 --- a/core/os/dir_windows.odin +++ b/core/os/dir_windows.odin @@ -88,8 +88,8 @@ read_dir :: proc(fd: Handle, n: int, allocator := context.allocator) -> (fi: []F find_data := &win32.WIN32_FIND_DATAW{} find_handle := win32.FindFirstFileW(raw_data(wpath_search), find_data) if find_handle == win32.INVALID_HANDLE_VALUE { - e = Errno(win32.GetLastError()) - return + err = Errno(win32.GetLastError()) + return dfi[:], err } defer win32.FindClose(find_handle) for n != 0 { From be0774acc8a67ac2f9e003764284bbda21d4ebcd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 8 Jun 2024 16:07:28 +0100 Subject: [PATCH 212/270] Add error message on return a constant slice value from a procedure --- src/check_stmt.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 65bac36be..c37c58cd6 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -2501,6 +2501,10 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { unsafe_return_error(o, "the address of an indexed variable", f->type); } } + } else if (o.mode == Addressing_Constant && is_type_slice(o.type)) { + ERROR_BLOCK(); + unsafe_return_error(o, "a compound literal of a slice"); + error_line("\tNote: A constant slice value will use the memory of the current stack frame\n"); } } From 5c8f78a25aaf517b6a4670551f3668a45b068327 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 8 Jun 2024 16:40:59 -0400 Subject: [PATCH 213/270] Add `*.bmp` to core tests `.gitignore` --- tests/core/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/core/.gitignore b/tests/core/.gitignore index cd013e188..2a4e21679 100644 --- a/tests/core/.gitignore +++ b/tests/core/.gitignore @@ -1,3 +1,4 @@ +*.bmp *.zip *.png math_big_test_library.* \ No newline at end of file From 49f147cc86d06ee60dd6936404b18f952196d9a8 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sat, 8 Jun 2024 17:05:00 -0400 Subject: [PATCH 214/270] Prevent panic when `swizzle` called with < 2 indices The requirement for at least 2 indices has been sourced from `lb_addr_swizzle` in `llvm_backend_general.cpp`, where there is an assert to ensure the swizzle_count is `1 < n <= 4`. --- src/check_builtin.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index eef925d94..98c695a2c 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -2419,6 +2419,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (arg_count > max_count) { error(call, "Too many 'swizzle' indices, %td > %td", arg_count, max_count); return false; + } else if (arg_count < 2) { + error(call, "Not enough 'swizzle' indices, %td < 2", arg_count); + return false; } if (type->kind == Type_Array) { From 9ad9236c3bc2d9ea538bd1e5050141c85e0732ab Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 9 Jun 2024 02:47:05 +0200 Subject: [PATCH 215/270] fix large ints amd64 sysv abi Fixes #3707 --- src/llvm_abi.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 85a16d321..1f7a39447 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -900,7 +900,15 @@ namespace lbAbiAmd64SysV { } switch (LLVMGetTypeKind(t)) { - case LLVMIntegerTypeKind: + case LLVMIntegerTypeKind: { + i64 s = t_size; + while (s > 0) { + unify(cls, ix + off/8, RegClass_Int); + off += 8; + s -= 8; + } + break; + } case LLVMPointerTypeKind: case LLVMHalfTypeKind: unify(cls, ix + off/8, RegClass_Int); From 3628154849ff8908d5acc765d887a399ce4324c2 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 9 Jun 2024 03:33:23 +0200 Subject: [PATCH 216/270] fix swizzle crash due to wrong alignment Fixes #3691 --- src/llvm_backend_general.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index e8183027f..ea98fc60a 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -1383,8 +1383,6 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { LLVMTypeRef vector_type = nullptr; if (lb_try_vector_cast(p->module, addr.addr, &vector_type)) { - LLVMSetAlignment(res.addr.value, cast(unsigned)lb_alignof(vector_type)); - LLVMValueRef vp = LLVMBuildPointerCast(p->builder, addr.addr.value, LLVMPointerType(vector_type, 0), ""); LLVMValueRef v = LLVMBuildLoad2(p->builder, vector_type, vp, ""); LLVMValueRef scalars[4] = {}; @@ -1394,6 +1392,8 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { LLVMValueRef mask = LLVMConstVector(scalars, addr.swizzle.count); LLVMValueRef sv = llvm_basic_shuffle(p, v, mask); + LLVMSetAlignment(res.addr.value, cast(unsigned)lb_alignof(LLVMTypeOf(sv))); + LLVMValueRef dst = LLVMBuildPointerCast(p->builder, ptr.value, LLVMPointerType(LLVMTypeOf(sv), 0), ""); LLVMBuildStore(p->builder, sv, dst); } else { From 6d862cc4e529b71e65356c73d44fc39c61cbbb77 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sun, 9 Jun 2024 04:43:19 +0200 Subject: [PATCH 217/270] fix unreachable hit when param and/or return have complex inits Fixes #3630 --- src/llvm_backend_proc.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 6cb1efab2..87f75fb1d 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -710,13 +710,12 @@ gb_internal void lb_begin_procedure_body(lbProcedure *p) { lb_set_debug_position_to_procedure_begin(p); if (p->debug_info != nullptr) { if (p->context_stack.count != 0) { + lbBlock *prev_block = p->curr_block; p->curr_block = p->decl_block; lb_add_debug_context_variable(p, lb_find_or_generate_context_ptr(p)); + p->curr_block = prev_block; } - } - - lb_start_block(p, p->entry_block); } gb_internal void lb_end_procedure_body(lbProcedure *p) { From e0d0dc704ce69e079d70ad2e98c3ae21cce56379 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 9 Jun 2024 13:20:48 +0100 Subject: [PATCH 218/270] Make `f32(u8)` etc do an immediate cast to `f32(u32(u8))` in code generation --- src/llvm_backend_expr.cpp | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 36af60e46..98e773ddb 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -1873,13 +1873,40 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { lbValue res_i128 = lb_emit_runtime_call(p, call, args); return lb_emit_conv(p, res_i128, t); } + i64 sz = type_size_of(src); lbValue res = {}; res.type = t; if (is_type_unsigned(dst)) { - res.value = LLVMBuildFPToUI(p->builder, value.value, lb_type(m, t), ""); + switch (sz) { + case 2: + case 4: + res.value = LLVMBuildFPToUI(p->builder, value.value, lb_type(m, t_u32), ""); + res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), false, ""); + break; + case 8: + res.value = LLVMBuildFPToUI(p->builder, value.value, lb_type(m, t_u64), ""); + res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), false, ""); + break; + default: + GB_PANIC("Unhandled float type"); + break; + } } else { - res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t), ""); + switch (sz) { + case 2: + case 4: + res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t_i32), ""); + res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), false, ""); + break; + case 8: + res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t_i64), ""); + res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), false, ""); + break; + default: + GB_PANIC("Unhandled float type"); + break; + } } return res; } From 8fcfd8c506752d3e0d65e4d9ef7856486a65ca5f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 9 Jun 2024 13:21:22 +0100 Subject: [PATCH 219/270] Fix sign flag --- src/llvm_backend_expr.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 98e773ddb..a23f8cfbe 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -1897,11 +1897,11 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { case 2: case 4: res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t_i32), ""); - res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), false, ""); + res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), true, ""); break; case 8: res.value = LLVMBuildFPToSI(p->builder, value.value, lb_type(m, t_i64), ""); - res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), false, ""); + res.value = LLVMBuildIntCast2(p->builder, res.value, lb_type(m, t), true, ""); break; default: GB_PANIC("Unhandled float type"); From 4b52f7fe2bd68d74efffa55ef8b4582164c1e268 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 9 Jun 2024 13:35:22 +0100 Subject: [PATCH 220/270] Fix #3713 --- src/check_expr.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index ad546858c..4115cd1e9 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3336,11 +3336,12 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type) { } if (is_type_untyped(x->type)) { - Type *final_type = type; - if (is_const_expr && !is_type_constant_type(type)) { - final_type = default_type(x->type); - } - update_untyped_expr_type(c, x->expr, final_type, true); + convert_to_typed(c, x, type); + // Type *final_type = type; + // if (is_const_expr && !is_type_constant_type(type)) { + // final_type = default_type(x->type); + // } + // update_untyped_expr_type(c, x->expr, final_type, true); } else { Type *src = core_type(x->type); Type *dst = core_type(type); @@ -4286,7 +4287,8 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar } else { switch (operand->type->Basic.kind) { case Basic_UntypedBool: - if (!is_type_boolean(target_type)) { + if (!is_type_boolean(target_type) && + !is_type_integer(target_type)) { operand->mode = Addressing_Invalid; convert_untyped_error(c, operand, target_type); return; @@ -7527,9 +7529,6 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } operand->type = t; operand->expr = call; - if (operand->mode != Addressing_Invalid) { - update_untyped_expr_type(c, arg, t, false); - } break; } } From c17981ac38ace6cac12463029273d16068bf23c3 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 9 Jun 2024 14:02:01 +0100 Subject: [PATCH 221/270] Add edge case to `error_operand_no_value` --- src/check_expr.cpp | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 4115cd1e9..1e6d6e2ab 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -281,8 +281,20 @@ gb_internal void error_operand_not_expression(Operand *o) { gb_internal void error_operand_no_value(Operand *o) { if (o->mode == Addressing_NoValue) { - gbString err = expr_to_string(o->expr); Ast *x = unparen_expr(o->expr); + + if (x->kind == Ast_CallExpr) { + Ast *p = unparen_expr(x->CallExpr.proc); + if (p->kind == Ast_BasicDirective) { + String tag = p->BasicDirective.name.string; + if (tag == "panic" || + tag == "assert") { + return; + } + } + } + + gbString err = expr_to_string(o->expr); if (x->kind == Ast_CallExpr) { error(o->expr, "'%s' call does not return a value and cannot be used as a value", err); } else { @@ -3336,12 +3348,11 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type) { } if (is_type_untyped(x->type)) { - convert_to_typed(c, x, type); - // Type *final_type = type; - // if (is_const_expr && !is_type_constant_type(type)) { - // final_type = default_type(x->type); - // } - // update_untyped_expr_type(c, x->expr, final_type, true); + Type *final_type = type; + if (is_const_expr && !is_type_constant_type(type)) { + final_type = default_type(x->type); + } + update_untyped_expr_type(c, x->expr, final_type, true); } else { Type *src = core_type(x->type); Type *dst = core_type(type); @@ -4287,8 +4298,7 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar } else { switch (operand->type->Basic.kind) { case Basic_UntypedBool: - if (!is_type_boolean(target_type) && - !is_type_integer(target_type)) { + if (!is_type_boolean(target_type)) { operand->mode = Addressing_Invalid; convert_untyped_error(c, operand, target_type); return; @@ -7529,6 +7539,9 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } operand->type = t; operand->expr = call; + if (operand->mode != Addressing_Invalid) { + update_untyped_expr_type(c, arg, t, false); + } break; } } @@ -8336,7 +8349,7 @@ gb_internal ExprKind check_basic_directive_expr(CheckerContext *c, Operand *o, A name == "assert" || name == "defined" || name == "config" || - name == "exists" || + name == "exists" || name == "load" || name == "load_hash" || name == "load_directory" || From ef7c6b98951aacc72bd25750e1c4a3a8c30c7d06 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 9 Jun 2024 14:07:07 +0100 Subject: [PATCH 222/270] Re Fix #3713 --- src/check_expr.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 1e6d6e2ab..13d0e277d 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3350,6 +3350,9 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type) { if (is_type_untyped(x->type)) { Type *final_type = type; if (is_const_expr && !is_type_constant_type(type)) { + if (is_type_union(type)) { + convert_to_typed(c, x, type); + } final_type = default_type(x->type); } update_untyped_expr_type(c, x->expr, final_type, true); @@ -4298,7 +4301,8 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar } else { switch (operand->type->Basic.kind) { case Basic_UntypedBool: - if (!is_type_boolean(target_type)) { + if (!is_type_boolean(target_type) && + !is_type_integer(target_type)) { operand->mode = Addressing_Invalid; convert_untyped_error(c, operand, target_type); return; From d2a2c1e74e62ab3fdb11d93f4ef8f74e4727bcda Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 9 Jun 2024 16:10:06 +0200 Subject: [PATCH 223/270] Image: Add improved blending method and test it. --- core/image/common.odin | 17 +++++++++++++ core/image/png/png.odin | 20 +++++++-------- tests/core/image/test_core_image.odin | 36 +++++++++++++++++++++++++++ tests/core/normal.odin | 1 - tests/core/speed.odin | 1 + 5 files changed, 63 insertions(+), 12 deletions(-) diff --git a/core/image/common.odin b/core/image/common.odin index fe75371b3..15c5bbbce 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -42,6 +42,23 @@ GA_Pixel :: [2]u8 G_Pixel_16 :: [1]u16 GA_Pixel_16 :: [2]u16 +blend_single_channel :: #force_inline proc(fg, alpha, bg: $T) -> (res: T) where T == u8 || T == u16 { + MAX :: 256 when T == u8 else 65536 + + c := u32(fg) * (MAX - u32(alpha)) + u32(bg) * (1 + u32(alpha)) + return T(c & (MAX - 1)) +} + +blend_pixel :: #force_inline proc(fg: [$N]$T, alpha: T, bg: [N]T) -> (res: [N]T) where (T == u8 || T == u16), N >= 1 && N <= 4 { + MAX :: 256 when T == u8 else 65536 + + r_a := u32(fg) * (MAX - u32(alpha)) + u32(bg) * (1 + u32(alpha)) + + return +} + +blend :: proc{blend_single_channel, blend_pixel} + Image :: struct { width: int, height: int, diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 4bb070da8..9a91575a8 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -597,7 +597,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a dsc := depth_scale_table scale := dsc[info.header.bit_depth] if scale != 1 { - key := mem.slice_data_cast([]u16be, c.data)[0] * u16be(scale) + key := (^u16be)(raw_data(c.data))^ * u16be(scale) c.data = []u8{0, u8(key & 255)} } } @@ -762,19 +762,18 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a for w := 0; w < int(img.width); w += 1 { index := temp.buf[i] - c := _plte.entries[index] - a := int(index) < len(trns.data) ? trns.data[index] : 255 - alpha := f32(a) / 255.0 + c := _plte.entries[index] + a := int(index) < len(trns.data) ? trns.data[index] : 255 if blend_background { - c.r = u8((1.0 - alpha) * bg[0] + f32(c.r) * alpha) - c.g = u8((1.0 - alpha) * bg[1] + f32(c.g) * alpha) - c.b = u8((1.0 - alpha) * bg[2] + f32(c.b) * alpha) + c.r = image.blend(c.r, a, u8(u32(bg.r))) + c.g = image.blend(c.g, a, u8(u32(bg.g))) + c.b = image.blend(c.b, a, u8(u32(bg.b))) a = 255 } else if premultiply { - c.r = u8(f32(c.r) * alpha) - c.g = u8(f32(c.g) * alpha) - c.b = u8(f32(c.b) * alpha) + c.r = image.blend(u8(0), a, c.r) + c.g = image.blend(u8(0), a, c.g) + c.b = image.blend(u8(0), a, c.b) } t.buf[j ] = c.r @@ -1627,7 +1626,6 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH return nil } - @(init, private) _register :: proc() { image.register(.PNG, load_from_bytes, destroy) diff --git a/tests/core/image/test_core_image.odin b/tests/core/image/test_core_image.odin index 857022b81..0cd118497 100644 --- a/tests/core/image/test_core_image.odin +++ b/tests/core/image/test_core_image.odin @@ -2358,4 +2358,40 @@ run_bmp_suite :: proc(t: ^testing.T, suite: []Test) { } } return +} + +@test +will_it_blend :: proc(t: ^testing.T) { + Pixel :: image.RGB_Pixel + Pixel_16 :: image.RGB_Pixel_16 + + { + bg := Pixel{255, 255, 0} + fg := Pixel{ 0, 0, 255} + + for a in 0..=255 { + blended := Pixel{ + image.blend(fg.r, u8(a), bg.r), + image.blend(fg.g, u8(a), bg.g), + image.blend(fg.b, u8(a), bg.b), + } + testing.expectf(t, blended.r == bg.r - u8(a), "Expected blend(%v, %3d, %v) = %v, got %v", fg.r, a, bg.r, bg.r - u8(a), blended.r) + testing.expectf(t, blended.b == 255 - blended.r, "Expected blend(%v, %3d, %v) = %v, got %v", fg.b, a, bg.b, 255 - blended.r, blended.b) + } + } + + { + bg := Pixel_16{65535, 65535, 0} + fg := Pixel_16{ 0, 0, 65535} + + for a in 0..=65535 { + blended := Pixel_16{ + image.blend(fg.r, u16(a), bg.r), + image.blend(fg.g, u16(a), bg.g), + image.blend(fg.b, u16(a), bg.b), + } + testing.expectf(t, blended.r == bg.r - u16(a), "Expected blend(%v, %3d, %v) = %v, got %v", fg.r, a, bg.r, bg.r - u16(a), blended.r) + testing.expectf(t, blended.b == 65535 - blended.r, "Expected blend(%v, %3d, %v) = %v, got %v", fg.b, a, bg.b, 65535 - blended.r, blended.b) + } + } } \ No newline at end of file diff --git a/tests/core/normal.odin b/tests/core/normal.odin index f23763a59..29f4e9b80 100644 --- a/tests/core/normal.odin +++ b/tests/core/normal.odin @@ -20,7 +20,6 @@ download_assets :: proc() { @(require) import "encoding/varint" @(require) import "encoding/xml" @(require) import "fmt" -@(require) import "image" @(require) import "math" @(require) import "math/big" @(require) import "math/linalg/glsl" diff --git a/tests/core/speed.odin b/tests/core/speed.odin index 555d30f5e..a4b2b6a69 100644 --- a/tests/core/speed.odin +++ b/tests/core/speed.odin @@ -3,3 +3,4 @@ package tests_core @(require) import "crypto" @(require) import "hash" +@(require) import "image" \ No newline at end of file From 6b88d0a8206e78f2bf84bba39dc2261f906d0a58 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 9 Jun 2024 16:37:27 +0200 Subject: [PATCH 224/270] Use new blend helper --- core/image/common.odin | 53 +++++++++++++++++++++++++++-------------- core/image/png/png.odin | 16 +++++-------- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/core/image/common.odin b/core/image/common.odin index 15c5bbbce..fed2c1470 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -42,23 +42,6 @@ GA_Pixel :: [2]u8 G_Pixel_16 :: [1]u16 GA_Pixel_16 :: [2]u16 -blend_single_channel :: #force_inline proc(fg, alpha, bg: $T) -> (res: T) where T == u8 || T == u16 { - MAX :: 256 when T == u8 else 65536 - - c := u32(fg) * (MAX - u32(alpha)) + u32(bg) * (1 + u32(alpha)) - return T(c & (MAX - 1)) -} - -blend_pixel :: #force_inline proc(fg: [$N]$T, alpha: T, bg: [N]T) -> (res: [N]T) where (T == u8 || T == u16), N >= 1 && N <= 4 { - MAX :: 256 when T == u8 else 65536 - - r_a := u32(fg) * (MAX - u32(alpha)) + u32(bg) * (1 + u32(alpha)) - - return -} - -blend :: proc{blend_single_channel, blend_pixel} - Image :: struct { width: int, height: int, @@ -1276,6 +1259,40 @@ apply_palette_rgba :: proc(img: ^Image, palette: [256]RGBA_Pixel, allocator := c } apply_palette :: proc{apply_palette_rgb, apply_palette_rgba} +blend_single_channel :: #force_inline proc(fg, alpha, bg: $T) -> (res: T) where T == u8 || T == u16 { + MAX :: 256 when T == u8 else 65536 + + c := u32(fg) * (MAX - u32(alpha)) + u32(bg) * (1 + u32(alpha)) + return T(c & (MAX - 1)) +} + +blend_pixel :: #force_inline proc(fg: [$N]$T, alpha: T, bg: [N]T) -> (res: [N]T) where (T == u8 || T == u16), N >= 1 && N <= 4 { + MAX :: 256 when T == u8 else 65536 + + when N == 1 { + r := u32(fg.r) * (MAX - u32(alpha)) + u32(bg.r) * (1 + u32(alpha)) + return {T(r & (MAX - 1))} + } + when N == 2 { + r := u32(fg.r) * (MAX - u32(alpha)) + u32(bg.r) * (1 + u32(alpha)) + g := u32(fg.g) * (MAX - u32(alpha)) + u32(bg.g) * (1 + u32(alpha)) + return {T(r & (MAX - 1)), T(g & (MAX - 1))} + } + when N == 3 || N == 4 { + r := u32(fg.r) * (MAX - u32(alpha)) + u32(bg.r) * (1 + u32(alpha)) + g := u32(fg.g) * (MAX - u32(alpha)) + u32(bg.g) * (1 + u32(alpha)) + b := u32(fg.b) * (MAX - u32(alpha)) + u32(bg.b) * (1 + u32(alpha)) + + when N == 3 { + return {T(r & (MAX - 1)), T(g & (MAX - 1)), T(b & (MAX - 1))} + } else { + return {T(r & (MAX - 1)), T(g & (MAX - 1)), T(b & (MAX - 1)), MAX - 1} + } + } + unreachable() +} +blend :: proc{blend_single_channel, blend_pixel} + // Replicates grayscale values into RGB(A) 8- or 16-bit images as appropriate. // Returns early with `false` if already an RGB(A) image. @@ -1388,4 +1405,4 @@ write_bytes :: proc(buf: ^bytes.Buffer, data: []u8) -> (err: compress.General_Er return .Resize_Failed } return nil -} +} \ No newline at end of file diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 9a91575a8..56a939682 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -749,10 +749,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } } } else if add_alpha || .alpha_drop_if_present in options { - bg := [3]f32{0, 0, 0} + bg := PLTE_Entry{0, 0, 0} if premultiply && seen_bkgd { c16 := img.background.([3]u16) - bg = [3]f32{f32(c16.r), f32(c16.g), f32(c16.b)} + bg = {u8(c16.r), u8(c16.g), u8(c16.b)} } no_alpha := (.alpha_drop_if_present in options || premultiply) && .alpha_add_if_missing not_in options @@ -766,14 +766,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a a := int(index) < len(trns.data) ? trns.data[index] : 255 if blend_background { - c.r = image.blend(c.r, a, u8(u32(bg.r))) - c.g = image.blend(c.g, a, u8(u32(bg.g))) - c.b = image.blend(c.b, a, u8(u32(bg.b))) + c = image.blend(c, a, bg) a = 255 } else if premultiply { - c.r = image.blend(u8(0), a, c.r) - c.g = image.blend(u8(0), a, c.g) - c.b = image.blend(u8(0), a, c.b) + c = image.blend(PLTE_Entry{}, a, c) } t.buf[j ] = c.r @@ -1014,8 +1010,8 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a return {}, .Unable_To_Allocate_Or_Resize } - p := mem.slice_data_cast([]u8, temp.buf[:]) - o := mem.slice_data_cast([]u8, t.buf[:]) + p := temp.buf[:] + o := t.buf[:] switch raw_image_channels { case 1: From 5be7d8e32d7f299c74df39368395a0505b39ab13 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 9 Jun 2024 17:59:59 +0200 Subject: [PATCH 225/270] Clean up PNG code. --- core/image/png/png.odin | 46 ++++++++++++--------------- tests/core/image/test_core_image.odin | 1 - 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 56a939682..aa1c5f781 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -735,18 +735,11 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a return {}, .Unable_To_Allocate_Or_Resize } - i := 0; j := 0 - // If we don't have transparency or drop it without applying it, we can do this: if (!seen_trns || (seen_trns && .alpha_drop_if_present in options && .alpha_premultiply not_in options)) && .alpha_add_if_missing not_in options { - for h := 0; h < int(img.height); h += 1 { - for w := 0; w < int(img.width); w += 1 { - c := _plte.entries[temp.buf[i]] - t.buf[j ] = c.r - t.buf[j+1] = c.g - t.buf[j+2] = c.b - i += 1; j += 3 - } + output := mem.slice_data_cast([]image.RGB_Pixel, t.buf[:]) + for pal_idx, idx in temp.buf { + output[idx] = _plte.entries[pal_idx] } } else if add_alpha || .alpha_drop_if_present in options { bg := PLTE_Entry{0, 0, 0} @@ -758,12 +751,23 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a no_alpha := (.alpha_drop_if_present in options || premultiply) && .alpha_add_if_missing not_in options blend_background := seen_bkgd && .blend_background in options - for h := 0; h < int(img.height); h += 1 { - for w := 0; w < int(img.width); w += 1 { - index := temp.buf[i] + if no_alpha { + output := mem.slice_data_cast([]image.RGB_Pixel, t.buf[:]) + for orig, idx in temp.buf { + c := _plte.entries[orig] + a := int(orig) < len(trns.data) ? trns.data[orig] : 255 - c := _plte.entries[index] - a := int(index) < len(trns.data) ? trns.data[index] : 255 + if blend_background { + output[idx] = image.blend(c, a, bg) + } else if premultiply { + output[idx] = image.blend(PLTE_Entry{}, a, c) + } + } + } else { + output := mem.slice_data_cast([]image.RGBA_Pixel, t.buf[:]) + for orig, idx in temp.buf { + c := _plte.entries[orig] + a := int(orig) < len(trns.data) ? trns.data[orig] : 255 if blend_background { c = image.blend(c, a, bg) @@ -772,17 +776,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a c = image.blend(PLTE_Entry{}, a, c) } - t.buf[j ] = c.r - t.buf[j+1] = c.g - t.buf[j+2] = c.b - i += 1 - - if no_alpha { - j += 3 - } else { - t.buf[j+3] = u8(a) - j += 4 - } + output[idx] = {c.r, c.g, c.b, u8(a)} } } } else { diff --git a/tests/core/image/test_core_image.odin b/tests/core/image/test_core_image.odin index 0cd118497..899596229 100644 --- a/tests/core/image/test_core_image.odin +++ b/tests/core/image/test_core_image.odin @@ -1479,7 +1479,6 @@ run_png_suite :: proc(t: ^testing.T, suite: []Test) { png_hash := hash.crc32(pixels) testing.expectf(t, test.hash == png_hash, "%v test %v hash is %08x, expected %08x with %v", file.file, count, png_hash, test.hash, test.options) - passed &= test.hash == png_hash if passed { From 828870004bb84d3a23c4ebe6c33acad086036a9c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 9 Jun 2024 17:15:08 +0100 Subject: [PATCH 226/270] Change indentation --- base/runtime/wasm_allocator.odin | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/base/runtime/wasm_allocator.odin b/base/runtime/wasm_allocator.odin index acfc80b0a..6bca0b3d6 100644 --- a/base/runtime/wasm_allocator.odin +++ b/base/runtime/wasm_allocator.odin @@ -7,20 +7,20 @@ import "base:intrinsics" Port of emmalloc, modified for use in Odin. Invariants: - - Per-allocation header overhead is 8 bytes, smallest allocated payload - amount is 8 bytes, and a multiple of 4 bytes. - - Acquired memory blocks are subdivided into disjoint regions that lie - next to each other. - - A region is either in used or free. - Used regions may be adjacent, and a used and unused region - may be adjacent, but not two unused ones - they would be - merged. - - Memory allocation takes constant time, unless the alloc needs to wasm_memory_grow() - or memory is very close to being exhausted. - - Free and used regions are managed inside "root regions", which are slabs - of memory acquired via wasm_memory_grow(). - - Memory retrieved using wasm_memory_grow() can not be given back to the OS. - Therefore, frees are internal to the allocator. + - Per-allocation header overhead is 8 bytes, smallest allocated payload + amount is 8 bytes, and a multiple of 4 bytes. + - Acquired memory blocks are subdivided into disjoint regions that lie + next to each other. + - A region is either in used or free. + Used regions may be adjacent, and a used and unused region + may be adjacent, but not two unused ones - they would be + merged. + - Memory allocation takes constant time, unless the alloc needs to wasm_memory_grow() + or memory is very close to being exhausted. + - Free and used regions are managed inside "root regions", which are slabs + of memory acquired via wasm_memory_grow(). + - Memory retrieved using wasm_memory_grow() can not be given back to the OS. + Therefore, frees are internal to the allocator. Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file. From 7c529e990d815963df213145c1a5d6edecc969ad Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 9 Jun 2024 17:48:46 +0100 Subject: [PATCH 227/270] Add `-target:freestanding_arm32` (experimental) --- src/build_settings.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 251dd06dd..dc11a5fd2 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1137,6 +1137,14 @@ gb_global TargetMetrics target_freestanding_arm64 = { str_lit("aarch64-none-elf"), }; +gb_global TargetMetrics target_freestanding_arm32 = { + TargetOs_freestanding, + TargetArch_arm32, + 4, 4, 4, 8, + str_lit("arm-unknown-unknown-gnueabihf"), +}; + + struct NamedTargetMetrics { String name; TargetMetrics *metrics; @@ -1179,6 +1187,7 @@ gb_global NamedTargetMetrics named_targets[] = { { str_lit("freestanding_amd64_win64"), &target_freestanding_amd64_win64 }, { str_lit("freestanding_arm64"), &target_freestanding_arm64 }, + { str_lit("freestanding_arm32"), &target_freestanding_arm32 }, }; gb_global NamedTargetMetrics *selected_target_metrics; From 1f64d8d5bdd43396ed7cf441cc2abd4323491291 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 9 Jun 2024 15:24:32 -0400 Subject: [PATCH 228/270] Add `slice.permute` --- core/slice/permute.odin | 105 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 core/slice/permute.odin diff --git a/core/slice/permute.odin b/core/slice/permute.odin new file mode 100644 index 000000000..42b6d4129 --- /dev/null +++ b/core/slice/permute.odin @@ -0,0 +1,105 @@ +package slice + +import "base:runtime" + +// An in-place permutation iterator. +Permutation_Iterator :: struct($T: typeid) { + index: int, + slice: []T, + counters: []int, +} + +/* +Make an iterator to permute a slice in-place. + +*Allocates Using Provided Allocator* + +This procedure allocates some state to assist in permutation and does not make +a copy of the underlying slice. If you want to permute a slice without altering +the underlying data, use `clone` to create a copy, then permute that instead. + +Inputs: +- slice: The slice to permute. +- allocator: (default is context.allocator) + +Returns: +- iter: The iterator, to be passed to `permute`. +- error: An `Allocator_Error`, if allocation failed. +*/ +make_permutation_iterator :: proc( + slice: []$T, + allocator := context.allocator, +) -> ( + iter: Permutation_Iterator(T), + error: runtime.Allocator_Error, +) #optional_allocator_error { + iter.slice = slice + iter.counters = make([]int, len(iter.slice), allocator) or_return + + return +} +/* +Free the state allocated by `make_permutation_iterator`. + +Inputs: +- iter: The iterator created by `make_permutation_iterator`. +- allocator: The allocator used to create the iterator. (default is context.allocator) +*/ +destroy_permutation_iterator :: proc( + iter: Permutation_Iterator($T), + allocator := context.allocator, +) { + delete(iter.counters, allocator = allocator) +} +/* +Permute a slice in-place. + +Note that the first iteration will always be the original, unpermuted slice. + +Inputs: +- iter: The iterator created by `make_permutation_iterator`. + +Returns: +- ok: True if the permutation succeeded, false if the iteration is complete. +*/ +permute :: proc(iter: ^Permutation_Iterator($T)) -> (ok: bool) { + // This is an iterative, resumable implementation of Heap's algorithm. + // + // The original algorithm was described by B. R. Heap as "Permutations by + // interchanges" in The Computer Journal, 1963. + // + // This implementation is based on the nonrecursive version described by + // Robert Sedgewick in "Permutation Generation Methods" which was published + // in ACM Computing Surveys in 1977. + + i := iter.index + + if i == 0 { + iter.index = 1 + return true + } + + n := len(iter.counters) + #no_bounds_check for i < n { + if iter.counters[i] < i { + if i & 1 == 0 { + iter.slice[0], iter.slice[i] = iter.slice[i], iter.slice[0] + } else { + iter.slice[iter.counters[i]], iter.slice[i] = iter.slice[i], iter.slice[iter.counters[i]] + } + + iter.counters[i] += 1 + i = 1 + + break + } else { + iter.counters[i] = 0 + i += 1 + } + } + if i == n { + return false + } + iter.index = i + return true +} From 047b5058369641109951503a291cb3eb01c40a82 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 9 Jun 2024 16:09:45 -0400 Subject: [PATCH 229/270] Add test for `slice.permute` --- tests/core/slice/test_core_slice.odin | 32 ++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index 4a464503f..23de1b482 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -184,4 +184,34 @@ test_binary_search :: proc(t: ^testing.T) { index, found = slice.binary_search(b, 0) testing.expect(t, index == 0, "Expected index to be 0.") testing.expect(t, found == false, "Expected found to be false.") -} \ No newline at end of file +} + +@test +test_permutation_iterator :: proc(t: ^testing.T) { + // Big enough to do some sanity checking but not overly large. + FAC_5 :: 120 + s := []int{1, 2, 3, 4, 5} + seen: map[int]bool + defer delete(seen) + + iter := slice.make_permutation_iterator(s) + defer slice.destroy_permutation_iterator(iter) + + permutations_counted: int + for slice.permute(&iter) { + n := 0 + for item, index in s { + n *= 10 + n += item + } + if n in seen { + testing.fail_now(t, "Permutation iterator made a duplicate permutation.") + return + } + seen[n] = true + permutations_counted += 1 + } + + testing.expect_value(t, len(seen), FAC_5) + testing.expect_value(t, permutations_counted, FAC_5) +} From 5fbd876db14f77ddbd95c144d9b262474764c4cd Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 9 Jun 2024 20:29:09 -0400 Subject: [PATCH 230/270] Add permutation & combination procedures to `core:math/big` --- core/math/big/combinatorics.odin | 60 ++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 core/math/big/combinatorics.odin diff --git a/core/math/big/combinatorics.odin b/core/math/big/combinatorics.odin new file mode 100644 index 000000000..87c76d830 --- /dev/null +++ b/core/math/big/combinatorics.odin @@ -0,0 +1,60 @@ +package math_big + +/* + With `n` items, calculate how many ways that `r` of them can be ordered. +*/ +permutations_with_repetition :: int_pow_int + +/* + With `n` items, calculate how many ways that `r` of them can be ordered without any repeats. +*/ +permutations_without_repetition :: proc(dest: ^Int, n, r: int) -> (error: Error) { + if n == r { + return factorial(dest, n) + } + + tmp := &Int{} + defer internal_destroy(tmp) + + // n! + // -------- + // (n - r)! + factorial(dest, n) or_return + factorial(tmp, n - r) or_return + div(dest, dest, tmp) or_return + + return +} + +/* + With `n` items, calculate how many ways that `r` of them can be chosen. + + Also known as the multiset coefficient or (n multichoose k). +*/ +combinations_with_repetition :: proc(dest: ^Int, n, r: int) -> (error: Error) { + // (n + r - 1)! + // ------------ + // r! (n - 1)! + return combinations_without_repetition(dest, n + r - 1, r) +} + +/* + With `n` items, calculate how many ways that `r` of them can be chosen without any repeats. + + Also known as the binomial coefficient or (n choose k). +*/ +combinations_without_repetition :: proc(dest: ^Int, n, r: int) -> (error: Error) { + tmp_a, tmp_b := &Int{}, &Int{} + defer internal_destroy(tmp_a, tmp_b) + + // n! + // ------------ + // r! (n - r)! + factorial(dest, n) or_return + factorial(tmp_a, r) or_return + factorial(tmp_b, n - r) or_return + mul(tmp_a, tmp_a, tmp_b) or_return + div(dest, dest, tmp_a) or_return + + return +} From f0d65112b8f6a4ed70f437a8bfb3acd46e46d80f Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 9 Jun 2024 20:39:11 -0400 Subject: [PATCH 231/270] Rename `math/big` test package This is in line with the other tests, and it does not seem to affect building the library. --- tests/core/math/big/test.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/math/big/test.odin b/tests/core/math/big/test.odin index e0762a66d..f35f0b72b 100644 --- a/tests/core/math/big/test.odin +++ b/tests/core/math/big/test.odin @@ -8,7 +8,7 @@ This file exports procedures for use with the test.py test suite. */ -package math_big_tests +package test_core_math_big /* TODO: Write tests for `internal_*` and test reusing parameters with the public implementations. From fe5c278fcac16ac51257e47e35d898659b009164 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 9 Jun 2024 20:40:19 -0400 Subject: [PATCH 232/270] Add test for `core:math/big` permutation & combination procs --- tests/core/math/big/test_core_math_big.odin | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/core/math/big/test_core_math_big.odin diff --git a/tests/core/math/big/test_core_math_big.odin b/tests/core/math/big/test_core_math_big.odin new file mode 100644 index 000000000..9a1e7b01b --- /dev/null +++ b/tests/core/math/big/test_core_math_big.odin @@ -0,0 +1,37 @@ +package test_core_math_big + +import "core:math/big" +import "core:testing" + +@(test) +test_permutations_and_combinations :: proc(t: ^testing.T) { + { + calc, exp := &big.Int{}, &big.Int{} + defer big.destroy(calc, exp) + big.permutations_without_repetition(calc, 9000, 10) + big.int_atoi(exp, "3469387884476822917768284664849390080000") + equals, error := big.equals(calc, exp) + testing.expect(t, equals) + testing.expect_value(t, error, nil) + } + + { + calc, exp := &big.Int{}, &big.Int{} + defer big.destroy(calc, exp) + big.combinations_with_repetition(calc, 9000, 10) + big.int_atoi(exp, "965678962435231708695393645683400") + equals, error := big.equals(calc, exp) + testing.expect(t, equals) + testing.expect_value(t, error, nil) + } + + { + calc, exp := &big.Int{}, &big.Int{} + defer big.destroy(calc, exp) + big.combinations_without_repetition(calc, 9000, 10) + big.int_atoi(exp, "956070294443568925751842114431600") + equals, error := big.equals(calc, exp) + testing.expect(t, equals) + testing.expect_value(t, error, nil) + } +} From 70820c2c406f81e192923a7488cd76ec978cbeb2 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 9 Jun 2024 21:37:56 -0400 Subject: [PATCH 233/270] Add missing `string_to_int` alias in `core:math/big` --- core/math/big/radix.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/math/big/radix.odin b/core/math/big/radix.odin index f4eed879f..a5100e478 100644 --- a/core/math/big/radix.odin +++ b/core/math/big/radix.odin @@ -315,6 +315,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context atoi :: proc { int_atoi, } +string_to_int :: int_atoi /* We size for `string` by default. From 9d28f2e18c80b1537b9a71b907839b20973feb21 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 9 Jun 2024 22:46:45 -0400 Subject: [PATCH 234/270] Fix `or_or_` error messages --- src/parser.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/parser.cpp b/src/parser.cpp index c004a8f65..eff5e0c6c 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -555,7 +555,7 @@ gb_internal Ast *ast_unary_expr(AstFile *f, Token op, Ast *expr) { syntax_error_with_verbose(expr, "'or_return' within an unary expression not wrapped in parentheses (...)"); break; case Ast_OrBranchExpr: - syntax_error_with_verbose(expr, "'or_%.*s' within an unary expression not wrapped in parentheses (...)", LIT(expr->OrBranchExpr.token.string)); + syntax_error_with_verbose(expr, "'%.*s' within an unary expression not wrapped in parentheses (...)", LIT(expr->OrBranchExpr.token.string)); break; } @@ -583,7 +583,7 @@ gb_internal Ast *ast_binary_expr(AstFile *f, Token op, Ast *left, Ast *right) { syntax_error_with_verbose(left, "'or_return' within a binary expression not wrapped in parentheses (...)"); break; case Ast_OrBranchExpr: - syntax_error_with_verbose(left, "'or_%.*s' within a binary expression not wrapped in parentheses (...)", LIT(left->OrBranchExpr.token.string)); + syntax_error_with_verbose(left, "'%.*s' within a binary expression not wrapped in parentheses (...)", LIT(left->OrBranchExpr.token.string)); break; } if (right) switch (right->kind) { @@ -591,7 +591,7 @@ gb_internal Ast *ast_binary_expr(AstFile *f, Token op, Ast *left, Ast *right) { syntax_error_with_verbose(right, "'or_return' within a binary expression not wrapped in parentheses (...)"); break; case Ast_OrBranchExpr: - syntax_error_with_verbose(right, "'or_%.*s' within a binary expression not wrapped in parentheses (...)", LIT(right->OrBranchExpr.token.string)); + syntax_error_with_verbose(right, "'%.*s' within a binary expression not wrapped in parentheses (...)", LIT(right->OrBranchExpr.token.string)); break; } @@ -3102,7 +3102,7 @@ gb_internal void parse_check_or_return(Ast *operand, char const *msg) { syntax_error_with_verbose(operand, "'or_return' use within %s is not wrapped in parentheses (...)", msg); break; case Ast_OrBranchExpr: - syntax_error_with_verbose(operand, "'or_%.*s' use within %s is not wrapped in parentheses (...)", msg, LIT(operand->OrBranchExpr.token.string)); + syntax_error_with_verbose(operand, "'%.*s' use within %s is not wrapped in parentheses (...)", msg, LIT(operand->OrBranchExpr.token.string)); break; } } From 8702bf00d5f5b22142e88258c288cfec2423089c Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Sun, 9 Jun 2024 22:47:22 -0400 Subject: [PATCH 235/270] Remove `_` in `Syntax_Error` verbose message --- src/error.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/error.cpp b/src/error.cpp index eed69b779..03d96219b 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -516,7 +516,7 @@ gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, if (pos.line == 0) { error_out_empty(); - error_out_coloured("Syntax_Error: ", TerminalStyle_Normal, TerminalColour_Red); + error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); error_out_va(fmt, va); error_out("\n"); } else { @@ -527,7 +527,7 @@ gb_internal void syntax_error_with_verbose_va(TokenPos const &pos, TokenPos end, error_out_pos(pos); } if (has_ansi_terminal_colours()) { - error_out_coloured("Syntax_Error: ", TerminalStyle_Normal, TerminalColour_Red); + error_out_coloured("Syntax Error: ", TerminalStyle_Normal, TerminalColour_Red); } error_out_va(fmt, va); error_out("\n"); From 45d1328a85818fc77f2aacc77fa6ce0cfbce3473 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 10 Jun 2024 00:50:16 -0400 Subject: [PATCH 236/270] Fix typo in a `#force_inline` error message --- src/check_expr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 13d0e277d..641f70566 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7672,7 +7672,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c if (decl->proc_lit) { ast_node(pl, ProcLit, decl->proc_lit); if (pl->inlining == ProcInlining_no_inline) { - error(call, "'#force_inline' cannot be applied to a procedure that has be marked as '#force_no_inline'"); + error(call, "'#force_inline' cannot be applied to a procedure that has been marked as '#force_no_inline'"); } } } From ff7fcb6d380d1e45402de2b2e0d2b577ad9f6d59 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 10 Jun 2024 03:47:20 -0400 Subject: [PATCH 237/270] Add compilation-related constants `ODIN_VERSION_HASH` is the `git` SHA hash of the commit the Odin compiler was built with. `ODIN_MICROARCH_STRING` is the string passed to `-microarch` when the program was built. `ODIN_OPTIMIZATION_MODE` is an enum value of which optimization mode was used to build the program. --- base/runtime/core.odin | 13 +++++++++++++ src/checker.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/base/runtime/core.odin b/base/runtime/core.odin index 3e24060af..8671920f5 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -560,6 +560,19 @@ Odin_Platform_Subtarget_Type :: type_of(ODIN_PLATFORM_SUBTARGET) */ Odin_Sanitizer_Flags :: type_of(ODIN_SANITIZER_FLAGS) +/* + // Defined internally by the compiler + Odin_Optimization_Mode :: enum int { + None = -1, + Minimal = 0, + Size = 1, + Speed = 2, + Aggressive = 3, + } + + ODIN_OPTIMIZATION_MODE // is a constant +*/ +Odin_Optimization_Mode :: type_of(ODIN_OPTIMIZATION_MODE) ///////////////////////////// // Init Startup Procedures // diff --git a/src/checker.cpp b/src/checker.cpp index 8a58bb425..08a03ac62 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1040,6 +1040,8 @@ gb_internal void init_universal(void) { add_global_enum_constant(fields, "ODIN_ARCH", bc->metrics.arch); add_global_string_constant("ODIN_ARCH_STRING", target_arch_names[bc->metrics.arch]); } + + add_global_string_constant("ODIN_MICROARCH_STRING", bc->microarch); { GlobalEnumValue values[BuildMode_COUNT] = { @@ -1130,6 +1132,17 @@ gb_internal void init_universal(void) { add_global_constant("ODIN_COMPILE_TIMESTAMP", t_untyped_integer, exact_value_i64(odin_compile_timestamp())); + { + String version = {}; + + #ifdef GIT_SHA + version.text = cast(u8 *)GIT_SHA; + version.len = gb_strlen(GIT_SHA); + #endif + + add_global_string_constant("ODIN_VERSION_HASH", version); + } + { bool f16_supported = lb_use_new_pass_system(); if (is_arch_wasm()) { @@ -1167,6 +1180,18 @@ gb_internal void init_universal(void) { add_global_constant("ODIN_SANITIZER_FLAGS", named_type, exact_value_u64(bc->sanitizer_flags)); } + { + GlobalEnumValue values[5] = { + {"None", -1}, + {"Minimal", 0}, + {"Size", 1}, + {"Speed", 2}, + {"Aggressive", 3}, + }; + + auto fields = add_global_enum_type(str_lit("Odin_Optimization_Mode"), values, gb_count_of(values)); + add_global_enum_constant(fields, "ODIN_OPTIMIZATION_MODE", bc->optimization_level); + } // Builtin Procedures From 71a812e7fe7d7200be014c2c0a7e0ea3d7988359 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 10 Jun 2024 05:30:16 -0400 Subject: [PATCH 238/270] Use `get_final_microarchitecture()` for `ODIN_MICROARCH_STRING` --- src/checker.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/checker.cpp b/src/checker.cpp index 08a03ac62..8f0cc1cd1 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -3,6 +3,8 @@ #include "entity.cpp" #include "types.cpp" +String get_final_microarchitecture(); + gb_internal void check_expr(CheckerContext *c, Operand *operand, Ast *expression); gb_internal void check_expr_or_type(CheckerContext *c, Operand *operand, Ast *expression, Type *type_hint=nullptr); gb_internal void add_comparison_procedures_for_fields(CheckerContext *c, Type *t); @@ -1041,7 +1043,7 @@ gb_internal void init_universal(void) { add_global_string_constant("ODIN_ARCH_STRING", target_arch_names[bc->metrics.arch]); } - add_global_string_constant("ODIN_MICROARCH_STRING", bc->microarch); + add_global_string_constant("ODIN_MICROARCH_STRING", get_final_microarchitecture()); { GlobalEnumValue values[BuildMode_COUNT] = { From 1945218f6df814ea95233035d0b51585e2522b2e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 10 Jun 2024 14:18:33 +0100 Subject: [PATCH 239/270] Improve parsing for `label: #reverse for` and `label: #partial switch` --- core/odin/parser/parser.odin | 37 +++++++++++++++++++++++++++++++++++- src/parser.cpp | 6 ++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index ad5ee9087..6b0aa2888 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -1455,7 +1455,7 @@ parse_stmt :: proc(p: ^Parser) -> ^ast.Stmt { case "unroll": return parse_unrolled_for_loop(p, tag) case "reverse": - stmt := parse_for_stmt(p) + stmt := parse_stmt(p) if range, is_range := stmt.derived.(^ast.Range_Stmt); is_range { if range.reverse { @@ -3515,6 +3515,25 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt { case op.kind == .Colon: expect_token_after(p, .Colon, "identifier list") if .Label in flags && len(lhs) == 1 { + is_partial := false + is_reverse := false + + partial_token: tokenizer.Token + if p.curr_tok.kind == .Hash { + name := peek_token(p) + if name.kind == .Ident && name.text == "partial" && + peek_token(p, 1).kind == .Switch { + partial_token = expect_token(p, .Hash) + expect_token(p, .Ident) + is_partial = true + } else if name.kind == .Ident && name.text == "reverse" && + peek_token(p, 1).kind == .For { + partial_token = expect_token(p, .Hash) + expect_token(p, .Ident) + is_reverse = true + } + } + #partial switch p.curr_tok.kind { case .Open_Brace, .If, .For, .Switch: label := lhs[0] @@ -3529,6 +3548,22 @@ parse_simple_stmt :: proc(p: ^Parser, flags: Stmt_Allow_Flags) -> ^ast.Stmt { case ^ast.Type_Switch_Stmt: n.label = label case ^ast.Range_Stmt: n.label = label } + + if is_partial { + #partial switch n in stmt.derived_stmt { + case ^ast.Switch_Stmt: n.partial = true + case ^ast.Type_Switch_Stmt: n.partial = true + case: + error(p, partial_token.pos, "incorrect use of directive, use '%s: #partial switch'", partial_token.text) + } + } + if is_reverse { + #partial switch n in stmt.derived_stmt { + case ^ast.Range_Stmt: n.reverse = true + case: + error(p, partial_token.pos, "incorrect use of directive, use '%s: #reverse for'", partial_token.text) + } + } } return stmt diff --git a/src/parser.cpp b/src/parser.cpp index eff5e0c6c..2cdcea417 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -3747,8 +3747,10 @@ gb_internal Ast *parse_simple_stmt(AstFile *f, u32 flags) { case Ast_TypeSwitchStmt: stmt->TypeSwitchStmt.partial = true; break; + default: + syntax_error(partial_token, "Incorrect use of directive, use '%.*s: #partial switch'", LIT(ast_token(name).string)); + break; } - syntax_error(partial_token, "Incorrect use of directive, use '#partial %.*s: switch'", LIT(ast_token(name).string)); } else if (is_reverse) { switch (stmt->kind) { case Ast_RangeStmt: @@ -5176,7 +5178,7 @@ gb_internal Ast *parse_stmt(AstFile *f) { } else if (tag == "unroll") { return parse_unrolled_for_loop(f, name); } else if (tag == "reverse") { - Ast *for_stmt = parse_for_stmt(f); + Ast *for_stmt = parse_stmt(f); if (for_stmt->kind == Ast_RangeStmt) { if (for_stmt->RangeStmt.reverse) { syntax_error(token, "#reverse already applied to a 'for in' statement"); From 71929f737bcb70db6875839b6beba020d84d24ff Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 10 Jun 2024 15:35:23 +0200 Subject: [PATCH 240/270] add forced shutdown to new test runner Currently, a Ctrl+c starts a graceful shutdown of the tests and runner. Sometimes tests get stuck and this would never complete. This simply adds an extra step, if Ctrl+c is given for the second time, just `os.exit` right away. --- core/testing/signal_handler_libc.odin | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index d76fdd66b..0ab34776e 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -6,6 +6,7 @@ import "base:intrinsics" import "core:c/libc" import "core:encoding/ansi" import "core:sync" +import "core:os" @require import "core:sys/unix" @(private="file") stop_runner_flag: libc.sig_atomic_t @@ -20,7 +21,13 @@ local_test_index: libc.sig_atomic_t @(private="file") stop_runner_callback :: proc "c" (sig: libc.int) { - intrinsics.atomic_store(&stop_runner_flag, 1) + prev := intrinsics.atomic_add(&stop_runner_flag, 1) + + // If the flag was already set (if this is the second signal sent for example), + // consider this a forced (not graceful) exit. + if prev > 0 { + os.exit(int(sig)) + } } @(private="file") From fa3cae2bb04db76f52f1b2288a9c858f20332b8a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 10 Jun 2024 15:02:34 +0100 Subject: [PATCH 241/270] Add `intrinsics.procedure_of` ```odin foo :: proc(x: $T) { fmt.println(x) } bar :: intrinsics.procedure_of(foo(int(123))) // parameters are never ran at compile time, similar to `size_of` bar(333) // prints 333 ``` --- base/intrinsics/intrinsics.odin | 4 +++ src/check_builtin.cpp | 46 +++++++++++++++++++++++++++++++++ src/check_decl.cpp | 17 ++++++++---- src/check_expr.cpp | 1 + src/check_stmt.cpp | 10 ++++++- src/checker.cpp | 4 +++ src/checker.hpp | 6 +++++ src/checker_builtin_procs.hpp | 4 +++ src/parser.hpp | 1 + 9 files changed, 87 insertions(+), 6 deletions(-) diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index 8873f3bbc..4f6fa2713 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -295,6 +295,10 @@ simd_rotate_right :: proc(a: #simd[N]T, $offset: int) -> #simd[N]T --- // if all listed features are supported. has_target_feature :: proc($test: $T) -> bool where type_is_string(T) || type_is_proc(T) --- + +// Returns the value of the procedure where `x` must be a call expression +procedure_of :: proc(x: $T) -> T where type_is_proc(T) --- + // WASM targets only wasm_memory_grow :: proc(index, delta: uintptr) -> int --- wasm_memory_size :: proc(index: uintptr) -> int --- diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 98c695a2c..3aee804df 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -1843,6 +1843,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case BuiltinProc_objc_register_class: case BuiltinProc_atomic_type_is_lock_free: case BuiltinProc_has_target_feature: + case BuiltinProc_procedure_of: // NOTE(bill): The first arg may be a Type, this will be checked case by case break; @@ -6157,6 +6158,51 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As break; } + case BuiltinProc_procedure_of: + { + Ast *call_expr = unparen_expr(ce->args[0]); + Operand op = {}; + check_expr_base(c, &op, ce->args[0], nullptr); + if (op.mode != Addressing_Value && !(call_expr && call_expr->kind == Ast_CallExpr)) { + error(ce->args[0], "Expected a call expression for '%.*s'", LIT(builtin_name)); + return false; + } + + Ast *proc = call_expr->CallExpr.proc; + Entity *e = entity_of_node(proc); + + if (e == nullptr) { + error(ce->args[0], "Invalid procedure value, expected a regular/specialized procedure"); + return false; + } + + TypeAndValue tav = proc->tav; + + + operand->type = e->type; + operand->mode = Addressing_Value; + operand->value = tav.value; + operand->builtin_id = BuiltinProc_Invalid; + operand->proc_group = nullptr; + + if (tav.mode == Addressing_Builtin) { + operand->mode = tav.mode; + operand->builtin_id = cast(BuiltinProcId)e->Builtin.id; + break; + } + + if (!is_type_proc(e->type)) { + gbString s = type_to_string(e->type); + error(ce->args[0], "Expected a procedure value, got '%s'", s); + gb_string_free(s); + return false; + } + + + ce->entity_procedure_of = e; + break; + } + case BuiltinProc_constant_utf16_cstring: { String value = {}; diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 43947836b..13b14149a 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -88,11 +88,14 @@ gb_internal Type *check_init_variable(CheckerContext *ctx, Entity *e, Operand *o e->type = t_invalid; return nullptr; } else if (is_type_polymorphic(t)) { - gbString str = type_to_string(t); - defer (gb_string_free(str)); - error(e->token, "Invalid use of a polymorphic type '%s' in %.*s", str, LIT(context_name)); - e->type = t_invalid; - return nullptr; + Entity *e = entity_of_node(operand->expr); + if (e->state.load() != EntityState_Resolved) { + gbString str = type_to_string(t); + defer (gb_string_free(str)); + error(e->token, "Invalid use of a polymorphic type '%s' in %.*s", str, LIT(context_name)); + e->type = t_invalid; + return nullptr; + } } else if (is_type_empty_union(t)) { gbString str = type_to_string(t); defer (gb_string_free(str)); @@ -479,6 +482,9 @@ gb_internal void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr entity = check_selector(ctx, &operand, init, e->type); } else { check_expr_or_type(ctx, &operand, init, e->type); + if (init->kind == Ast_CallExpr) { + entity = init->CallExpr.entity_procedure_of; + } } switch (operand.mode) { @@ -526,6 +532,7 @@ gb_internal void check_const_decl(CheckerContext *ctx, Entity *e, Ast *type_expr return; } + if (entity != nullptr) { if (e->type != nullptr) { Operand x = {}; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 641f70566..01cba881e 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -578,6 +578,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E d->defer_use_checked = false; Entity *entity = alloc_entity_procedure(nullptr, token, final_proc_type, tags); + entity->state.store(EntityState_Resolved); entity->identifier = ident; add_entity_and_decl_info(&nctx, ident, entity, d); diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index c37c58cd6..f2e3b0242 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -2224,8 +2224,16 @@ gb_internal void check_expr_stmt(CheckerContext *ctx, Ast *node) { } if (do_require) { gbString expr_str = expr_to_string(ce->proc); + defer (gb_string_free(expr_str)); + if (builtin_id) { + String real_name = builtin_procs[builtin_id].name; + if (real_name != make_string(cast(u8 const *)expr_str, gb_string_length(expr_str))) { + error(node, "'%s' ('%.*s.%.*s') requires that its results must be handled", expr_str, + LIT(builtin_proc_pkg_name[builtin_procs[builtin_id].pkg]), LIT(real_name)); + return; + } + } error(node, "'%s' requires that its results must be handled", expr_str); - gb_string_free(expr_str); } return; } else if (expr && expr->kind == Ast_SelectorCallExpr) { diff --git a/src/checker.cpp b/src/checker.cpp index 8f0cc1cd1..852fb89bb 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1479,6 +1479,10 @@ gb_internal Entity *entity_of_node(Ast *expr) { case_ast_node(cc, CaseClause, expr); return cc->implicit_entity; case_end; + + case_ast_node(ce, CallExpr, expr); + return ce->entity_procedure_of; + case_end; } return nullptr; } diff --git a/src/checker.hpp b/src/checker.hpp index 2ac4c8e7a..492a64fb6 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -51,6 +51,12 @@ enum StmtFlag { enum BuiltinProcPkg { BuiltinProcPkg_builtin, BuiltinProcPkg_intrinsics, + BuiltinProcPkg_COUNT +}; + +String builtin_proc_pkg_name[BuiltinProcPkg_COUNT] = { + str_lit("builtin"), + str_lit("intrinsics"), }; struct BuiltinProc { diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index 5f98bb7b3..35acad42f 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -299,6 +299,8 @@ BuiltinProc__type_simple_boolean_end, BuiltinProc__type_end, + BuiltinProc_procedure_of, + BuiltinProc___entry_point, BuiltinProc_objc_send, @@ -614,6 +616,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT(""), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, + {STR_LIT("procedure_of"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("__entry_point"), 0, false, Expr_Stmt, BuiltinProcPkg_intrinsics}, {STR_LIT("objc_send"), 3, true, Expr_Expr, BuiltinProcPkg_intrinsics, false, true}, diff --git a/src/parser.hpp b/src/parser.hpp index 0e411d9ac..02f2af28d 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -458,6 +458,7 @@ AST_KIND(_ExprBegin, "", bool) \ bool optional_ok_one; \ bool was_selector; \ AstSplitArgs *split_args; \ + Entity *entity_procedure_of; \ }) \ AST_KIND(FieldValue, "field value", struct { Token eq; Ast *field, *value; }) \ AST_KIND(EnumFieldValue, "enum field value", struct { \ From c1e81dc14d7afec9e8924e8d93b6de6109cd1790 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 10 Jun 2024 15:05:24 +0100 Subject: [PATCH 242/270] Fix #3726 --- core/math/linalg/specific.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/math/linalg/specific.odin b/core/math/linalg/specific.odin index 41d0e5344..b841f0610 100644 --- a/core/math/linalg/specific.odin +++ b/core/math/linalg/specific.odin @@ -527,7 +527,7 @@ angle_from_quaternion :: proc{ @(require_results) axis_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> Vector3f16 { t1 := 1 - q.w*q.w - if t1 < 0 { + if t1 <= 0 { return {0, 0, 1} } t2 := 1.0 / math.sqrt(t1) @@ -536,7 +536,7 @@ axis_from_quaternion_f16 :: proc "contextless" (q: Quaternionf16) -> Vector3f16 @(require_results) axis_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> Vector3f32 { t1 := 1 - q.w*q.w - if t1 < 0 { + if t1 <= 0 { return {0, 0, 1} } t2 := 1.0 / math.sqrt(t1) @@ -545,7 +545,7 @@ axis_from_quaternion_f32 :: proc "contextless" (q: Quaternionf32) -> Vector3f32 @(require_results) axis_from_quaternion_f64 :: proc "contextless" (q: Quaternionf64) -> Vector3f64 { t1 := 1 - q.w*q.w - if t1 < 0 { + if t1 <= 0 { return {0, 0, 1} } t2 := 1.0 / math.sqrt(t1) From eef2aef021a039e627693c73c7962ed57f4ea073 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 10 Jun 2024 15:07:45 +0100 Subject: [PATCH 243/270] Fix #3724 --- src/llvm_backend_debug.cpp | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index afbf3e046..f1ace5f06 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -626,50 +626,50 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) { case Basic_complex32: { LLVMMetadataRef elements[2] = {}; - elements[0] = lb_debug_struct_field(m, str_lit("real"), t_f16, 0); - elements[1] = lb_debug_struct_field(m, str_lit("imag"), t_f16, 4); + elements[0] = lb_debug_struct_field(m, str_lit("real"), t_f16, 0*16); + elements[1] = lb_debug_struct_field(m, str_lit("imag"), t_f16, 1*16); return lb_debug_basic_struct(m, str_lit("complex32"), 64, 32, elements, gb_count_of(elements)); } case Basic_complex64: { LLVMMetadataRef elements[2] = {}; - elements[0] = lb_debug_struct_field(m, str_lit("real"), t_f32, 0); - elements[1] = lb_debug_struct_field(m, str_lit("imag"), t_f32, 4); + elements[0] = lb_debug_struct_field(m, str_lit("real"), t_f32, 0*32); + elements[1] = lb_debug_struct_field(m, str_lit("imag"), t_f32, 2*32); return lb_debug_basic_struct(m, str_lit("complex64"), 64, 32, elements, gb_count_of(elements)); } case Basic_complex128: { LLVMMetadataRef elements[2] = {}; - elements[0] = lb_debug_struct_field(m, str_lit("real"), t_f64, 0); - elements[1] = lb_debug_struct_field(m, str_lit("imag"), t_f64, 8); + elements[0] = lb_debug_struct_field(m, str_lit("real"), t_f64, 0*64); + elements[1] = lb_debug_struct_field(m, str_lit("imag"), t_f64, 1*64); return lb_debug_basic_struct(m, str_lit("complex128"), 128, 64, elements, gb_count_of(elements)); } case Basic_quaternion64: { LLVMMetadataRef elements[4] = {}; - elements[0] = lb_debug_struct_field(m, str_lit("imag"), t_f16, 0); - elements[1] = lb_debug_struct_field(m, str_lit("jmag"), t_f16, 4); - elements[2] = lb_debug_struct_field(m, str_lit("kmag"), t_f16, 8); - elements[3] = lb_debug_struct_field(m, str_lit("real"), t_f16, 12); + elements[0] = lb_debug_struct_field(m, str_lit("imag"), t_f16, 0*16); + elements[1] = lb_debug_struct_field(m, str_lit("jmag"), t_f16, 1*16); + elements[2] = lb_debug_struct_field(m, str_lit("kmag"), t_f16, 2*16); + elements[3] = lb_debug_struct_field(m, str_lit("real"), t_f16, 3*16); return lb_debug_basic_struct(m, str_lit("quaternion64"), 128, 32, elements, gb_count_of(elements)); } case Basic_quaternion128: { LLVMMetadataRef elements[4] = {}; - elements[0] = lb_debug_struct_field(m, str_lit("imag"), t_f32, 0); - elements[1] = lb_debug_struct_field(m, str_lit("jmag"), t_f32, 4); - elements[2] = lb_debug_struct_field(m, str_lit("kmag"), t_f32, 8); - elements[3] = lb_debug_struct_field(m, str_lit("real"), t_f32, 12); + elements[0] = lb_debug_struct_field(m, str_lit("imag"), t_f32, 0*32); + elements[1] = lb_debug_struct_field(m, str_lit("jmag"), t_f32, 1*32); + elements[2] = lb_debug_struct_field(m, str_lit("kmag"), t_f32, 2*32); + elements[3] = lb_debug_struct_field(m, str_lit("real"), t_f32, 3*32); return lb_debug_basic_struct(m, str_lit("quaternion128"), 128, 32, elements, gb_count_of(elements)); } case Basic_quaternion256: { LLVMMetadataRef elements[4] = {}; - elements[0] = lb_debug_struct_field(m, str_lit("imag"), t_f64, 0); - elements[1] = lb_debug_struct_field(m, str_lit("jmag"), t_f64, 8); - elements[2] = lb_debug_struct_field(m, str_lit("kmag"), t_f64, 16); - elements[3] = lb_debug_struct_field(m, str_lit("real"), t_f64, 24); + elements[0] = lb_debug_struct_field(m, str_lit("imag"), t_f64, 0*64); + elements[1] = lb_debug_struct_field(m, str_lit("jmag"), t_f64, 1*64); + elements[2] = lb_debug_struct_field(m, str_lit("kmag"), t_f64, 2*64); + elements[3] = lb_debug_struct_field(m, str_lit("real"), t_f64, 3*64); return lb_debug_basic_struct(m, str_lit("quaternion256"), 256, 32, elements, gb_count_of(elements)); } From f1779c85dedb8bb309a9afa8cfa7e35ad727d237 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 10 Jun 2024 18:50:53 +0100 Subject: [PATCH 244/270] Fix #3727 --- src/parser.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/parser.cpp b/src/parser.cpp index 2cdcea417..0cd96f5b5 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2091,6 +2091,9 @@ gb_internal bool ast_on_same_line(Token const &x, Ast *yp) { gb_internal Ast *parse_force_inlining_operand(AstFile *f, Token token) { Ast *expr = parse_unary_expr(f, false); Ast *e = strip_or_return_expr(expr); + if (e == nullptr) { + return expr; + } if (e->kind != Ast_ProcLit && e->kind != Ast_CallExpr) { syntax_error(expr, "%.*s must be followed by a procedure literal or call, got %.*s", LIT(token.string), LIT(ast_strings[expr->kind])); return ast_bad_expr(f, token, f->curr_token); From 50464bdce374e688313961a37f12197b505a6391 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 10 Jun 2024 20:51:58 -0400 Subject: [PATCH 245/270] Use `#any_int` for `reserve_*` and `resize_*` procs Resolves #3088 --- base/runtime/core_builtin.odin | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 00c30d3fd..a9566c831 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -383,7 +383,7 @@ clear_map :: proc "contextless" (m: ^$T/map[$K]$V) { // // Note: Prefer the procedure group `reserve` @builtin -reserve_map :: proc(m: ^$T/map[$K]$V, capacity: int, loc := #caller_location) -> Allocator_Error { +reserve_map :: proc(m: ^$T/map[$K]$V, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc) if m != nil else nil } @@ -721,12 +721,12 @@ _reserve_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, capacity: i } @builtin -reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { +reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { return _reserve_dynamic_array(array, capacity, true, loc) } @builtin -non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, capacity: int, loc := #caller_location) -> Allocator_Error { +non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity: int, loc := #caller_location) -> Allocator_Error { return _reserve_dynamic_array(array, capacity, false, loc) } @@ -773,12 +773,12 @@ _resize_dynamic_array :: #force_inline proc(array: ^$T/[dynamic]$E, length: int, } @builtin -resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { +resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { return _resize_dynamic_array(array, length, true, loc=loc) } @builtin -non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, length: int, loc := #caller_location) -> Allocator_Error { +non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: int, loc := #caller_location) -> Allocator_Error { return _resize_dynamic_array(array, length, false, loc=loc) } From bd198aeada1ea94b69fed5a6c9a7acf0759c9884 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Mon, 10 Jun 2024 23:37:54 -0400 Subject: [PATCH 246/270] Fix #3460 --- core/time/datetime/datetime.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/time/datetime/datetime.odin b/core/time/datetime/datetime.odin index e15ced5a5..89fa2ce98 100644 --- a/core/time/datetime/datetime.odin +++ b/core/time/datetime/datetime.odin @@ -127,13 +127,13 @@ days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: return delta.days, .None } -last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) -> (day: i64, err: Error) { +last_day_of_month :: proc "contextless" (#any_int year: i64, #any_int month: i8) -> (day: i8, err: Error) { // Not using formula 2.27 from the book. This is far simpler and gives the same answer. validate(Date{year, month, 1}) or_return month_days := MONTH_DAYS - day = i64(month_days[month]) + day = month_days[month] if month == 2 && is_leap_year(year) { day += 1 } From 34af2bb8adc7f1cfb91530bcdcc954d72af68aee Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Tue, 11 Jun 2024 08:34:45 +0200 Subject: [PATCH 247/270] Moved rlgl.odin to subpackage 'raylib/rlgl' --- vendor/raylib/{ => rlgl}/rlgl.odin | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename vendor/raylib/{ => rlgl}/rlgl.odin (100%) diff --git a/vendor/raylib/rlgl.odin b/vendor/raylib/rlgl/rlgl.odin similarity index 100% rename from vendor/raylib/rlgl.odin rename to vendor/raylib/rlgl/rlgl.odin From d2cd96c3c8bc271916ebaf189226ac84f23eed4d Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Tue, 11 Jun 2024 08:46:44 +0200 Subject: [PATCH 248/270] Made rlgl.odin work as a subpackage of raylib. So now you import vendor:raylib/rlgl. Instead of rl.rlBegin(rl.RL_TRIANGLES) you now type rlgl.Begin(rlgl.TRIANGLES). --- vendor/raylib/rlgl/rlgl.odin | 479 ++++++++++++++++++----------------- 1 file changed, 242 insertions(+), 237 deletions(-) diff --git a/vendor/raylib/rlgl/rlgl.odin b/vendor/raylib/rlgl/rlgl.odin index c9e8c28c2..4a4ab4b7f 100644 --- a/vendor/raylib/rlgl/rlgl.odin +++ b/vendor/raylib/rlgl/rlgl.odin @@ -105,25 +105,25 @@ **********************************************************************************************/ -package raylib +package rlgl import "core:c" -RLGL_VERSION :: "4.5" +RLGL_VERSION :: "5.0" when ODIN_OS == .Windows { foreign import lib { - "windows/raylib.lib", + "../windows/raylib.lib", "system:Winmm.lib", "system:Gdi32.lib", "system:User32.lib", "system:Shell32.lib", } } else when ODIN_OS == .Linux { - foreign import lib "linux/libraylib.a" + foreign import lib "../linux/libraylib.a" } else when ODIN_OS == .Darwin { foreign import lib { - "macos-arm64/libraylib.a" when ODIN_ARCH == .arm64 else "macos/libraylib.a", + "../macos-arm64/libraylib.a" when ODIN_ARCH == .arm64 else "../macos/libraylib.a", "system:Cocoa.framework", "system:OpenGL.framework", "system:IOKit.framework", @@ -132,122 +132,122 @@ when ODIN_OS == .Windows { foreign import lib "system:raylib" } -RL_GRAPHICS_API_OPENGL_11 :: false -RL_GRAPHICS_API_OPENGL_21 :: true -RL_GRAPHICS_API_OPENGL_33 :: RL_GRAPHICS_API_OPENGL_21 // default currently -RL_GRAPHICS_API_OPENGL_ES2 :: false -RL_GRAPHICS_API_OPENGL_43 :: false -RL_GRAPHICS_API_OPENGL_ES3 :: false +GRAPHICS_API_OPENGL_11 :: false +GRAPHICS_API_OPENGL_21 :: true +GRAPHICS_API_OPENGL_33 :: GRAPHICS_API_OPENGL_21 // default currently +GRAPHICS_API_OPENGL_ES2 :: false +GRAPHICS_API_OPENGL_43 :: false +GRAPHICS_API_OPENGL_ES3 :: false -when RL_GRAPHICS_API_OPENGL_ES3 { - RL_GRAPHICS_API_OPENGL_ES2 :: true +when GRAPHICS_API_OPENGL_ES3 { + GRAPHICS_API_OPENGL_ES2 :: true } -when !RL_GRAPHICS_API_OPENGL_ES2 { +when !GRAPHICS_API_OPENGL_ES2 { // This is the maximum amount of elements (quads) per batch // NOTE: Be careful with text, every letter maps to a quad - RL_DEFAULT_BATCH_BUFFER_ELEMENTS :: 8192 + DEFAULT_BATCH_BUFFER_ELEMENTS :: 8192 } else { // We reduce memory sizes for embedded systems (RPI and HTML5) // NOTE: On HTML5 (emscripten) this is allocated on heap, // by default it's only 16MB!...just take care... - RL_DEFAULT_BATCH_BUFFER_ELEMENTS :: 2048 + DEFAULT_BATCH_BUFFER_ELEMENTS :: 2048 } -RL_DEFAULT_BATCH_BUFFERS :: 1 // Default number of batch buffers (multi-buffering) -RL_DEFAULT_BATCH_DRAWCALLS :: 256 // Default number of batch draw calls (by state changes: mode, texture) -RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS :: 4 // Maximum number of additional textures that can be activated on batch drawing (SetShaderValueTexture()) +DEFAULT_BATCH_BUFFERS :: 1 // Default number of batch buffers (multi-buffering) +DEFAULT_BATCH_DRAWCALLS :: 256 // Default number of batch draw calls (by state changes: mode, texture) +DEFAULT_BATCH_MAX_TEXTURE_UNITS :: 4 // Maximum number of additional textures that can be activated on batch drawing (SetShaderValueTexture()) // Internal Matrix stack -RL_MAX_MATRIX_STACK_SIZE :: 32 // Maximum size of Matrix stack +MAX_MATRIX_STACK_SIZE :: 32 // Maximum size of Matrix stack // Shader limits -RL_MAX_SHADER_LOCATIONS :: 32 // Maximum number of shader locations supported +MAX_SHADER_LOCATIONS :: 32 // Maximum number of shader locations supported // Projection matrix culling -RL_CULL_DISTANCE_NEAR :: 0.01 // Default near cull distance -RL_CULL_DISTANCE_FAR :: 1000.0 // Default far cull distance +CULL_DISTANCE_NEAR :: 0.01 // Default near cull distance +CULL_DISTANCE_FAR :: 1000.0 // Default far cull distance // Texture parameters (equivalent to OpenGL defines) -RL_TEXTURE_WRAP_S :: 0x2802 // GL_TEXTURE_WRAP_S -RL_TEXTURE_WRAP_T :: 0x2803 // GL_TEXTURE_WRAP_T -RL_TEXTURE_MAG_FILTER :: 0x2800 // GL_TEXTURE_MAG_FILTER -RL_TEXTURE_MIN_FILTER :: 0x2801 // GL_TEXTURE_MIN_FILTER +TEXTURE_WRAP_S :: 0x2802 // GL_TEXTURE_WRAP_S +TEXTURE_WRAP_T :: 0x2803 // GL_TEXTURE_WRAP_T +TEXTURE_MAG_FILTER :: 0x2800 // GL_TEXTURE_MAG_FILTER +TEXTURE_MIN_FILTER :: 0x2801 // GL_TEXTURE_MIN_FILTER -RL_TEXTURE_FILTER_NEAREST :: 0x2600 // GL_NEAREST -RL_TEXTURE_FILTER_LINEAR :: 0x2601 // GL_LINEAR -RL_TEXTURE_FILTER_MIP_NEAREST :: 0x2700 // GL_NEAREST_MIPMAP_NEAREST -RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR :: 0x2702 // GL_NEAREST_MIPMAP_LINEAR -RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST :: 0x2701 // GL_LINEAR_MIPMAP_NEAREST -RL_TEXTURE_FILTER_MIP_LINEAR :: 0x2703 // GL_LINEAR_MIPMAP_LINEAR -RL_TEXTURE_FILTER_ANISOTROPIC :: 0x3000 // Anisotropic filter (custom identifier) +TEXTURE_FILTER_NEAREST :: 0x2600 // GL_NEAREST +TEXTURE_FILTER_LINEAR :: 0x2601 // GL_LINEAR +TEXTURE_FILTER_MIP_NEAREST :: 0x2700 // GL_NEAREST_MIPMAP_NEAREST +TEXTURE_FILTER_NEAREST_MIP_LINEAR :: 0x2702 // GL_NEAREST_MIPMAP_LINEAR +TEXTURE_FILTER_LINEAR_MIP_NEAREST :: 0x2701 // GL_LINEAR_MIPMAP_NEAREST +TEXTURE_FILTER_MIP_LINEAR :: 0x2703 // GL_LINEAR_MIPMAP_LINEAR +TEXTURE_FILTER_ANISOTROPIC :: 0x3000 // Anisotropic filter (custom identifier) -RL_TEXTURE_WRAP_REPEAT :: 0x2901 // GL_REPEAT -RL_TEXTURE_WRAP_CLAMP :: 0x812F // GL_CLAMP_TO_EDGE -RL_TEXTURE_WRAP_MIRROR_REPEAT :: 0x8370 // GL_MIRRORED_REPEAT -RL_TEXTURE_WRAP_MIRROR_CLAMP :: 0x8742 // GL_MIRROR_CLAMP_EXT +TEXTURE_WRAP_REPEAT :: 0x2901 // GL_REPEAT +TEXTURE_WRAP_CLAMP :: 0x812F // GL_CLAMP_TO_EDGE +TEXTURE_WRAP_MIRROR_REPEAT :: 0x8370 // GL_MIRRORED_REPEAT +TEXTURE_WRAP_MIRROR_CLAMP :: 0x8742 // GL_MIRROR_CLAMP_EXT // Matrix modes (equivalent to OpenGL) -RL_MODELVIEW :: 0x1700 // GL_MODELVIEW -RL_PROJECTION :: 0x1701 // GL_PROJECTION -RL_TEXTURE :: 0x1702 // GL_TEXTURE +MODELVIEW :: 0x1700 // GL_MODELVIEW +PROJECTION :: 0x1701 // GL_PROJECTION +TEXTURE :: 0x1702 // GL_TEXTURE // Primitive assembly draw modes -RL_LINES :: 0x0001 // GL_LINES -RL_TRIANGLES :: 0x0004 // GL_TRIANGLES -RL_QUADS :: 0x0007 // GL_QUADS +LINES :: 0x0001 // GL_LINES +TRIANGLES :: 0x0004 // GL_TRIANGLES +QUADS :: 0x0007 // GL_QUADS // GL equivalent data types -RL_UNSIGNED_BYTE :: 0x1401 // GL_UNSIGNED_BYTE -RL_FLOAT :: 0x1406 // GL_FLOAT +UNSIGNED_BYTE :: 0x1401 // GL_UNSIGNED_BYTE +FLOAT :: 0x1406 // GL_FLOAT // Buffer usage hint -RL_STREAM_DRAW :: 0x88E0 // GL_STREAM_DRAW -RL_STREAM_READ :: 0x88E1 // GL_STREAM_READ -RL_STREAM_COPY :: 0x88E2 // GL_STREAM_COPY -RL_STATIC_DRAW :: 0x88E4 // GL_STATIC_DRAW -RL_STATIC_READ :: 0x88E5 // GL_STATIC_READ -RL_STATIC_COPY :: 0x88E6 // GL_STATIC_COPY -RL_DYNAMIC_DRAW :: 0x88E8 // GL_DYNAMIC_DRAW -RL_DYNAMIC_READ :: 0x88E9 // GL_DYNAMIC_READ -RL_DYNAMIC_COPY :: 0x88EA // GL_DYNAMIC_COPY +STREAM_DRAW :: 0x88E0 // GL_STREAM_DRAW +STREAM_READ :: 0x88E1 // GL_STREAM_READ +STREAM_COPY :: 0x88E2 // GL_STREAM_COPY +STATIC_DRAW :: 0x88E4 // GL_STATIC_DRAW +STATIC_READ :: 0x88E5 // GL_STATIC_READ +STATIC_COPY :: 0x88E6 // GL_STATIC_COPY +DYNAMIC_DRAW :: 0x88E8 // GL_DYNAMIC_DRAW +DYNAMIC_READ :: 0x88E9 // GL_DYNAMIC_READ +DYNAMIC_COPY :: 0x88EA // GL_DYNAMIC_COPY // GL Shader type -RL_FRAGMENT_SHADER :: 0x8B30 // GL_FRAGMENT_SHADER -RL_VERTEX_SHADER :: 0x8B31 // GL_VERTEX_SHADER -RL_COMPUTE_SHADER :: 0x91B9 // GL_COMPUTE_SHADER +FRAGMENT_SHADER :: 0x8B30 // GL_FRAGMENT_SHADER +VERTEX_SHADER :: 0x8B31 // GL_VERTEX_SHADER +COMPUTE_SHADER :: 0x91B9 // GL_COMPUTE_SHADER // GL blending factors -RL_ZERO :: 0 // GL_ZERO -RL_ONE :: 1 // GL_ONE -RL_SRC_COLOR :: 0x0300 // GL_SRC_COLOR -RL_ONE_MINUS_SRC_COLOR :: 0x0301 // GL_ONE_MINUS_SRC_COLOR -RL_SRC_ALPHA :: 0x0302 // GL_SRC_ALPHA -RL_ONE_MINUS_SRC_ALPHA :: 0x0303 // GL_ONE_MINUS_SRC_ALPHA -RL_DST_ALPHA :: 0x0304 // GL_DST_ALPHA -RL_ONE_MINUS_DST_ALPHA :: 0x0305 // GL_ONE_MINUS_DST_ALPHA -RL_DST_COLOR :: 0x0306 // GL_DST_COLOR -RL_ONE_MINUS_DST_COLOR :: 0x0307 // GL_ONE_MINUS_DST_COLOR -RL_SRC_ALPHA_SATURATE :: 0x0308 // GL_SRC_ALPHA_SATURATE -RL_CONSTANT_COLOR :: 0x8001 // GL_CONSTANT_COLOR -RL_ONE_MINUS_CONSTANT_COLOR :: 0x8002 // GL_ONE_MINUS_CONSTANT_COLOR -RL_CONSTANT_ALPHA :: 0x8003 // GL_CONSTANT_ALPHA -RL_ONE_MINUS_CONSTANT_ALPHA :: 0x8004 // GL_ONE_MINUS_CONSTANT_ALPHA +ZERO :: 0 // GL_ZERO +ONE :: 1 // GL_ONE +SRC_COLOR :: 0x0300 // GL_SRC_COLOR +ONE_MINUS_SRC_COLOR :: 0x0301 // GL_ONE_MINUS_SRC_COLOR +SRC_ALPHA :: 0x0302 // GL_SRC_ALPHA +ONE_MINUS_SRC_ALPHA :: 0x0303 // GL_ONE_MINUS_SRC_ALPHA +DST_ALPHA :: 0x0304 // GL_DST_ALPHA +ONE_MINUS_DST_ALPHA :: 0x0305 // GL_ONE_MINUS_DST_ALPHA +DST_COLOR :: 0x0306 // GL_DST_COLOR +ONE_MINUS_DST_COLOR :: 0x0307 // GL_ONE_MINUS_DST_COLOR +SRC_ALPHA_SATURATE :: 0x0308 // GL_SRC_ALPHA_SATURATE +CONSTANT_COLOR :: 0x8001 // GL_CONSTANT_COLOR +ONE_MINUS_CONSTANT_COLOR :: 0x8002 // GL_ONE_MINUS_CONSTANT_COLOR +CONSTANT_ALPHA :: 0x8003 // GL_CONSTANT_ALPHA +ONE_MINUS_CONSTANT_ALPHA :: 0x8004 // GL_ONE_MINUS_CONSTANT_ALPHA // GL blending functions/equations -RL_FUNC_ADD :: 0x8006 // GL_FUNC_ADD -RL_MIN :: 0x8007 // GL_MIN -RL_MAX :: 0x8008 // GL_MAX -RL_FUNC_SUBTRACT :: 0x800A // GL_FUNC_SUBTRACT -RL_FUNC_REVERSE_SUBTRACT :: 0x800B // GL_FUNC_REVERSE_SUBTRACT -RL_BLEND_EQUATION :: 0x8009 // GL_BLEND_EQUATION -RL_BLEND_EQUATION_RGB :: 0x8009 // GL_BLEND_EQUATION_RGB // (Same as BLEND_EQUATION) -RL_BLEND_EQUATION_ALPHA :: 0x883D // GL_BLEND_EQUATION_ALPHA -RL_BLEND_DST_RGB :: 0x80C8 // GL_BLEND_DST_RGB -RL_BLEND_SRC_RGB :: 0x80C9 // GL_BLEND_SRC_RGB -RL_BLEND_DST_ALPHA :: 0x80CA // GL_BLEND_DST_ALPHA -RL_BLEND_SRC_ALPHA :: 0x80CB // GL_BLEND_SRC_ALPHA -RL_BLEND_COLOR :: 0x8005 // GL_BLEND_COLOR +FUNC_ADD :: 0x8006 // GL_FUNC_ADD +MIN :: 0x8007 // GL_MIN +MAX :: 0x8008 // GL_MAX +FUNC_SUBTRACT :: 0x800A // GL_FUNC_SUBTRACT +FUNC_REVERSE_SUBTRACT :: 0x800B // GL_FUNC_REVERSE_SUBTRACT +BLEND_EQUATION :: 0x8009 // GL_BLEND_EQUATION +BLEND_EQUATION_RGB :: 0x8009 // GL_BLEND_EQUATION_RGB // (Same as BLEND_EQUATION) +BLEND_EQUATION_ALPHA :: 0x883D // GL_BLEND_EQUATION_ALPHA +BLEND_DST_RGB :: 0x80C8 // GL_BLEND_DST_RGB +BLEND_SRC_RGB :: 0x80C9 // GL_BLEND_SRC_RGB +BLEND_DST_ALPHA :: 0x80CA // GL_BLEND_DST_ALPHA +BLEND_SRC_ALPHA :: 0x80CB // GL_BLEND_SRC_ALPHA +BLEND_COLOR :: 0x8005 // GL_BLEND_COLOR //---------------------------------------------------------------------------------- @@ -255,7 +255,7 @@ RL_BLEND_COLOR :: 0x8005 // GL_BLEND_COLOR //---------------------------------------------------------------------------------- -VertexBufferIndexType :: c.ushort when RL_GRAPHICS_API_OPENGL_ES2 else c.uint +VertexBufferIndexType :: c.ushort when GRAPHICS_API_OPENGL_ES2 else c.uint // Dynamic vertex buffers (position + texcoords + colors + indices arrays) VertexBuffer :: struct { @@ -343,36 +343,39 @@ CullMode :: enum c.int { BACK, } -@(default_calling_convention="c") +// Matrix type (right handed, stored row major) +Matrix :: #row_major matrix[4, 4]f32 + +@(default_calling_convention="c", link_prefix="rl") foreign lib { //------------------------------------------------------------------------------------ // Functions Declaration - Matrix operations //------------------------------------------------------------------------------------ - rlMatrixMode :: proc(mode: c.int) --- // Choose the current matrix to be transformed - rlPushMatrix :: proc() --- // Push the current matrix to stack - rlPopMatrix :: proc() --- // Pop lattest inserted matrix from stack - rlLoadIdentity :: proc() --- // Reset current matrix to identity matrix - rlTranslatef :: proc(x, y, z: f32) --- // Multiply the current matrix by a translation matrix - rlRotatef :: proc(angleDeg: f32, x, y, z: f32) --- // Multiply the current matrix by a rotation matrix - rlScalef :: proc(x, y, z: f32) --- // Multiply the current matrix by a scaling matrix - rlMultMatrixf :: proc(matf: [^]f32) --- // Multiply the current matrix by another matrix - rlFrustum :: proc(left, right, bottom, top, znear, zfar: f64) --- - rlOrtho :: proc(left, right, bottom, top, znear, zfar: f64) --- - rlViewport :: proc(x, y, width, height: c.int) --- // Set the viewport area + MatrixMode :: proc(mode: c.int) --- // Choose the current matrix to be transformed + PushMatrix :: proc() --- // Push the current matrix to stack + PopMatrix :: proc() --- // Pop lattest inserted matrix from stack + LoadIdentity :: proc() --- // Reset current matrix to identity matrix + Translatef :: proc(x, y, z: f32) --- // Multiply the current matrix by a translation matrix + Rotatef :: proc(angleDeg: f32, x, y, z: f32) --- // Multiply the current matrix by a rotation matrix + Scalef :: proc(x, y, z: f32) --- // Multiply the current matrix by a scaling matrix + MultMatrixf :: proc(matf: [^]f32) --- // Multiply the current matrix by another matrix + Frustum :: proc(left, right, bottom, top, znear, zfar: f64) --- + Ortho :: proc(left, right, bottom, top, znear, zfar: f64) --- + Viewport :: proc(x, y, width, height: c.int) --- // Set the viewport area //------------------------------------------------------------------------------------ // Functions Declaration - Vertex level operations //------------------------------------------------------------------------------------ - rlBegin :: proc(mode: c.int) --- // Initialize drawing mode (how to organize vertex) - rlEnd :: proc() --- // Finish vertex providing - rlVertex2i :: proc(x, y: c.int) --- // Define one vertex (position) - 2 int - rlVertex2f :: proc(x, y: f32) --- // Define one vertex (position) - 2 f32 - rlVertex3f :: proc(x, y, z: f32) --- // Define one vertex (position) - 3 f32 - rlTexCoord2f :: proc(x, y: f32) --- // Define one vertex (texture coordinate) - 2 f32 - rlNormal3f :: proc(x, y, z: f32) --- // Define one vertex (normal) - 3 f32 - rlColor4ub :: proc(r, g, b, a: u8) --- // Define one vertex (color) - 4 byte - rlColor3f :: proc(x, y, z: f32) --- // Define one vertex (color) - 3 f32 - rlColor4f :: proc(x, y, z, w: f32) --- // Define one vertex (color) - 4 f32 + Begin :: proc(mode: c.int) --- // Initialize drawing mode (how to organize vertex) + End :: proc() --- // Finish vertex providing + Vertex2i :: proc(x, y: c.int) --- // Define one vertex (position) - 2 int + Vertex2f :: proc(x, y: f32) --- // Define one vertex (position) - 2 f32 + Vertex3f :: proc(x, y, z: f32) --- // Define one vertex (position) - 3 f32 + TexCoord2f :: proc(x, y: f32) --- // Define one vertex (texture coordinate) - 2 f32 + Normal3f :: proc(x, y, z: f32) --- // Define one vertex (normal) - 3 f32 + Color4ub :: proc(r, g, b, a: u8) --- // Define one vertex (color) - 4 byte + Color3f :: proc(x, y, z: f32) --- // Define one vertex (color) - 3 f32 + Color4f :: proc(x, y, z, w: f32) --- // Define one vertex (color) - 4 f32 //------------------------------------------------------------------------------------ // Functions Declaration - OpenGL style functions (common to 1.1, 3.3+, ES2) @@ -381,175 +384,177 @@ foreign lib { //------------------------------------------------------------------------------------ // Vertex buffers state - rlEnableVertexArray :: proc(vaoId: c.uint) -> bool --- // Enable vertex array (VAO, if supported) - rlDisableVertexArray :: proc() --- // Disable vertex array (VAO, if supported) - rlEnableVertexBuffer :: proc(id: c.uint) --- // Enable vertex buffer (VBO) - rlDisableVertexBuffer :: proc() --- // Disable vertex buffer (VBO) - rlEnableVertexBufferElement :: proc(id: c.uint) --- // Enable vertex buffer element (VBO element) - rlDisableVertexBufferElement :: proc() --- // Disable vertex buffer element (VBO element) - rlEnableVertexAttribute :: proc(index: c.uint) --- // Enable vertex attribute index - rlDisableVertexAttribute :: proc(index: c.uint) --- // Disable vertex attribute index - when RL_GRAPHICS_API_OPENGL_11 { - rlEnableStatePointer :: proc(vertexAttribType: c.int, buffer: rawptr) --- - rlDisableStatePointer :: proc(vertexAttribType: c.int) --- + EnableVertexArray :: proc(vaoId: c.uint) -> bool --- // Enable vertex array (VAO, if supported) + DisableVertexArray :: proc() --- // Disable vertex array (VAO, if supported) + EnableVertexBuffer :: proc(id: c.uint) --- // Enable vertex buffer (VBO) + DisableVertexBuffer :: proc() --- // Disable vertex buffer (VBO) + EnableVertexBufferElement :: proc(id: c.uint) --- // Enable vertex buffer element (VBO element) + DisableVertexBufferElement :: proc() --- // Disable vertex buffer element (VBO element) + EnableVertexAttribute :: proc(index: c.uint) --- // Enable vertex attribute index + DisableVertexAttribute :: proc(index: c.uint) --- // Disable vertex attribute index + when GRAPHICS_API_OPENGL_11 { + EnableStatePointer :: proc(vertexAttribType: c.int, buffer: rawptr) --- + DisableStatePointer :: proc(vertexAttribType: c.int) --- } // Textures state - rlActiveTextureSlot :: proc(slot: c.int) --- // Select and active a texture slot - rlEnableTexture :: proc(id: c.uint) --- // Enable texture - rlDisableTexture :: proc() --- // Disable texture - rlEnableTextureCubemap :: proc(id: c.uint) --- // Enable texture cubemap - rlDisableTextureCubemap :: proc() --- // Disable texture cubemap - rlTextureParameters :: proc(id: c.uint, param: c.int, value: c.int) --- // Set texture parameters (filter, wrap) - rlCubemapParameters :: proc(id: i32, param: c.int, value: c.int) --- // Set cubemap parameters (filter, wrap) + ActiveTextureSlot :: proc(slot: c.int) --- // Select and active a texture slot + EnableTexture :: proc(id: c.uint) --- // Enable texture + DisableTexture :: proc() --- // Disable texture + EnableTextureCubemap :: proc(id: c.uint) --- // Enable texture cubemap + DisableTextureCubemap :: proc() --- // Disable texture cubemap + TextureParameters :: proc(id: c.uint, param: c.int, value: c.int) --- // Set texture parameters (filter, wrap) + CubemapParameters :: proc(id: i32, param: c.int, value: c.int) --- // Set cubemap parameters (filter, wrap) // Shader state - rlEnableShader :: proc(id: c.uint) --- // Enable shader program - rlDisableShader :: proc() --- // Disable shader program + EnableShader :: proc(id: c.uint) --- // Enable shader program + DisableShader :: proc() --- // Disable shader program // Framebuffer state - rlEnableFramebuffer :: proc(id: c.uint) --- // Enable render texture (fbo) - rlDisableFramebuffer :: proc() --- // Disable render texture (fbo), return to default framebuffer - rlActiveDrawBuffers :: proc(count: c.int) --- // Activate multiple draw color buffers - rlBlitFramebuffer :: proc(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, bufferMask: c.int) --- // Blit active framebuffer to main framebuffer + EnableFramebuffer :: proc(id: c.uint) --- // Enable render texture (fbo) + DisableFramebuffer :: proc() --- // Disable render texture (fbo), return to default framebuffer + ActiveDrawBuffers :: proc(count: c.int) --- // Activate multiple draw color buffers + BlitFramebuffer :: proc(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, bufferMask: c.int) --- // Blit active framebuffer to main framebuffer // General render state - rlDisableColorBlend :: proc() --- // Disable color blending - rlEnableDepthTest :: proc() --- // Enable depth test - rlDisableDepthTest :: proc() --- // Disable depth test - rlEnableDepthMask :: proc() --- // Enable depth write - rlDisableDepthMask :: proc() --- // Disable depth write - rlEnableBackfaceCulling :: proc() --- // Enable backface culling - rlDisableBackfaceCulling :: proc() --- // Disable backface culling - rlSetCullFace :: proc(mode: CullMode) --- // Set face culling mode - rlEnableScissorTest :: proc() --- // Enable scissor test - rlDisableScissorTest :: proc() --- // Disable scissor test - rlScissor :: proc(x, y, width, height: c.int) --- // Scissor test - rlEnableWireMode :: proc() --- // Enable wire mode - rlEnablePointMode :: proc() --- // Enable point mode - rlDisableWireMode :: proc() --- // Disable wire and point modes - rlSetLineWidth :: proc(width: f32) --- // Set the line drawing width - rlGetLineWidth :: proc() -> f32 --- // Get the line drawing width - rlEnableSmoothLines :: proc() --- // Enable line aliasing - rlDisableSmoothLines :: proc() --- // Disable line aliasing - rlEnableStereoRender :: proc() --- // Enable stereo rendering - rlDisableStereoRender :: proc() --- // Disable stereo rendering - rlIsStereoRenderEnabled :: proc() -> bool --- // Check if stereo render is enabled + DisableColorBlend :: proc() --- // Disable color blending + EnableDepthTest :: proc() --- // Enable depth test + DisableDepthTest :: proc() --- // Disable depth test + EnableDepthMask :: proc() --- // Enable depth write + DisableDepthMask :: proc() --- // Disable depth write + EnableBackfaceCulling :: proc() --- // Enable backface culling + DisableBackfaceCulling :: proc() --- // Disable backface culling + SetCullFace :: proc(mode: CullMode) --- // Set face culling mode + EnableScissorTest :: proc() --- // Enable scissor test + DisableScissorTest :: proc() --- // Disable scissor test + Scissor :: proc(x, y, width, height: c.int) --- // Scissor test + EnableWireMode :: proc() --- // Enable wire mode + EnablePointMode :: proc() --- // Enable point mode + DisableWireMode :: proc() --- // Disable wire and point modes + SetLineWidth :: proc(width: f32) --- // Set the line drawing width + GetLineWidth :: proc() -> f32 --- // Get the line drawing width + EnableSmoothLines :: proc() --- // Enable line aliasing + DisableSmoothLines :: proc() --- // Disable line aliasing + EnableStereoRender :: proc() --- // Enable stereo rendering + DisableStereoRender :: proc() --- // Disable stereo rendering + IsStereoRenderEnabled :: proc() -> bool --- // Check if stereo render is enabled - rlClearColor :: proc(r, g, b, a: u8) --- // Clear color buffer with color - rlClearScreenBuffers :: proc() --- // Clear used screen buffers (color and depth) - rlCheckErrors :: proc() --- // Check and log OpenGL error codes - rlSetBlendMode :: proc(mode: c.int) --- // Set blending mode - rlSetBlendFactors :: proc(glSrcFactor, glDstFactor, glEquation: c.int) --- // Set blending mode factor and equation (using OpenGL factors) - rlSetBlendFactorsSeparate :: proc(glSrcRGB, glDstRGB, glSrcAlpha, glDstAlpha, glEqRGB, glEqAlpha: c.int) --- // Set blending mode factors and equations separately (using OpenGL factors) + ClearColor :: proc(r, g, b, a: u8) --- // Clear color buffer with color + ClearScreenBuffers :: proc() --- // Clear used screen buffers (color and depth) + CheckErrors :: proc() --- // Check and log OpenGL error codes + SetBlendMode :: proc(mode: c.int) --- // Set blending mode + SetBlendFactors :: proc(glSrcFactor, glDstFactor, glEquation: c.int) --- // Set blending mode factor and equation (using OpenGL factors) + SetBlendFactorsSeparate :: proc(glSrcRGB, glDstRGB, glSrcAlpha, glDstAlpha, glEqRGB, glEqAlpha: c.int) --- // Set blending mode factors and equations separately (using OpenGL factors) //------------------------------------------------------------------------------------ // Functions Declaration - rlgl functionality //------------------------------------------------------------------------------------ // rlgl initialization functions - rlglInit :: proc(width, height: c.int) --- // Initialize rlgl (buffers, shaders, textures, states) - rlglClose :: proc() --- // De-initialize rlgl (buffers, shaders, textures) - rlLoadExtensions :: proc(loader: rawptr) --- // Load OpenGL extensions (loader function required) - rlGetVersion :: proc() -> GlVersion --- // Get current OpenGL version - rlSetFramebufferWidth :: proc(width: c.int) --- // Set current framebuffer width - rlGetFramebufferWidth :: proc() -> c.int --- // Get default framebuffer width - rlSetFramebufferHeight :: proc(height: c.int) --- // Set current framebuffer height - rlGetFramebufferHeight :: proc() -> c.int --- // Get default framebuffer height + @(link_prefix="rlgl") + Init :: proc(width, height: c.int) --- // Initialize rlgl (buffers, shaders, textures, states) + @(link_prefix="rlgl") + Close :: proc() --- // De-initialize rlgl (buffers, shaders, textures) + LoadExtensions :: proc(loader: rawptr) --- // Load OpenGL extensions (loader function required) + GetVersion :: proc() -> GlVersion --- // Get current OpenGL version + SetFramebufferWidth :: proc(width: c.int) --- // Set current framebuffer width + GetFramebufferWidth :: proc() -> c.int --- // Get default framebuffer width + SetFramebufferHeight :: proc(height: c.int) --- // Set current framebuffer height + GetFramebufferHeight :: proc() -> c.int --- // Get default framebuffer height - rlGetTextureIdDefault :: proc() -> c.uint --- // Get default texture id - rlGetShaderIdDefault :: proc() -> c.uint --- // Get default shader id - rlGetShaderLocsDefault :: proc() -> [^]c.int --- // Get default shader locations + GetTextureIdDefault :: proc() -> c.uint --- // Get default texture id + GetShaderIdDefault :: proc() -> c.uint --- // Get default shader id + GetShaderLocsDefault :: proc() -> [^]c.int --- // Get default shader locations // Render batch management // NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode // but this render batch API is exposed in case of custom batches are required - rlLoadRenderBatch :: proc(numBuffers, bufferElements: c.int) -> RenderBatch --- // Load a render batch system - rlUnloadRenderBatch :: proc(batch: RenderBatch) --- // Unload render batch system - rlDrawRenderBatch :: proc(batch: ^RenderBatch) --- // Draw render batch data (Update->Draw->Reset) - rlSetRenderBatchActive :: proc(batch: ^RenderBatch) --- // Set the active render batch for rlgl (NULL for default internal) - rlDrawRenderBatchActive :: proc() --- // Update and draw internal render batch - rlCheckRenderBatchLimit :: proc(vCount: c.int) -> c.int --- // Check internal buffer overflow for a given number of vertex + LoadRenderBatch :: proc(numBuffers, bufferElements: c.int) -> RenderBatch --- // Load a render batch system + UnloadRenderBatch :: proc(batch: RenderBatch) --- // Unload render batch system + DrawRenderBatch :: proc(batch: ^RenderBatch) --- // Draw render batch data (Update->Draw->Reset) + SetRenderBatchActive :: proc(batch: ^RenderBatch) --- // Set the active render batch for rlgl (NULL for default internal) + DrawRenderBatchActive :: proc() --- // Update and draw internal render batch + CheckRenderBatchLimit :: proc(vCount: c.int) -> c.int --- // Check internal buffer overflow for a given number of vertex - rlSetTexture :: proc(id: c.uint) --- // Set current texture for render batch and check buffers limits + SetTexture :: proc(id: c.uint) --- // Set current texture for render batch and check buffers limits //------------------------------------------------------------------------------------------------------------------------ // Vertex buffers management - rlLoadVertexArray :: proc() -> c.uint --- // Load vertex array (vao) if supported - rlLoadVertexBuffer :: proc(buffer: rawptr, size: c.int, is_dynamic: bool) -> c.uint --- // Load a vertex buffer attribute - rlLoadVertexBufferElement :: proc(buffer: rawptr, size: c.int, is_dynamic: bool) -> c.uint --- // Load a new attributes element buffer - rlUpdateVertexBuffer :: proc(bufferId: c.uint, data: rawptr, dataSize: c.int, offset: c.int) --- // Update GPU buffer with new data - rlUpdateVertexBufferElements :: proc(id: c.uint, data: rawptr, dataSize: c.int, offset: c.int) --- // Update vertex buffer elements with new data - rlUnloadVertexArray :: proc(vaoId: c.uint) --- - rlUnloadVertexBuffer :: proc(vboId: c.uint) --- - rlSetVertexAttribute :: proc(index: c.uint, compSize: c.int, type: c.int, normalized: bool, stride: c.int, pointer: rawptr) --- - rlSetVertexAttributeDivisor :: proc(index: c.uint, divisor: c.int) --- - rlSetVertexAttributeDefault :: proc(locIndex: c.int, value: rawptr, attribType: c.int, count: c.int) --- // Set vertex attribute default value - rlDrawVertexArray :: proc(offset: c.int, count: c.int) --- - rlDrawVertexArrayElements :: proc(offset: c.int, count: c.int, buffer: rawptr) --- - rlDrawVertexArrayInstanced :: proc(offset: c.int, count: c.int, instances: c.int) --- - rlDrawVertexArrayElementsInstanced :: proc(offset: c.int, count: c.int, buffer: rawptr, instances: c.int) --- + LoadVertexArray :: proc() -> c.uint --- // Load vertex array (vao) if supported + LoadVertexBuffer :: proc(buffer: rawptr, size: c.int, is_dynamic: bool) -> c.uint --- // Load a vertex buffer attribute + LoadVertexBufferElement :: proc(buffer: rawptr, size: c.int, is_dynamic: bool) -> c.uint --- // Load a new attributes element buffer + UpdateVertexBuffer :: proc(bufferId: c.uint, data: rawptr, dataSize: c.int, offset: c.int) --- // Update GPU buffer with new data + UpdateVertexBufferElements :: proc(id: c.uint, data: rawptr, dataSize: c.int, offset: c.int) --- // Update vertex buffer elements with new data + UnloadVertexArray :: proc(vaoId: c.uint) --- + UnloadVertexBuffer :: proc(vboId: c.uint) --- + SetVertexAttribute :: proc(index: c.uint, compSize: c.int, type: c.int, normalized: bool, stride: c.int, pointer: rawptr) --- + SetVertexAttributeDivisor :: proc(index: c.uint, divisor: c.int) --- + SetVertexAttributeDefault :: proc(locIndex: c.int, value: rawptr, attribType: c.int, count: c.int) --- // Set vertex attribute default value + DrawVertexArray :: proc(offset: c.int, count: c.int) --- + DrawVertexArrayElements :: proc(offset: c.int, count: c.int, buffer: rawptr) --- + DrawVertexArrayInstanced :: proc(offset: c.int, count: c.int, instances: c.int) --- + DrawVertexArrayElementsInstanced :: proc(offset: c.int, count: c.int, buffer: rawptr, instances: c.int) --- // Textures management - rlLoadTexture :: proc(data: rawptr, width, height: c.int, format: c.int, mipmapCount: c.int) -> c.uint --- // Load texture in GPU - rlLoadTextureDepth :: proc(width, height: c.int, useRenderBuffer: bool) -> c.uint --- // Load depth texture/renderbuffer (to be attached to fbo) - rlLoadTextureCubemap :: proc(data: rawptr, size: c.int, format: c.int) -> c.uint --- // Load texture cubemap - rlUpdateTexture :: proc(id: c.uint, offsetX, offsetY: c.int, width, height: c.int, format: c.int, data: rawptr) --- // Update GPU texture with new data - rlGetGlTextureFormats :: proc(format: c.int, glInternalFormat, glFormat, glType: ^c.uint) --- // Get OpenGL internal formats - rlGetPixelFormatName :: proc(format: c.uint) -> cstring --- // Get name string for pixel format - rlUnloadTexture :: proc(id: c.uint) --- // Unload texture from GPU memory - rlGenTextureMipmaps :: proc(id: c.uint, width, height: c.int, format: c.int, mipmaps: ^c.int) --- // Generate mipmap data for selected texture - rlReadTexturePixels :: proc(id: c.uint, width, height: c.int, format: c.int) -> rawptr --- // Read texture pixel data - rlReadScreenPixels :: proc(width, height: c.int) -> [^]byte --- // Read screen pixel data (color buffer) + LoadTexture :: proc(data: rawptr, width, height: c.int, format: c.int, mipmapCount: c.int) -> c.uint --- // Load texture in GPU + LoadTextureDepth :: proc(width, height: c.int, useRenderBuffer: bool) -> c.uint --- // Load depth texture/renderbuffer (to be attached to fbo) + LoadTextureCubemap :: proc(data: rawptr, size: c.int, format: c.int) -> c.uint --- // Load texture cubemap + UpdateTexture :: proc(id: c.uint, offsetX, offsetY: c.int, width, height: c.int, format: c.int, data: rawptr) --- // Update GPU texture with new data + GetGlTextureFormats :: proc(format: c.int, glInternalFormat, glFormat, glType: ^c.uint) --- // Get OpenGL internal formats + GetPixelFormatName :: proc(format: c.uint) -> cstring --- // Get name string for pixel format + UnloadTexture :: proc(id: c.uint) --- // Unload texture from GPU memory + GenTextureMipmaps :: proc(id: c.uint, width, height: c.int, format: c.int, mipmaps: ^c.int) --- // Generate mipmap data for selected texture + ReadTexturePixels :: proc(id: c.uint, width, height: c.int, format: c.int) -> rawptr --- // Read texture pixel data + ReadScreenPixels :: proc(width, height: c.int) -> [^]byte --- // Read screen pixel data (color buffer) // Framebuffer management (fbo) - rlLoadFramebuffer :: proc(width, height: c.int) -> c.uint --- // Load an empty framebuffer - rlFramebufferAttach :: proc(fboId, texId: c.uint, attachType: c.int, texType: c.int, mipLevel: c.int) --- // Attach texture/renderbuffer to a framebuffer - rlFramebufferComplete :: proc(id: c.uint) -> bool --- // Verify framebuffer is complete - rlUnloadFramebuffer :: proc(id: c.uint) --- // Delete framebuffer from GPU + LoadFramebuffer :: proc(width, height: c.int) -> c.uint --- // Load an empty framebuffer + FramebufferAttach :: proc(fboId, texId: c.uint, attachType: c.int, texType: c.int, mipLevel: c.int) --- // Attach texture/renderbuffer to a framebuffer + FramebufferComplete :: proc(id: c.uint) -> bool --- // Verify framebuffer is complete + UnloadFramebuffer :: proc(id: c.uint) --- // Delete framebuffer from GPU // Shaders management - rlLoadShaderCode :: proc(vsCode, fsCode: cstring) -> c.uint --- // Load shader from code strings - rlCompileShader :: proc(shaderCode: cstring, type: c.int) -> c.uint --- // Compile custom shader and return shader id (type: RL_VERTEX_SHADER, RL_FRAGMENT_SHADER, RL_COMPUTE_SHADER) - rlLoadShaderProgram :: proc(vShaderId, fShaderId: c.uint) -> c.uint --- // Load custom shader program - rlUnloadShaderProgram :: proc(id: c.uint) --- // Unload shader program - rlGetLocationUniform :: proc(shaderId: c.uint, uniformName: cstring) -> c.int --- // Get shader location uniform - rlGetLocationAttrib :: proc(shaderId: c.uint, attribName: cstring) -> c.int --- // Get shader location attribute - rlSetUniform :: proc(locIndex: c.int, value: rawptr, uniformType: c.int, count: c.int) --- // Set shader value uniform - rlSetUniformMatrix :: proc(locIndex: c.int, mat: Matrix) --- // Set shader value matrix - rlSetUniformSampler :: proc(locIndex: c.int, textureId: c.uint) --- // Set shader value sampler - rlSetShader :: proc(id: c.uint, locs: [^]c.int) --- // Set shader currently active (id and locations) + LoadShaderCode :: proc(vsCode, fsCode: cstring) -> c.uint --- // Load shader from code strings + CompileShader :: proc(shaderCode: cstring, type: c.int) -> c.uint --- // Compile custom shader and return shader id (type: VERTEX_SHADER, FRAGMENT_SHADER, COMPUTE_SHADER) + LoadShaderProgram :: proc(vShaderId, fShaderId: c.uint) -> c.uint --- // Load custom shader program + UnloadShaderProgram :: proc(id: c.uint) --- // Unload shader program + GetLocationUniform :: proc(shaderId: c.uint, uniformName: cstring) -> c.int --- // Get shader location uniform + GetLocationAttrib :: proc(shaderId: c.uint, attribName: cstring) -> c.int --- // Get shader location attribute + SetUniform :: proc(locIndex: c.int, value: rawptr, uniformType: c.int, count: c.int) --- // Set shader value uniform + SetUniformMatrix :: proc(locIndex: c.int, mat: Matrix) --- // Set shader value matrix + SetUniformSampler :: proc(locIndex: c.int, textureId: c.uint) --- // Set shader value sampler + SetShader :: proc(id: c.uint, locs: [^]c.int) --- // Set shader currently active (id and locations) // Compute shader management - rlLoadComputeShaderProgram :: proc(shaderId: c.uint) -> c.uint --- // Load compute shader program - rlComputeShaderDispatch :: proc(groupX, groupY, groupZ: c.uint) --- // Dispatch compute shader (equivalent to *draw* for graphics pipeline) + LoadComputeShaderProgram :: proc(shaderId: c.uint) -> c.uint --- // Load compute shader program + ComputeShaderDispatch :: proc(groupX, groupY, groupZ: c.uint) --- // Dispatch compute shader (equivalent to *draw* for graphics pipeline) // Shader buffer storage object management (ssbo) - rlLoadShaderBuffer :: proc(size: c.uint, data: rawptr, usageHint: c.int) -> c.uint --- // Load shader storage buffer object (SSBO) - rlUnloadShaderBuffer :: proc(ssboId: c.uint) --- // Unload shader storage buffer object (SSBO) - rlUpdateShaderBuffer :: proc(id: c.uint, data: rawptr, dataSize: c.uint, offset: c.uint) --- // Update SSBO buffer data - rlBindShaderBuffer :: proc(id: c.uint, index: c.uint) --- // Bind SSBO buffer - rlReadShaderBuffer :: proc(id: c.uint, dest: rawptr, count: c.uint, offset: c.uint) --- // Read SSBO buffer data (GPU->CPU) - rlCopyShaderBuffer :: proc(destId, srcId: c.uint, destOffset, srcOffset: c.uint, count: c.uint) --- // Copy SSBO data between buffers - rlGetShaderBufferSize :: proc(id: c.uint) -> c.uint --- // Get SSBO buffer size + LoadShaderBuffer :: proc(size: c.uint, data: rawptr, usageHint: c.int) -> c.uint --- // Load shader storage buffer object (SSBO) + UnloadShaderBuffer :: proc(ssboId: c.uint) --- // Unload shader storage buffer object (SSBO) + UpdateShaderBuffer :: proc(id: c.uint, data: rawptr, dataSize: c.uint, offset: c.uint) --- // Update SSBO buffer data + BindShaderBuffer :: proc(id: c.uint, index: c.uint) --- // Bind SSBO buffer + ReadShaderBuffer :: proc(id: c.uint, dest: rawptr, count: c.uint, offset: c.uint) --- // Read SSBO buffer data (GPU->CPU) + CopyShaderBuffer :: proc(destId, srcId: c.uint, destOffset, srcOffset: c.uint, count: c.uint) --- // Copy SSBO data between buffers + GetShaderBufferSize :: proc(id: c.uint) -> c.uint --- // Get SSBO buffer size // Buffer management - rlBindImageTexture :: proc(id: c.uint, index: c.uint, format: c.int, readonly: bool) --- // Bind image texture + BindImageTexture :: proc(id: c.uint, index: c.uint, format: c.int, readonly: bool) --- // Bind image texture // Matrix state management - rlGetMatrixModelview :: proc() -> Matrix --- // Get internal modelview matrix - rlGetMatrixProjection :: proc() -> Matrix --- // Get internal projection matrix - rlGetMatrixTransform :: proc() -> Matrix --- // Get internal accumulated transform matrix - rlGetMatrixProjectionStereo :: proc(eye: c.int) -> Matrix --- // Get internal projection matrix for stereo render (selected eye) - rlGetMatrixViewOffsetStereo :: proc(eye: c.int) -> Matrix --- // Get internal view offset matrix for stereo render (selected eye) - rlSetMatrixProjection :: proc(proj: Matrix) --- // Set a custom projection matrix (replaces internal projection matrix) - rlSetMatrixModelview :: proc(view: Matrix) --- // Set a custom modelview matrix (replaces internal modelview matrix) - rlSetMatrixProjectionStereo :: proc(right, left: Matrix) --- // Set eyes projection matrices for stereo rendering - rlSetMatrixViewOffsetStereo :: proc(right, left: Matrix) --- // Set eyes view offsets matrices for stereo rendering + GetMatrixModelview :: proc() -> Matrix --- // Get internal modelview matrix + GetMatrixProjection :: proc() -> Matrix --- // Get internal projection matrix + GetMatrixTransform :: proc() -> Matrix --- // Get internal accumulated transform matrix + GetMatrixProjectionStereo :: proc(eye: c.int) -> Matrix --- // Get internal projection matrix for stereo render (selected eye) + GetMatrixViewOffsetStereo :: proc(eye: c.int) -> Matrix --- // Get internal view offset matrix for stereo render (selected eye) + SetMatrixProjection :: proc(proj: Matrix) --- // Set a custom projection matrix (replaces internal projection matrix) + SetMatrixModelview :: proc(view: Matrix) --- // Set a custom modelview matrix (replaces internal modelview matrix) + SetMatrixProjectionStereo :: proc(right, left: Matrix) --- // Set eyes projection matrices for stereo rendering + SetMatrixViewOffsetStereo :: proc(right, left: Matrix) --- // Set eyes view offsets matrices for stereo rendering // Quick and dirty cube/quad buffers load->draw->unload - rlLoadDrawCube :: proc() --- // Load and draw a cube - rlLoadDrawQuad :: proc() --- // Load and draw a quad + LoadDrawCube :: proc() --- // Load and draw a cube + LoadDrawQuad :: proc() --- // Load and draw a quad } From c9e732d14172f5bdd1c69befacd8e49343f0f6c2 Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Tue, 11 Jun 2024 08:59:30 +0200 Subject: [PATCH 249/270] rlgl.RLGL_VERSION -> rlgl.VERSION --- vendor/raylib/rlgl/rlgl.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/raylib/rlgl/rlgl.odin b/vendor/raylib/rlgl/rlgl.odin index 4a4ab4b7f..b36764830 100644 --- a/vendor/raylib/rlgl/rlgl.odin +++ b/vendor/raylib/rlgl/rlgl.odin @@ -109,7 +109,7 @@ package rlgl import "core:c" -RLGL_VERSION :: "5.0" +VERSION :: "5.0" when ODIN_OS == .Windows { foreign import lib { From 76292c8ed5c78f714d64f32bb1ea231cb64152c1 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 11 Jun 2024 00:46:10 -0400 Subject: [PATCH 250/270] Forbid all BSDs from running `core:net` tests `net` is not yet implemented on them. --- tests/core/net/test_core_net.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index 6aada88a6..f806463e9 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -8,7 +8,7 @@ A test suite for `core:net` */ -//+build !netbsd +//+build !netbsd !freebsd !openbsd package test_core_net import "core:testing" From c68560c5737b16c71a25aa0b11cfddc139a7c565 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 11 Jun 2024 01:03:37 -0400 Subject: [PATCH 251/270] Use correct `__error` link name for FreeBSD --- core/path/filepath/path_unix.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/path/filepath/path_unix.odin b/core/path/filepath/path_unix.odin index a4b27b027..b44a6a344 100644 --- a/core/path/filepath/path_unix.odin +++ b/core/path/filepath/path_unix.odin @@ -56,7 +56,7 @@ foreign libc { @(link_name="free") _unix_free :: proc(ptr: rawptr) --- } -when ODIN_OS == .Darwin { +when ODIN_OS == .Darwin || ODIN_OS == .FreeBSD { @(private) foreign libc { @(link_name="__error") __error :: proc() -> ^i32 --- From 40e99ebb1018914813d8b74ab9db222174b705dc Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 11 Jun 2024 03:34:10 -0400 Subject: [PATCH 252/270] Fix file open `O_*` flags on FreeBSD --- core/os/os_freebsd.odin | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index cdd44d301..1b32128d6 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -112,15 +112,15 @@ EOWNERDEAD: Errno : 96 O_RDONLY :: 0x00000 O_WRONLY :: 0x00001 O_RDWR :: 0x00002 -O_CREATE :: 0x00040 -O_EXCL :: 0x00080 -O_NOCTTY :: 0x00100 -O_TRUNC :: 0x00200 -O_NONBLOCK :: 0x00800 -O_APPEND :: 0x00400 -O_SYNC :: 0x01000 -O_ASYNC :: 0x02000 -O_CLOEXEC :: 0x80000 +O_NONBLOCK :: 0x00004 +O_APPEND :: 0x00008 +O_ASYNC :: 0x00040 +O_SYNC :: 0x00080 +O_CREATE :: 0x00200 +O_TRUNC :: 0x00400 +O_EXCL :: 0x00800 +O_NOCTTY :: 0x08000 +O_CLOEXEC :: 0100000 SEEK_DATA :: 3 From a3da796d5410efd22a991bdd82fec947a0a11471 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 11 Jun 2024 03:42:13 -0400 Subject: [PATCH 253/270] Fix `file_size` on FreeBSD It was using the generic UNIX `fstat` implemented in Odin, which is more than what is needed here. This also avoids the issue of needing a proper `absolute_path_from_handle` implementation for it to work without error. --- core/os/os_freebsd.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index 1b32128d6..45f3ec23c 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -365,7 +365,7 @@ seek :: proc(fd: Handle, offset: i64, whence: int) -> (i64, Errno) { } file_size :: proc(fd: Handle) -> (i64, Errno) { - s, err := fstat(fd) + s, err := _fstat(fd) if err != ERROR_NONE { return -1, err } From fc88de12c2284edb3eb5b1d22d1fd4924ea82aaa Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 11 Jun 2024 04:13:09 -0400 Subject: [PATCH 254/270] Implement `absolute_path_from_handle` for FreeBSD --- core/os/os_freebsd.odin | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/core/os/os_freebsd.odin b/core/os/os_freebsd.odin index 45f3ec23c..8fe179478 100644 --- a/core/os/os_freebsd.odin +++ b/core/os/os_freebsd.odin @@ -140,6 +140,8 @@ RTLD_NOLOAD :: 0x02000 MAX_PATH :: 1024 +KINFO_FILE_SIZE :: 1392 + args := _alloc_command_line_arguments() Unix_File_Time :: struct { @@ -191,6 +193,21 @@ OS_Stat :: struct { lspare: [10]u64, } +KInfo_File :: struct { + structsize: c.int, + type: c.int, + fd: c.int, + ref_count: c.int, + flags: c.int, + pad0: c.int, + offset: i64, + + // NOTE(Feoramund): This field represents a complicated union that I am + // avoiding implementing for now. I only need the path data below. + _union: [336]byte, + + path: [MAX_PATH]c.char, +} // since FreeBSD v12 Dirent :: struct { @@ -254,6 +271,8 @@ X_OK :: 1 // Test for execute permission W_OK :: 2 // Test for write permission R_OK :: 4 // Test for read permission +F_KINFO :: 22 + foreign libc { @(link_name="__error") __errno_location :: proc() -> ^c.int --- @@ -274,6 +293,7 @@ foreign libc { @(link_name="unlink") _unix_unlink :: proc(path: cstring) -> c.int --- @(link_name="rmdir") _unix_rmdir :: proc(path: cstring) -> c.int --- @(link_name="mkdir") _unix_mkdir :: proc(path: cstring, mode: mode_t) -> c.int --- + @(link_name="fcntl") _unix_fcntl :: proc(fd: Handle, cmd: c.int, arg: uintptr) -> c.int --- @(link_name="fdopendir") _unix_fdopendir :: proc(fd: Handle) -> Dir --- @(link_name="closedir") _unix_closedir :: proc(dirp: Dir) -> c.int --- @@ -591,9 +611,26 @@ _readlink :: proc(path: string) -> (string, Errno) { return "", Errno{} } -// XXX FreeBSD absolute_path_from_handle :: proc(fd: Handle) -> (string, Errno) { - return "", Errno(ENOSYS) + // NOTE(Feoramund): The situation isn't ideal, but this was the best way I + // could find to implement this. There are a couple outstanding bug reports + // regarding the desire to retrieve an absolute path from a handle, but to + // my knowledge, there hasn't been any work done on it. + // + // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198570 + // + // This may be unreliable, according to a comment from 2023. + + kinfo: KInfo_File + kinfo.structsize = KINFO_FILE_SIZE + + res := _unix_fcntl(fd, F_KINFO, cast(uintptr)&kinfo) + if res == -1 { + return "", Errno(get_last_error()) + } + + path := strings.clone_from_cstring_bounded(cast(cstring)&kinfo.path[0], len(kinfo.path)) + return path, ERROR_NONE } absolute_path_from_relative :: proc(rel: string) -> (path: string, err: Errno) { From 0e6bcd0dbb8d5d979b2c7b772907d9576ed586ff Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 11 Jun 2024 00:30:31 -0400 Subject: [PATCH 255/270] Add FreeBSD to CI --- .github/workflows/ci.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66c848a8a..c9c453331 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,35 @@ jobs: ./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false ./odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false (cd tests/issues; ./run.sh) + build_freebsd: + name: FreeBSD Build, Check, and Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build, Check, and Test + timeout-minutes: 15 + uses: vmactions/freebsd-vm@v1 + with: + usesh: true + copyback: false + prepare: | + pkg install -y gmake git bash python3 libxml2 llvm17 + run: | + # `set -e` is needed for test failures to register. https://github.com/vmactions/freebsd-vm/issues/72 + set -e -x + git config --global --add safe.directory $(pwd) + gmake release + ./odin version + ./odin report + gmake -C vendor/stb/src + gmake -C vendor/cgltf/src + gmake -C vendor/miniaudio/src + ./odin check examples/all -vet -strict-style -target:freebsd_amd64 + ./odin test tests/core/normal.odin -file -all-packages -define:ODIN_TEST_FANCY=false + ./odin test tests/core/speed.odin -file -all-packages -o:speed -define:ODIN_TEST_FANCY=false + ./odin test tests/vendor -all-packages -define:ODIN_TEST_FANCY=false + ./odin test tests/benchmark -all-packages -define:ODIN_TEST_FANCY=false + (cd tests/issues; ./run.sh) ci: strategy: fail-fast: false From 61c630bbf8367ab473897b066175990d0996bd14 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Tue, 11 Jun 2024 05:23:43 -0400 Subject: [PATCH 256/270] Fix #3730 --- src/check_decl.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 13b14149a..8f4d9f922 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -89,6 +89,9 @@ gb_internal Type *check_init_variable(CheckerContext *ctx, Entity *e, Operand *o return nullptr; } else if (is_type_polymorphic(t)) { Entity *e = entity_of_node(operand->expr); + if (e == nullptr) { + return nullptr; + } if (e->state.load() != EntityState_Resolved) { gbString str = type_to_string(t); defer (gb_string_free(str)); From 9b0e87544a2de1f817be78f0183c8485870d9fcf Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 11 Jun 2024 12:07:22 +0100 Subject: [PATCH 257/270] Unify LLVMVerifyFunction invocations into on place --- src/llvm_backend.cpp | 101 ++++++++++++++++++++----------------------- 1 file changed, 47 insertions(+), 54 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 5dc6d94d5..01680ffa9 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1125,6 +1125,49 @@ gb_internal void lb_finalize_objc_names(lbProcedure *p) { lb_end_procedure_body(p); } +gb_internal void lb_verify_function(lbModule *m, lbProcedure *p, bool dump_ll=false) { + if (!m->debug_builder && LLVMVerifyFunction(p->value, LLVMReturnStatusAction)) { + char *llvm_error = nullptr; + + gb_printf_err("LLVM CODE GEN FAILED FOR PROCEDURE: %.*s\n", LIT(p->name)); + LLVMDumpValue(p->value); + gb_printf_err("\n"); + if (dump_ll) { + gb_printf_err("\n\n\n"); + String filepath_ll = lb_filepath_ll_for_module(m); + if (LLVMPrintModuleToFile(m->mod, cast(char const *)filepath_ll.text, &llvm_error)) { + gb_printf_err("LLVM Error: %s\n", llvm_error); + } + } + LLVMVerifyFunction(p->value, LLVMPrintMessageAction); + exit_with_errors(); + } +} + +gb_internal WORKER_TASK_PROC(lb_llvm_module_verification_worker_proc) { + char *llvm_error = nullptr; + defer (LLVMDisposeMessage(llvm_error)); + lbModule *m = cast(lbModule *)data; + + if (LLVMVerifyModule(m->mod, LLVMReturnStatusAction, &llvm_error)) { + gb_printf_err("LLVM Error:\n%s\n", llvm_error); + if (build_context.keep_temp_files) { + TIME_SECTION("LLVM Print Module to File"); + String filepath_ll = lb_filepath_ll_for_module(m); + if (LLVMPrintModuleToFile(m->mod, cast(char const *)filepath_ll.text, &llvm_error)) { + gb_printf_err("LLVM Error: %s\n", llvm_error); + exit_with_errors(); + return false; + } + } + exit_with_errors(); + return 1; + } + return 0; +} + + + gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProcedure *objc_names, Array &global_variables) { // Startup Runtime Type *proc_type = alloc_type_proc(nullptr, nullptr, 0, nullptr, 0, false, ProcCC_Odin); @@ -1227,13 +1270,7 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc lb_end_procedure_body(p); - if (!main_module->debug_builder && LLVMVerifyFunction(p->value, LLVMReturnStatusAction)) { - gb_printf_err("LLVM CODE GEN FAILED FOR PROCEDURE: %s\n", "main"); - LLVMDumpValue(p->value); - gb_printf_err("\n\n\n\n"); - LLVMVerifyFunction(p->value, LLVMAbortProcessAction); - } - + lb_verify_function(main_module, p); return p; } @@ -1256,13 +1293,7 @@ gb_internal lbProcedure *lb_create_cleanup_runtime(lbModule *main_module) { // C lb_end_procedure_body(p); - if (!main_module->debug_builder && LLVMVerifyFunction(p->value, LLVMReturnStatusAction)) { - gb_printf_err("LLVM CODE GEN FAILED FOR PROCEDURE: %s\n", "main"); - LLVMDumpValue(p->value); - gb_printf_err("\n\n\n\n"); - LLVMVerifyFunction(p->value, LLVMAbortProcessAction); - } - + lb_verify_function(main_module, p); return p; } @@ -2523,27 +2554,6 @@ gb_internal String lb_filepath_obj_for_module(lbModule *m) { return concatenate_strings(permanent_allocator(), path, ext); } -gb_internal WORKER_TASK_PROC(lb_llvm_module_verification_worker_proc) { - char *llvm_error = nullptr; - defer (LLVMDisposeMessage(llvm_error)); - lbModule *m = cast(lbModule *)data; - if (LLVMVerifyModule(m->mod, LLVMReturnStatusAction, &llvm_error)) { - gb_printf_err("LLVM Error:\n%s\n", llvm_error); - if (build_context.keep_temp_files) { - TIME_SECTION("LLVM Print Module to File"); - String filepath_ll = lb_filepath_ll_for_module(m); - if (LLVMPrintModuleToFile(m->mod, cast(char const *)filepath_ll.text, &llvm_error)) { - gb_printf_err("LLVM Error: %s\n", llvm_error); - exit_with_errors(); - return false; - } - } - exit_with_errors(); - return 1; - } - return 0; -} - gb_internal bool lb_llvm_module_verification(lbGenerator *gen, bool do_threading) { for (auto const &entry : gen->modules) { @@ -2777,12 +2787,7 @@ gb_internal lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *star } - if (!m->debug_builder && LLVMVerifyFunction(p->value, LLVMReturnStatusAction)) { - gb_printf_err("LLVM CODE GEN FAILED FOR PROCEDURE: %s\n", "main"); - LLVMDumpValue(p->value); - gb_printf_err("\n\n\n\n"); - LLVMVerifyFunction(p->value, LLVMAbortProcessAction); - } + lb_verify_function(m, p); lb_run_function_pass_manager(default_function_pass_manager, p, lbFunctionPassManager_default); return p; @@ -2812,19 +2817,7 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) { } } - if (!m->debug_builder && LLVMVerifyFunction(p->value, LLVMReturnStatusAction)) { - char *llvm_error = nullptr; - - gb_printf_err("LLVM CODE GEN FAILED FOR PROCEDURE: %.*s\n", LIT(p->name)); - LLVMDumpValue(p->value); - gb_printf_err("\n\n\n\n"); - String filepath_ll = lb_filepath_ll_for_module(m); - if (LLVMPrintModuleToFile(m->mod, cast(char const *)filepath_ll.text, &llvm_error)) { - gb_printf_err("LLVM Error: %s\n", llvm_error); - } - LLVMVerifyFunction(p->value, LLVMPrintMessageAction); - exit_with_errors(); - } + lb_verify_function(m, p, true); } From 0b02c67cdf316d55232f0433d51f6ea781d74a22 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 11 Jun 2024 12:19:52 +0100 Subject: [PATCH 258/270] Minor clean up for backend --- src/check_decl.cpp | 9 ++- src/entity.cpp | 1 + src/llvm_backend.cpp | 130 ++++++++++++++++++++--------------- src/llvm_backend.hpp | 3 +- src/llvm_backend_general.cpp | 3 +- 5 files changed, 89 insertions(+), 57 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 13b14149a..a5c9119ea 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1142,7 +1142,14 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { } if (ac.link_name.len > 0) { - e->Procedure.link_name = ac.link_name; + String ln = ac.link_name; + e->Procedure.link_name = ln; + if (ln == "memcpy" || + ln == "memmove" || + ln == "mem_copy" || + ln == "mem_copy_non_overlapping") { + e->Procedure.is_memcpy_like = true; + } } if (ac.deferred_procedure.entity != nullptr) { diff --git a/src/entity.cpp b/src/entity.cpp index 7f484e308..8f55c1faf 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -256,6 +256,7 @@ struct Entity { bool generated_from_polymorphic : 1; bool entry_point_only : 1; bool has_instrumentation : 1; + bool is_memcpy_like : 1; } Procedure; struct { Array entities; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 01680ffa9..81de2c224 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1300,18 +1300,14 @@ gb_internal lbProcedure *lb_create_cleanup_runtime(lbModule *main_module) { // C gb_internal WORKER_TASK_PROC(lb_generate_procedures_and_types_per_module) { lbModule *m = cast(lbModule *)data; - for (Entity *e : m->global_procedures_and_types_to_create) { - if (e->kind == Entity_TypeName) { - (void)lb_get_entity_name(m, e); - lb_type(m, e->type); - } + for (Entity *e : m->global_types_to_create) { + (void)lb_get_entity_name(m, e); + (void)lb_type(m, e->type); } - for (Entity *e : m->global_procedures_and_types_to_create) { - if (e->kind == Entity_Procedure) { - (void)lb_get_entity_name(m, e); - array_add(&m->procedures_to_generate, lb_create_procedure(m, e)); - } + for (Entity *e : m->global_procedures_to_create) { + (void)lb_get_entity_name(m, e); + array_add(&m->procedures_to_generate, lb_create_procedure(m, e)); } return 0; } @@ -1365,16 +1361,24 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker m = lb_module_of_entity(gen, e); } - array_add(&m->global_procedures_and_types_to_create, e); + if (e->kind == Entity_Procedure) { + array_add(&m->global_procedures_to_create, e); + } else if (e->kind == Entity_TypeName) { + array_add(&m->global_types_to_create, e); + } } - for (auto const &entry : gen->modules) { - lbModule *m = entry.value; - if (do_threading) { + if (do_threading) { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; thread_pool_add_task(lb_generate_procedures_and_types_per_module, m); - } else { + } + } else { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; lb_generate_procedures_and_types_per_module(m); } + } thread_pool_wait(); @@ -2405,16 +2409,19 @@ gb_internal WORKER_TASK_PROC(lb_generate_procedures_worker_proc) { } gb_internal void lb_generate_procedures(lbGenerator *gen, bool do_threading) { - for (auto const &entry : gen->modules) { - lbModule *m = entry.value; - if (do_threading) { + if (do_threading) { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; thread_pool_add_task(lb_generate_procedures_worker_proc, m); - } else { + } + + thread_pool_wait(); + } else { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; lb_generate_procedures_worker_proc(m); } } - - thread_pool_wait(); } gb_internal WORKER_TASK_PROC(lb_generate_missing_procedures_to_check_worker_proc) { @@ -2428,17 +2435,20 @@ gb_internal WORKER_TASK_PROC(lb_generate_missing_procedures_to_check_worker_proc } gb_internal void lb_generate_missing_procedures(lbGenerator *gen, bool do_threading) { - for (auto const &entry : gen->modules) { - lbModule *m = entry.value; - // NOTE(bill): procedures may be added during generation - if (do_threading) { + if (do_threading) { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; + // NOTE(bill): procedures may be added during generation thread_pool_add_task(lb_generate_missing_procedures_to_check_worker_proc, m); - } else { + } + thread_pool_wait(); + } else { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; + // NOTE(bill): procedures may be added during generation lb_generate_missing_procedures_to_check_worker_proc(m); } } - - thread_pool_wait(); } gb_internal void lb_debug_info_complete_types_and_finalize(lbGenerator *gen) { @@ -2451,32 +2461,45 @@ gb_internal void lb_debug_info_complete_types_and_finalize(lbGenerator *gen) { } gb_internal void lb_llvm_function_passes(lbGenerator *gen, bool do_threading) { - for (auto const &entry : gen->modules) { - lbModule *m = entry.value; - if (do_threading) { + if (do_threading) { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; thread_pool_add_task(lb_llvm_function_pass_per_module, m); - } else { + } + thread_pool_wait(); + } else { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; lb_llvm_function_pass_per_module(m); } } - thread_pool_wait(); } gb_internal void lb_llvm_module_passes(lbGenerator *gen, bool do_threading) { - for (auto const &entry : gen->modules) { - lbModule *m = entry.value; - auto wd = gb_alloc_item(permanent_allocator(), lbLLVMModulePassWorkerData); - wd->m = m; - wd->target_machine = m->target_machine; + if (do_threading) { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; + auto wd = gb_alloc_item(permanent_allocator(), lbLLVMModulePassWorkerData); + wd->m = m; + wd->target_machine = m->target_machine; - if (do_threading) { - thread_pool_add_task(lb_llvm_module_pass_worker_proc, wd); - } else { + if (do_threading) { + thread_pool_add_task(lb_llvm_module_pass_worker_proc, wd); + } else { + lb_llvm_module_pass_worker_proc(wd); + } + } + thread_pool_wait(); + } else { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; + auto wd = gb_alloc_item(permanent_allocator(), lbLLVMModulePassWorkerData); + wd->m = m; + wd->target_machine = m->target_machine; lb_llvm_module_pass_worker_proc(wd); } } - thread_pool_wait(); } gb_internal String lb_filepath_ll_for_module(lbModule *m) { @@ -2556,17 +2579,21 @@ gb_internal String lb_filepath_obj_for_module(lbModule *m) { gb_internal bool lb_llvm_module_verification(lbGenerator *gen, bool do_threading) { - for (auto const &entry : gen->modules) { - lbModule *m = entry.value; - if (do_threading) { + if (do_threading) { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; thread_pool_add_task(lb_llvm_module_verification_worker_proc, m); - } else { + } + thread_pool_wait(); + + } else { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; if (lb_llvm_module_verification_worker_proc(m)) { return false; } } } - thread_pool_wait(); return true; } @@ -2808,13 +2835,8 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) { lb_end_procedure(p); // Add Flags - if (p->body != nullptr) { - if (p->name == "memcpy" || p->name == "memmove" || - p->name == "runtime.mem_copy" || p->name == "mem_copy_non_overlapping" || - string_starts_with(p->name, str_lit("llvm.memcpy")) || - string_starts_with(p->name, str_lit("llvm.memmove"))) { - p->flags |= lbProcedureFlag_WithoutMemcpyPass; - } + if (p->entity && p->entity->kind == Entity_Procedure && p->entity->Procedure.is_memcpy_like) { + p->flags |= lbProcedureFlag_WithoutMemcpyPass; } lb_verify_function(m, p, true); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 9f7bc8843..447e93d42 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -181,7 +181,8 @@ struct lbModule { std::atomic nested_type_name_guid; Array procedures_to_generate; - Array global_procedures_and_types_to_create; + Array global_procedures_to_create; + Array global_types_to_create; lbProcedure *curr_procedure; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index ea98fc60a..03d0f8b32 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -78,7 +78,8 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) { array_init(&m->procedures_to_generate, a, 0, c->info.all_procedures.count); map_init(&m->procedure_values, c->info.all_procedures.count*2); } - array_init(&m->global_procedures_and_types_to_create, a, 0, 1024); + array_init(&m->global_procedures_to_create, a, 0, 1024); + array_init(&m->global_types_to_create, a, 0, 1024); array_init(&m->missing_procedures_to_check, a, 0, 16); map_init(&m->debug_values); From 1dc90103bd9e4c7783222d34cd16a2237a7dc377 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 11 Jun 2024 12:30:24 +0100 Subject: [PATCH 259/270] Make verification ignorable with a define flag --- src/llvm_backend.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 81de2c224..04c4ce244 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -9,6 +9,11 @@ #endif +#ifndef LLVM_IGNORE_VERIFICATION +#define LLVM_IGNORE_VERIFICATION 0 +#endif + + #include "llvm_backend.hpp" #include "llvm_abi.cpp" #include "llvm_backend_opt.cpp" @@ -1126,6 +1131,10 @@ gb_internal void lb_finalize_objc_names(lbProcedure *p) { } gb_internal void lb_verify_function(lbModule *m, lbProcedure *p, bool dump_ll=false) { + if (LLVM_IGNORE_VERIFICATION) { + return; + } + if (!m->debug_builder && LLVMVerifyFunction(p->value, LLVMReturnStatusAction)) { char *llvm_error = nullptr; @@ -2579,6 +2588,10 @@ gb_internal String lb_filepath_obj_for_module(lbModule *m) { gb_internal bool lb_llvm_module_verification(lbGenerator *gen, bool do_threading) { + if (LLVM_IGNORE_VERIFICATION) { + return true; + } + if (do_threading) { for (auto const &entry : gen->modules) { lbModule *m = entry.value; From 3ff89528134b078a7af3e1d2335e154cf94bf843 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 11 Jun 2024 13:11:14 +0100 Subject: [PATCH 260/270] Replace `panic(fmt.tprintf(` antipattern with `fmt.panicf` --- core/crypto/rand_darwin.odin | 4 ++-- core/crypto/rand_linux.odin | 2 +- core/crypto/rand_windows.odin | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/crypto/rand_darwin.odin b/core/crypto/rand_darwin.odin index 5355f31c5..56acb5d22 100644 --- a/core/crypto/rand_darwin.odin +++ b/core/crypto/rand_darwin.odin @@ -11,7 +11,7 @@ HAS_RAND_BYTES :: true _rand_bytes :: proc(dst: []byte) { err := Sec.RandomCopyBytes(count=len(dst), bytes=raw_data(dst)) if err != .Success { - msg := CF.StringCopyToOdinString(Sec.CopyErrorMessageString(err)) - panic(fmt.tprintf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg)) + msg := CF.StringCopyToOdinString(Sec.CopyErrorMessageString(err)) + fmt.panicf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg) } } diff --git a/core/crypto/rand_linux.odin b/core/crypto/rand_linux.odin index 43b3b3075..7e0edbb7e 100644 --- a/core/crypto/rand_linux.odin +++ b/core/crypto/rand_linux.odin @@ -32,7 +32,7 @@ _rand_bytes :: proc (dst: []byte) { // All other failures are things that should NEVER happen // unless the kernel interface changes (ie: the Linux // developers break userland). - panic(fmt.tprintf("crypto: getrandom failed: %v", errno)) + fmt.panicf("crypto: getrandom failed: %v", errno) } l -= n_read dst = dst[n_read:] diff --git a/core/crypto/rand_windows.odin b/core/crypto/rand_windows.odin index a92d376cb..0ddbcaf9a 100644 --- a/core/crypto/rand_windows.odin +++ b/core/crypto/rand_windows.odin @@ -20,7 +20,7 @@ _rand_bytes :: proc(dst: []byte) { panic("crypto: BCryptGenRandom Invalid parameter") case: // Unknown error - panic(fmt.tprintf("crypto: BCryptGenRandom failed: %d\n", ret)) + fmt.panicf("crypto: BCryptGenRandom failed: %d\n", ret) } } } From 35a845b93f11b152879bd08f90d196afcb7b8b25 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 11 Jun 2024 13:12:41 +0100 Subject: [PATCH 261/270] Fix indentation --- core/crypto/rand_windows.odin | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/crypto/rand_windows.odin b/core/crypto/rand_windows.odin index 0ddbcaf9a..9cd647cc1 100644 --- a/core/crypto/rand_windows.odin +++ b/core/crypto/rand_windows.odin @@ -11,16 +11,16 @@ _rand_bytes :: proc(dst: []byte) { ret := (os.Errno)(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG)) if ret != os.ERROR_NONE { switch ret { - case os.ERROR_INVALID_HANDLE: - // The handle to the first parameter is invalid. - // This should not happen here, since we explicitly pass nil to it - panic("crypto: BCryptGenRandom Invalid handle for hAlgorithm") - case os.ERROR_INVALID_PARAMETER: - // One of the parameters was invalid - panic("crypto: BCryptGenRandom Invalid parameter") - case: - // Unknown error - fmt.panicf("crypto: BCryptGenRandom failed: %d\n", ret) + case os.ERROR_INVALID_HANDLE: + // The handle to the first parameter is invalid. + // This should not happen here, since we explicitly pass nil to it + panic("crypto: BCryptGenRandom Invalid handle for hAlgorithm") + case os.ERROR_INVALID_PARAMETER: + // One of the parameters was invalid + panic("crypto: BCryptGenRandom Invalid parameter") + case: + // Unknown error + fmt.panicf("crypto: BCryptGenRandom failed: %d\n", ret) } } } From 1a22f82f94928e43bf3945cf43dc60d8daab0067 Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Tue, 11 Jun 2024 18:03:20 +0200 Subject: [PATCH 262/270] Changed rl.SetShaderValue etc to take a c.int like in original raylib.h. You should be able to use other values than the ShaderLocationIndex enum, that enum is only for build in things in raylib. Added #any_int on those procs so you can pass both int and also a ShaderLocationIndex. --- vendor/raylib/raylib.odin | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index ebb1cdd27..3d1b74058 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -984,12 +984,14 @@ foreign lib { LoadShader :: proc(vsFileName, fsFileName: cstring) -> Shader --- // Load shader from files and bind default locations LoadShaderFromMemory :: proc(vsCode, fsCode: cstring) -> Shader --- // Load shader from code strings and bind default locations IsShaderReady :: proc(shader: Shader) -> bool --- // Check if a shader is ready - GetShaderLocation :: proc(shader: Shader, uniformName: cstring) -> ShaderLocationIndex --- // Get shader uniform location - GetShaderLocationAttrib :: proc(shader: Shader, attribName: cstring) -> ShaderLocationIndex --- // Get shader attribute location - SetShaderValue :: proc(shader: Shader, locIndex: ShaderLocationIndex, value: rawptr, uniformType: ShaderUniformDataType) --- // Set shader uniform value - SetShaderValueV :: proc(shader: Shader, locIndex: ShaderLocationIndex, value: rawptr, uniformType: ShaderUniformDataType, count: c.int) --- // Set shader uniform value vector - SetShaderValueMatrix :: proc(shader: Shader, locIndex: ShaderLocationIndex, mat: Matrix) --- // Set shader uniform value (matrix 4x4) - SetShaderValueTexture :: proc(shader: Shader, locIndex: ShaderLocationIndex, texture: Texture2D) --- // Set shader uniform value for texture (sampler2d) + GetShaderLocation :: proc(shader: Shader, uniformName: cstring) -> c.int --- // Get shader uniform location + GetShaderLocationAttrib :: proc(shader: Shader, attribName: cstring) -> c.int --- // Get shader attribute location + + // We use #any_int here so we can pass ShaderLocationIndex + SetShaderValue :: proc(shader: Shader, #any_int locIndex: c.int, value: rawptr, uniformType: ShaderUniformDataType) --- // Set shader uniform value + SetShaderValueV :: proc(shader: Shader, #any_int locIndex: c.int, value: rawptr, uniformType: ShaderUniformDataType, count: c.int) --- // Set shader uniform value vector + SetShaderValueMatrix :: proc(shader: Shader, #any_int locIndex: c.int, mat: Matrix) --- // Set shader uniform value (matrix 4x4) + SetShaderValueTexture :: proc(shader: Shader, #any_int locIndex: c.int, texture: Texture2D) --- // Set shader uniform value for texture (sampler2d) UnloadShader :: proc(shader: Shader) --- // Unload shader from GPU memory (VRAM) // Screen-space-related functions From ebadff555d70d5ef8faf91785696e5c5ea3605f8 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 12 Jun 2024 12:52:48 +0200 Subject: [PATCH 263/270] Update XML reader to normalize whitespace, part 1. --- core/encoding/entity/entity.odin | 109 ++++++++------------- core/encoding/xml/tokenizer.odin | 43 +++----- core/encoding/xml/xml_reader.odin | 24 +++-- tests/core/encoding/xml/test_core_xml.odin | 10 +- 4 files changed, 70 insertions(+), 116 deletions(-) diff --git a/core/encoding/entity/entity.odin b/core/encoding/entity/entity.odin index cee6230ef..280be9377 100644 --- a/core/encoding/entity/entity.odin +++ b/core/encoding/entity/entity.odin @@ -56,38 +56,27 @@ CDATA_END :: "]]>" COMMENT_START :: "" -/* - Default: CDATA and comments are passed through unchanged. -*/ +// Default: CDATA and comments are passed through unchanged. XML_Decode_Option :: enum u8 { - /* - Do not decode & entities. It decodes by default. - If given, overrides `Decode_CDATA`. - */ + // Do not decode & entities. It decodes by default. If given, overrides `Decode_CDATA`. No_Entity_Decode, - /* - CDATA is unboxed. - */ + // CDATA is unboxed. Unbox_CDATA, - /* - Unboxed CDATA is decoded as well. - Ignored if `.Unbox_CDATA` is not given. - */ + // Unboxed CDATA is decoded as well. Ignored if `.Unbox_CDATA` is not given. Decode_CDATA, - /* - Comments are stripped. - */ + // Comments are stripped. Comment_Strip, + + // Normalize whitespace + Normalize_Whitespace, } XML_Decode_Options :: bit_set[XML_Decode_Option; u8] -/* - Decode a string that may include SGML/XML/HTML entities. - The caller has to free the result. -*/ +// Decode a string that may include SGML/XML/HTML entities. +// The caller has to free the result. decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := context.allocator) -> (decoded: string, err: Error) { context.allocator = allocator @@ -100,14 +89,14 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := t := Tokenizer{src=input} in_data := false + prev: rune + loop: for { advance(&t) or_return if t.r < 0 { break loop } - /* - Below here we're never inside a CDATA tag. - At most we'll see the start of one, but that doesn't affect the logic. - */ + // Below here we're never inside a CDATA tag. At most we'll see the start of one, + // but that doesn't affect the logic. switch t.r { case '<': /* @@ -126,9 +115,7 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := in_data = _handle_xml_special(&t, &builder, options) or_return case ']': - /* - If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag. - */ + // If we're unboxing _and_ decoding CDATA, we'll have to check for the end tag. if in_data { if t.read_offset + len(CDATA_END) < len(t.src) { if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END { @@ -143,22 +130,16 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := case: if in_data && .Decode_CDATA not_in options { - /* - Unboxed, but undecoded. - */ + // Unboxed, but undecoded. write_rune(&builder, t.r) continue } if t.r == '&' { if entity, entity_err := _extract_xml_entity(&t); entity_err != .None { - /* - We read to the end of the string without closing the entity. - Pass through as-is. - */ + // We read to the end of the string without closing the entity. Pass through as-is. write_string(&builder, entity) } else { - if .No_Entity_Decode not_in options { if decoded, ok := xml_decode_entity(entity); ok { write_rune(&builder, decoded) @@ -166,19 +147,27 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := } } - /* - Literal passthrough because the decode failed or we want entities not decoded. - */ + // Literal passthrough because the decode failed or we want entities not decoded. write_string(&builder, "&") write_string(&builder, entity) write_string(&builder, ";") } } else { - write_rune(&builder, t.r) + // https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-line-ends + switch t.r { + case '\n', 0x85, 0x2028: + write_rune(&builder, '\n') + case '\r': // Do nothing until next character + case: + if prev == '\r' { // Turn a single carriage return into a \n + write_rune(&builder, '\n') + } + write_rune(&builder, t.r) + } + prev = t.r } } } - return strings.clone(strings.to_string(builder), allocator), err } @@ -253,24 +242,18 @@ xml_decode_entity :: proc(entity: string) -> (decoded: rune, ok: bool) { return rune(val), true case: - /* - Named entity. - */ + // Named entity. return named_xml_entity_to_rune(entity) } } -/* - Private XML helper to extract `&;` entity. -*/ +// Private XML helper to extract `&;` entity. @(private="file") _extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) { assert(t != nil && t.r == '&') - /* - All of these would be in the ASCII range. - Even if one is not, it doesn't matter. All characters we need to compare to extract are. - */ + // All of these would be in the ASCII range. + // Even if one is not, it doesn't matter. All characters we need to compare to extract are. length := len(t.src) found := false @@ -292,9 +275,7 @@ _extract_xml_entity :: proc(t: ^Tokenizer) -> (entity: string, err: Error) { return string(t.src[t.offset : t.read_offset]), .Invalid_Entity_Encoding } -/* - Private XML helper for CDATA and comments. -*/ +// Private XML helper for CDATA and comments. @(private="file") _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: XML_Decode_Options) -> (in_data: bool, err: Error) { assert(t != nil && t.r == '<') @@ -304,20 +285,14 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X t.read_offset += len(CDATA_START) - 1 if .Unbox_CDATA in options && .Decode_CDATA in options { - /* - We're unboxing _and_ decoding CDATA - */ + // We're unboxing _and_ decoding CDATA return true, .None } - /* - CDATA is passed through. - */ + // CDATA is passed through. offset := t.offset - /* - Scan until end of CDATA. - */ + // Scan until end of CDATA. for { advance(t) or_return if t.r < 0 { return true, .CDATA_Not_Terminated } @@ -341,14 +316,10 @@ _handle_xml_special :: proc(t: ^Tokenizer, builder: ^strings.Builder, options: X } else if string(t.src[t.offset:][:len(COMMENT_START)]) == COMMENT_START { t.read_offset += len(COMMENT_START) - /* - Comment is passed through by default. - */ + // Comment is passed through by default. offset := t.offset - /* - Scan until end of Comment. - */ + // Scan until end of Comment. for { advance(t) or_return if t.r < 0 { return true, .Comment_Not_Terminated } diff --git a/core/encoding/xml/tokenizer.odin b/core/encoding/xml/tokenizer.odin index 0f87c366b..2d06038b7 100644 --- a/core/encoding/xml/tokenizer.odin +++ b/core/encoding/xml/tokenizer.odin @@ -218,9 +218,7 @@ scan_identifier :: proc(t: ^Tokenizer) -> string { for is_valid_identifier_rune(t.ch) { advance_rune(t) if t.ch == ':' { - /* - A namespaced attr can have at most two parts, `namespace:ident`. - */ + // A namespaced attr can have at most two parts, `namespace:ident`. if namespaced { break } @@ -268,14 +266,10 @@ scan_comment :: proc(t: ^Tokenizer) -> (comment: string, err: Error) { return string(t.src[offset : t.offset - 1]), .None } -/* - Skip CDATA -*/ +// Skip CDATA skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) { if t.read_offset + len(CDATA_START) >= len(t.src) { - /* - Can't be the start of a CDATA tag. - */ + // Can't be the start of a CDATA tag. return .None } @@ -290,9 +284,7 @@ skip_cdata :: proc(t: ^Tokenizer) -> (err: Error) { return .Premature_EOF } - /* - Scan until the end of a CDATA tag. - */ + // Scan until the end of a CDATA tag. if t.read_offset + len(CDATA_END) < len(t.src) { if string(t.src[t.offset:][:len(CDATA_END)]) == CDATA_END { t.read_offset += len(CDATA_END) @@ -319,14 +311,10 @@ scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close case '<': if peek_byte(t) == '!' { if peek_byte(t, 1) == '[' { - /* - Might be the start of a CDATA tag. - */ + // Might be the start of a CDATA tag. skip_cdata(t) or_return } else if peek_byte(t, 1) == '-' && peek_byte(t, 2) == '-' { - /* - Comment start. Eat comment. - */ + // Comment start. Eat comment. t.read_offset += 3 _ = scan_comment(t) or_return } @@ -342,17 +330,13 @@ scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close } if t.ch == close { - /* - If it's not a CDATA or comment, it's the end of this body. - */ + // If it's not a CDATA or comment, it's the end of this body. break loop } advance_rune(t) } - /* - Strip trailing whitespace. - */ + // Strip trailing whitespace. lit := string(t.src[offset : t.offset]) end := len(lit) @@ -369,11 +353,6 @@ scan_string :: proc(t: ^Tokenizer, offset: int, close: rune = '<', consume_close if consume_close { advance_rune(t) } - - /* - TODO: Handle decoding escape characters and unboxing CDATA. - */ - return lit, err } @@ -384,7 +363,7 @@ peek :: proc(t: ^Tokenizer) -> (token: Token) { return token } -scan :: proc(t: ^Tokenizer) -> Token { +scan :: proc(t: ^Tokenizer, multiline_string := false) -> Token { skip_whitespace(t) offset := t.offset @@ -418,7 +397,7 @@ scan :: proc(t: ^Tokenizer) -> Token { case '"', '\'': kind = .Invalid - lit, err = scan_string(t, t.offset, ch, true, false) + lit, err = scan_string(t, t.offset, ch, true, multiline_string) if err == .None { kind = .String } @@ -435,4 +414,4 @@ scan :: proc(t: ^Tokenizer) -> Token { lit = string(t.src[offset : t.offset]) } return Token{kind, lit, pos} -} +} \ No newline at end of file diff --git a/core/encoding/xml/xml_reader.odin b/core/encoding/xml/xml_reader.odin index 5b4b12948..b9656900f 100644 --- a/core/encoding/xml/xml_reader.odin +++ b/core/encoding/xml/xml_reader.odin @@ -203,9 +203,7 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha doc.elements = make([dynamic]Element, 1024, 1024, allocator) - // strings.intern_init(&doc.intern, allocator, allocator) - - err = .Unexpected_Token + err = .Unexpected_Token element, parent: Element_ID open: Token @@ -259,8 +257,8 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha case .Slash: // Empty tag. Close it. expect(t, .Gt) or_return - parent = doc.elements[element].parent - element = parent + parent = doc.elements[element].parent + element = parent case: error(t, t.offset, "Expected close tag, got: %#v\n", end_token) @@ -276,8 +274,8 @@ parse_bytes :: proc(data: []u8, options := DEFAULT_OPTIONS, path := "", error_ha error(t, t.offset, "Mismatched Closing Tag. Expected %v, got %v\n", doc.elements[element].ident, ident.text) return doc, .Mismatched_Closing_Tag } - parent = doc.elements[element].parent - element = parent + parent = doc.elements[element].parent + element = parent } else if open.kind == .Exclaim { // (validated: Options, err: Error) { return validated, .None } -expect :: proc(t: ^Tokenizer, kind: Token_Kind) -> (tok: Token, err: Error) { - tok = scan(t) +expect :: proc(t: ^Tokenizer, kind: Token_Kind, multiline_string := false) -> (tok: Token, err: Error) { + tok = scan(t, multiline_string=multiline_string) if tok.kind == kind { return tok, .None } error(t, t.offset, "Expected \"%v\", got \"%v\".", kind, tok.kind) @@ -480,7 +478,13 @@ parse_attribute :: proc(doc: ^Document) -> (attr: Attribute, offset: int, err: E offset = t.offset - len(key.text) _ = expect(t, .Eq) or_return - value := expect(t, .String) or_return + value := expect(t, .String, multiline_string=true) or_return + + normalized, normalize_err := entity.decode_xml(value.text, {.Normalize_Whitespace}, doc.allocator) + if normalize_err == .None { + append(&doc.strings_to_free, normalized) + value.text = normalized + } attr.key = key.text attr.val = value.text diff --git a/tests/core/encoding/xml/test_core_xml.odin b/tests/core/encoding/xml/test_core_xml.odin index 22852d1f3..09d1a4611 100644 --- a/tests/core/encoding/xml/test_core_xml.odin +++ b/tests/core/encoding/xml/test_core_xml.odin @@ -36,7 +36,7 @@ xml_test_utf8_normal :: proc(t: ^testing.T) { }, expected_doctype = "恥ずべきフクロウ", }, - crc32 = 0xe9b62f03, + crc32 = 0xefa55f27, }) } @@ -52,7 +52,7 @@ xml_test_utf8_unbox_cdata :: proc(t: ^testing.T) { }, expected_doctype = "恥ずべきフクロウ", }, - crc32 = 0x9c2643ed, + crc32 = 0x2dd27770, }) } @@ -128,7 +128,7 @@ xml_test_entities_unbox :: proc(t: ^testing.T) { }, expected_doctype = "html", }, - crc32 = 0x3b6d4a90, + crc32 = 0x350ca83e, }) } @@ -142,7 +142,7 @@ xml_test_entities_unbox_decode :: proc(t: ^testing.T) { }, expected_doctype = "html", }, - crc32 = 0x5be2ffdc, + crc32 = 0x7f58db7d, }) } @@ -172,7 +172,7 @@ xml_test_unicode :: proc(t: ^testing.T) { expected_doctype = "", }, err = .None, - crc32 = 0x0b6100ab, + crc32 = 0x73070b55, }) } From 2fe961cbcd05a558fd13cc5f4c506373e7047a6b Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 12 Jun 2024 13:30:00 +0200 Subject: [PATCH 264/270] Fold XML attribute whitespace. --- core/encoding/entity/entity.odin | 36 +++++++++++++------ .../core/assets/XML/attribute-whitespace.xml | 8 +++++ tests/core/encoding/xml/test_core_xml.odin | 14 ++++++++ 3 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 tests/core/assets/XML/attribute-whitespace.xml diff --git a/core/encoding/entity/entity.odin b/core/encoding/entity/entity.odin index 280be9377..f5208ad6f 100644 --- a/core/encoding/entity/entity.odin +++ b/core/encoding/entity/entity.odin @@ -89,7 +89,7 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := t := Tokenizer{src=input} in_data := false - prev: rune + prev: rune = ' ' loop: for { advance(&t) or_return @@ -153,18 +153,32 @@ decode_xml :: proc(input: string, options := XML_Decode_Options{}, allocator := write_string(&builder, ";") } } else { - // https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-line-ends - switch t.r { - case '\n', 0x85, 0x2028: - write_rune(&builder, '\n') - case '\r': // Do nothing until next character - case: - if prev == '\r' { // Turn a single carriage return into a \n - write_rune(&builder, '\n') + // Handle AV Normalization: https://www.w3.org/TR/2006/REC-xml11-20060816/#AVNormalize + if .Normalize_Whitespace in options { + switch t.r { + case ' ', '\r', '\n', '\t': + if prev != ' ' { + write_rune(&builder, ' ') + prev = ' ' + } + case: + write_rune(&builder, t.r) + prev = t.r } - write_rune(&builder, t.r) + } else { + // https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-line-ends + switch t.r { + case '\n', 0x85, 0x2028: + write_rune(&builder, '\n') + case '\r': // Do nothing until next character + case: + if prev == '\r' { // Turn a single carriage return into a \n + write_rune(&builder, '\n') + } + write_rune(&builder, t.r) + } + prev = t.r } - prev = t.r } } } diff --git a/tests/core/assets/XML/attribute-whitespace.xml b/tests/core/assets/XML/attribute-whitespace.xml new file mode 100644 index 000000000..6381225d5 --- /dev/null +++ b/tests/core/assets/XML/attribute-whitespace.xml @@ -0,0 +1,8 @@ + + +Barzle +<부끄러운:barzle> + Indeed! + \ No newline at end of file diff --git a/tests/core/encoding/xml/test_core_xml.odin b/tests/core/encoding/xml/test_core_xml.odin index 09d1a4611..b29431e10 100644 --- a/tests/core/encoding/xml/test_core_xml.odin +++ b/tests/core/encoding/xml/test_core_xml.odin @@ -146,6 +146,20 @@ xml_test_entities_unbox_decode :: proc(t: ^testing.T) { }) } +@(test) +xml_test_attribute_whitespace :: proc(t: ^testing.T) { + run_test(t, { + // Same as above. + // Unbox CDATA in data tag. + filename = "XML/attribute-whitespace.xml", + options = { + flags = {}, + expected_doctype = "foozle", + }, + crc32 = 0x8f5fd6c1, + }) +} + @(test) xml_test_invalid_doctype :: proc(t: ^testing.T) { run_test(t, { From f57c03c1707f48608bcb3bc87aeee91af60e6d63 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 12 Jun 2024 12:40:29 +0100 Subject: [PATCH 265/270] Improve matrix type hinting rules a little --- src/check_expr.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 01cba881e..742909701 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3568,6 +3568,8 @@ gb_internal void check_binary_matrix(CheckerContext *c, Token const &op, Operand // prefer the named type x->type = y->type; } + // finish + return; } else { bool is_row_major = xt->Matrix.is_row_major && yt->Matrix.is_row_major; x->type = alloc_type_matrix(xt->Matrix.elem, xt->Matrix.row_count, yt->Matrix.column_count, nullptr, nullptr, is_row_major); From c5f7788652c6f46ae9b1588c8e3638d58c49c92b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 12 Jun 2024 12:54:57 +0100 Subject: [PATCH 266/270] Check to see if matrices are exactly the same type --- src/check_expr.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 742909701..0f9bb781e 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3564,12 +3564,13 @@ gb_internal void check_binary_matrix(CheckerContext *c, Token const &op, Operand x->mode = Addressing_Value; if (are_types_identical(xt, yt)) { + if (are_types_identical(x->type, y->type)) { + return; + } if (!is_type_named(x->type) && is_type_named(y->type)) { // prefer the named type x->type = y->type; } - // finish - return; } else { bool is_row_major = xt->Matrix.is_row_major && yt->Matrix.is_row_major; x->type = alloc_type_matrix(xt->Matrix.elem, xt->Matrix.row_count, yt->Matrix.column_count, nullptr, nullptr, is_row_major); From d37b5a7b67e5886c3361a81287e077505eed80b5 Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Wed, 12 Jun 2024 19:54:14 +0200 Subject: [PATCH 267/270] Make rl.MatrixToFloatV transpose the matrix before transmuting it to [16]f32, so it does the same thing as the raymath version implemented in C. --- vendor/raylib/raymath.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/raylib/raymath.odin b/vendor/raylib/raymath.odin index 9682ffe4f..eef5c2fcd 100644 --- a/vendor/raylib/raymath.odin +++ b/vendor/raylib/raymath.odin @@ -668,7 +668,7 @@ MatrixLookAt :: proc "c" (eye, target, up: Vector3) -> Matrix { // Get float array of matrix data @(require_results) MatrixToFloatV :: proc "c" (mat: Matrix) -> [16]f32 { - return transmute([16]f32)mat + return transmute([16]f32)linalg.transpose(mat) } From c7ea4ec71c8aaac234e1882a02be9ee42175ff4e Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Wed, 12 Jun 2024 21:13:12 +0200 Subject: [PATCH 268/270] rlgl: Pull in raylib and expose missing types, so it is the same as rlgl.h. This makes rlgl less stand-alone, but I left some notes in rlgl.odin how to easily make it stand-alone if one really wants to. --- vendor/raylib/rlgl/rlgl.odin | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/vendor/raylib/rlgl/rlgl.odin b/vendor/raylib/rlgl/rlgl.odin index b36764830..cef31c238 100644 --- a/vendor/raylib/rlgl/rlgl.odin +++ b/vendor/raylib/rlgl/rlgl.odin @@ -108,22 +108,41 @@ package rlgl import "core:c" +import rl "../." VERSION :: "5.0" +RAYLIB_SHARED :: #config(RAYLIB_SHARED, false) + +// Note: We pull in the full raylib library. If you want a truly stand-alone rlgl, then: +// - Compile a separate rlgl library and use that in the foreign import blocks below. +// - Remove the `import rl "../."` line +// - Copy the code from raylib.odin for any types we alias from that package (see PixelFormat etc) + when ODIN_OS == .Windows { + @(extra_linker_flags="/NODEFAULTLIB:" + ("msvcrt" when RAYLIB_SHARED else "libcmt")) foreign import lib { - "../windows/raylib.lib", + "../windows/raylibdll.lib" when RAYLIB_SHARED else "../windows/raylib.lib" , "system:Winmm.lib", "system:Gdi32.lib", "system:User32.lib", "system:Shell32.lib", } } else when ODIN_OS == .Linux { - foreign import lib "../linux/libraylib.a" + foreign import lib { + // Note(bumbread): I'm not sure why in `linux/` folder there are + // multiple copies of raylib.so, but since these bindings are for + // particular version of the library, I better specify it. Ideally, + // though, it's best specified in terms of major (.so.4) + "../linux/libraylib.so.500" when RAYLIB_SHARED else "../linux/libraylib.a", + "system:dl", + "system:pthread", + } } else when ODIN_OS == .Darwin { foreign import lib { - "../macos-arm64/libraylib.a" when ODIN_ARCH == .arm64 else "../macos/libraylib.a", + "../macos" + + ("-arm64" when ODIN_ARCH == .arm64 else "") + + "/libraylib" + (".500.dylib" when RAYLIB_SHARED else ".a"), "system:Cocoa.framework", "system:OpenGL.framework", "system:IOKit.framework", @@ -249,7 +268,6 @@ BLEND_DST_ALPHA :: 0x80CA // GL_BLEND_DST_ALPHA BLEND_SRC_ALPHA :: 0x80CB // GL_BLEND_SRC_ALPHA BLEND_COLOR :: 0x8005 // GL_BLEND_COLOR - //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- @@ -291,7 +309,6 @@ RenderBatch :: struct { currentDepth: f32, // Current depth value for next draw } - // OpenGL version GlVersion :: enum c.int { OPENGL_11 = 1, // OpenGL 1.1 @@ -302,6 +319,11 @@ GlVersion :: enum c.int { OPENGL_ES_30, // OpenGL ES 3.0 (GLSL 300 es) } +PixelFormat :: rl.PixelFormat +TextureFilter :: rl.TextureFilter +BlendMode :: rl.BlendMode +ShaderLocationIndex :: rl.ShaderLocationIndex +ShaderUniformDataType :: rl.ShaderUniformDataType // Shader attribute data types ShaderAttributeDataType :: enum c.int { @@ -343,8 +365,7 @@ CullMode :: enum c.int { BACK, } -// Matrix type (right handed, stored row major) -Matrix :: #row_major matrix[4, 4]f32 +Matrix :: rl.Matrix @(default_calling_convention="c", link_prefix="rl") foreign lib { From 33270f14a41a61137351907ccaa5317afbe47ad5 Mon Sep 17 00:00:00 2001 From: Feoramund <161657516+Feoramund@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:31:43 -0400 Subject: [PATCH 269/270] Fix #3739 --- src/check_expr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 0f9bb781e..359b30276 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2550,7 +2550,7 @@ gb_internal void check_unary_expr(CheckerContext *c, Operand *o, Token op, Ast * error_line("\tSuggestion: Did you want to pass the iterable value to the for statement by pointer to get addressable semantics?\n"); } - if (is_type_map(parent_type)) { + if (parent_type != nullptr && is_type_map(parent_type)) { error_line("\t Prefer doing 'for key, &%.*s in ...'\n", LIT(e->token.string)); } else { error_line("\t Prefer doing 'for &%.*s in ...'\n", LIT(e->token.string)); From a804463a575b2b543d8494402a3cdb9621a3bcc6 Mon Sep 17 00:00:00 2001 From: jasonkercher Date: Wed, 12 Jun 2024 15:31:59 -0400 Subject: [PATCH 270/270] add COMMTIMEOUTS, get/setCommTimeouts --- core/sys/windows/kernel32.odin | 13 +++++++++++++ 1 file changed, 13 insertions(+) mode change 100644 => 100755 core/sys/windows/kernel32.odin diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin old mode 100644 new mode 100755 index 16b6fa244..3c60cfc43 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -1153,6 +1153,19 @@ foreign kernel32 { SetCommState :: proc(handle: HANDLE, dcb: ^DCB) -> BOOL --- } +COMMTIMEOUTS :: struct { + ReadIntervalTimeout: DWORD, + ReadTotalTimeoutMultiplier: DWORD, + ReadTotalTimeoutConstant: DWORD, + WriteTotalTimeoutMultiplier: DWORD, + WriteTotalTimeoutConstant: DWORD, +} + +@(default_calling_convention="system") +foreign kernel32 { + GetCommTimeouts :: proc(handle: HANDLE, timeouts: ^COMMTIMEOUTS) -> BOOL --- + SetCommTimeouts :: proc(handle: HANDLE, timeouts: ^COMMTIMEOUTS) -> BOOL --- +} LPFIBER_START_ROUTINE :: #type proc "system" (lpFiberParameter: LPVOID)

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