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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 z 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
m48_I)?Rx1i0? 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ñ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≶hAgq7v#(}_X|98HG!E7`)fd%3v{yIiwW(& z{vT^mS%(@2EDWG~ej(x0P{GVlsstKW0cSPn+Jh2-E{HB3(8g$xc32oRR4_6iWI>C_ zK(apFB`Ou5f(&}ae0PWncyD$AsP6>20LmWJ?*!T4)w_oUl+pKrvbm4uG0?6u(CSD~ zXA0DM19h>%eCUQf=vs_9U_W?vKJe`W1yoRhn$9qnL(&Oo;Rs9wl1`wz`MM#xUdV!1t3U#$s|UQ$1U0RMLMjB%APK0X z2VdU_x$_FLn;2XffKH?a6-F5#yTAi{psmWFRtKWn2pVl<0nP(1=V7#-XRbqAfh!vxxI z)B?TcXAh)2@9Y6rGB%*L6S#rj0xD0f* z7r1bR9P-`(uRuU<0aqZUAYX%cunGjsMyf!-0|bqGR6sN+A;2mSD^MdI6bK&OyeyxP zllilc@ML}(%tWq3R<^+E5Ku=KR)-YZLh2Au7YLRCKn`iB0M#KoA$5qm4FiAcTCjIO zM^HiQ5DSmy7kvEflcC}kpgQCMv<`v97Tkx}s*%nqpdu5$LU2uzFa=T`gPaJiNfg1m zxA5x(*HmgmSOKn2{!T`-Vjs9ZA;OBolL`9*Y{e`zD_+Neayuj%A!!7?p$qmxG7;v3 z>lQ0?^TBlss1!ksR#4qC8(I&5A{th=fK-A?I1ml5TZ%!s3S7^Fy3-za4ST?sKS8S*kSw?W25(pac?VX_fY`8VCIGpb0de5f42T1-W5n*QFZ@G#j;?Z2801F8Imfc_(7x1k-phNK( zJfNi!xJ6rT3Th~TTeA_%L9N+iEh->YNv~y~to+wPP*&Az4k)VvG#LxZC9r{~aH6v2 ziGJj)KLvcxNEgIDEZwMC{Y>EFjUYo(po%I27KxB-GY#xBNX~#{8|Xo4&>MeVtN`C| z0a4J^qXJTomUWQM_i20sN>0#GUC>cXoi!>J;QJ=s!4)#7n+9&df$|-w5(Xbf0UpT$ zZ|(>62Hio!i2Ai4|ElmHD8g8FhDEs*XTXvGJ(_r=CgDhldLfh573 z7bii^6%qyoA=pXa7P5zuT z1aU#;fh+;(0oA)8OF=Z)QpkMplS@n2FZd}Pk~(80mJlz)Xa2et@Pt+xf6~D;ehuEKLc9!V{4odlHBO}QiyrvIA93)L z2ftR23djqe_~QCk3?-VMKs!f4LTMiSbq73lf-(vN14Dy{;fXX4 z{=5TlEub_K0d6F*sDK(sq|DBN)PttDK(qsRMh$XY1Beg0cL79$=4e1PxGkg!IgAp- z2d&En(V)T$M1zWX#4TW;feUDN1Qg<+<~7IwP-zD0c7P@xKw@xTf|4XCwd_g*-vbJ| zrr+gT@CqyFrDc%ux*8RM z&JysLoeaoyg>DxW8N?g}==@dC9UqWUy5tf)@F<;3^NYfgKp3+KdJMiy^NTX*G59jg zFUm^{UMGOcF$KsidEn7u)M3@x-Jpp;&_$%6xzpDnAnzcn1`obL#%^EKUk0f?2nuHo z4`z=7k4}$**Giz|uiyf5GN^C|jSWk9B!4ixv=ek-`3unALh!+hX|VHRKrsv|iWOWs zT~u^H{xtwiTUmfcD;+vOHv{{3gCx3rR1Cl))7>SYKm^Upb?yP57Y$Ng0h#_0?+8&T z2bq!4>7tU-d|)%=0$z#c7rOlIDWH`K-Jp!n&2sbtxLyPW2KeYG$W$Gud7#kcq9X3m zEvgyK$Kcb=D;3Sh0Pgm9bUyR}T`$)u2fF z6}))Di4kJbr$}7B16}R$;_<8h|6hV`D8*ya_DFCzt-xs#=6q>i74W6l4xsdG(Ru7e!Jq&CUpj-OT~sVOT~rd9Yg7^#ptDM) zf{itx17`%_XUT|yhMN?+OQ6QQxbW=%|JOy}>;Nh;KqVOZsQ(K1?1ynXXmlBrs6oja z5=fmF!6t%|GkB6Mz@wXYaRfMwrbO_8PEiD%9TdO;x}6;qG=?W%)V%!v|7Aa@r~_pe zP`d>!Vf%FR8i0*pRg8c~*dw1#A^vR_njbN~IQ{Ma|JQ+_T#qeV_;m9!L3Do($ELd% zq#Hb_j}#6kK-U*R58Lu+d;=;MT{?YKOh8R9Xf6khi~EBHJOV&@8QdTQS4yBj?{-l! z0l5b>59bf^5#%U$P!KtQnpofxxf6PFQzvA$5>gnbcT7dIW|9F+zHMxf16e0{w zhZq_`z)*0R5ezcao(MyQAcis%Fcj?lf1twg6*#Nn_x|2cu=m%6;`ctdBv}bE6f}@e zD55eThDH)F6qIvbgn$eMHMj^Fsthqygcw8rgG$fW(9D?79RfdmFA5(0|NpuQS+6ii`b=H6;HNo?Zpq?0{ngboy+zp=X zL_gFEvd{)pbb`DF9yxRdxfy(*C}gz_OcKrCFP=7nS|(G#ccFq>k)X2I1AZj{xY_{Q z3$?O42t3Ej0cy5^+OQxkpvEwW4=IOWcM$Y|n{1%K0-yN}lLWVUL3_18v3VTQBnR~m zKn8&hNdmRFLCtfp7^sH?QU^L`1+;A!e04bMOYkT#WVW{ld>;X5VNHt)=!|F3zz(RZ z0b6;i1@bq@Ja}IRWFBZA6Nm;+PeR=AdJ8vl8fvo|r-GD}q3kPVN36y~w_Nag< zh=Gum(jWl{pXoIpvIVbWVWz_ueKWmw2Q@kbKphNFw0600fcBH0hQt(@f*1uc8YBSW zGri_PF`5B1tOx3BKqG+}x_7b@N@GbPFLOY1?%m*aeUA$0{96ymsl?#nPH@h%U?>SB zGUMgcL7FiN&~ilrlvN<*3aB=%c%l6O)LsS^3DEmI8Y-9=_*)M`Ll?AK6Ew#FT4cuG zvKlIC&|m?(ql~`=G~NTM{K1_+ko(Zbi$V8Jy)dqYIRw(xDN(6-vHt%5|4@HG76^el zmf$`r=;FI+7m6X7mEf1Zosy?aph^;iRB)^DyZAKTxII>7xQU-?1}9MF!MH zQuxF#2wL0H*MTLh6oR7Fcoo~>-Qz#9-9~HDaA3E{`nXUvG0vjp^tLUv!DFdwtGW@oyiIIWf zg_g>%|Dd%%5X(Ukr2z6dN0*BV_~_gO@X|7nQc(90ylyN5lftFclC42V2iYQQzJphzd zd_ZS@gMz3Ve3o&DO2iAdpWxM%pg}y)>JFs)tUVgvSb!35XN^h#I0hgWu5^Z|(OsefH@Ze8U}ri51B2)B1E6yeUtIY6|Nl!*a97kv1-o*{ zSPp2U?ZqOHYH7TxQ^6xYY@iXB5|HY*pp*e#iq!3+5&$0If*iID8f^lV93UE0et~FE z@d=_q^(lx3Rh1wbl#4)R7HAX;R2}jQFoI9!6$n(|7l<_A7f5vA7sw3Y7br~d==LlC zg%wCCzvdPdP%R5SZVgmxf{t4Q({sSfroqRpf%%Zdm~g8=I}{ih7>+rDhKwMWm3nkK zf{R*Eq#Sc(W&|Za2%C`sBvlt(u!n-{p9_}vq9-!|x|d(e;+ z=q5DQKOmuQ-nSqMbap!^w^_UfuLc8WdGN99;A^i6K&1nC1)_!F0q`;)ln(9cTc9%3 zMJ0e=15###_9%eUlExJ9G0XfK&;+2d1l(%k*H{Ct#rQQK2?i9opacXOoZ=TONZ=RD z2;dh?aNrk=FyI#qP=KDb3u(+2beDkgZ;eX83!&@aG60m@52MT{fOaB)yRMx+D&X5S zJU|r&q~qG{qT&L|{?LryU83RvDri9?x}Z|P16*`Kwi|S|sMvtH(A$+dA@{d*wy3Cp zghBg=K{R;A7+f1EKrsJX*hb z@OxeZ9aOD(%!A+O5Ga>|3II@Ig{;4av_C;w!Te5G&Vyd11NB}*1qVZ^5vc754x;9Y z0QhK%O!FRaNR+`69QScZ&=f*;e1k%%(?vz5`9%bOOCu8l18nHCkiSI|iCc^qA1y0U zhAArNZ~4NA&{M(RV#mb5@Nyz}IG{xZl4 z1m#hL;obQClmW5_dRGD{L4az2*Q6Pt3N-@K34@$DN}>^uiXkBj>WzY20jhUi9tV#_ zfUc2%hb6jS4tgT30S0ftaNq##Nnv1c+yOo@)^jbyU{J{pG8j~tzsv(=MyQ#P9i`xb zC#c9V2gtGtP&X7DlgC|DK#LW?li#3Z0g6VDB!BNuP@Mo;>;s~~%Yd3|R0`m^!k}Rf zcu0$(L=DumLc|HUoPe*6-UW_u{UT6=vu3z}OX*Ay)d^lv4;spZ?vn86X4MC& z=;k#5QE-KzRvLJP5!fzBIpCvGz`u=!qw^RjT|j0$u$0}7piRjPFJ4{&mEbMl5ja>9 z1Njxy#;Je^xTsXT{P+)i?g})-x_ZFg?C@ZEEe2{{h+}{xc+mO{{Mf;i&g(_i2gO``a$E0aQ%HC z{h(ex7XL%^=b-Bc4I#qy`+@ZLf^u3nhW{b@bntd3fr977@;p$v0qPPV%|O69@sKN8 zeY$xC9l*tk5Qsvbhv@WC5rL!;kjo`rJiP$vc`M+u{1(V^P}+E*1+yHUA;D8=AU}g= zStCHD0LV{1-MoGFASa0SgDCJgCFtHx$h=%JAsda2f*C-|`WRm9$c1Dj@DUUq9*svp zAr8CG1bqJ;sEgQHgJp9VNCGy#0KRYm(iQ}_)Ig(*$6ZuF84bJz%HV}RFC+NsR`8Y+ z@O?hu8(BaT8x9_wl^hOj4#5EX&$5)~Oxjt1}G;NTZv;1}=^;1>u`;1`H6;1@`6;1|dU;1?)J;1{SU z;Md#(-D|uBy4QFObg%Ie=w9PF(7nb}z2BU-9Bj z5NPzJMn$39M@0ryoe6-HKx%DR)7wD;BnIl(fU*)qJ7mxCi`?}L3@^QyK@DS2VIlyk zQXmNx>L^gZ!Q*%f_}&VzLwfh9fW%&0{sXGzd%zsfuo~#n5YV}Opu!z~XLk>{3lCb= z1R2@<4>F=e1ME(agQ1ZQ>$pt;U-kW>;}dA=7`zY_Pr7^zDnW!mV-+tlA?Xs_E!|L5UecL&6heBq%%~ zTBm@oRM?pba*xD11_n?=2|Qx}%9`j|szwf06zM{p4r(%kECRKEUxGHJgK`Gg^Pt(2 z?h+N)t?ZyyEyxp~9x>PxpaUX7mO6uXG4w$0NrA33>4G$*L6rh1?SV=LP({$u0&Z); zTb7v+e?s#=dRGE;zx|7=>7dypu!CB_Cj)?@9Ap-#p93-*&5JKWG9YmRZuo(AIlTmp z!{ZGVM1+ciHHWA`svvMwf`$g+TP2UXs6aNF!ebC54a*WI{ZXS2w3`BQ5CACRz_%E| z2L2%Nm9d8C_^M3@c?;GGgNz{{imw;vj)B{>&@vHxA7A5}3{W4W6SS?k6V!PJZ8-(i z<&c?hQ2h=bsRVEEgj5fpv0L!T4V^tIpe8RUVSq}o7w`Q*J_8$a2-I8z-_`>eV}PE2 z09rT*%7Kt8e?i3^DA_`kfY$wh%U}tlGMK-m8(c_%jPn2$0pRw81it_izkrtjzd(=z zzd)1$zd(`$zd%+1zd%s}zd#kZ42BHOK`v1T4bnlz0hfTY9KYrq$XJagV5_fQ2z`xRwn=&00#vW_|{Pgk6y@)Igrhq9(*8ggLY_lLxf+jDKdiA z=(K>dO)qpx=;I(7mObb@h z&FRt2a}dz0Cpg8n}!H?J@+7 zx#PSXDGL{00f1x_bq5g};Z2dJ0<6=9%+1S--X9ev1}Y>?+2Kw~+bkei~rTOis&hkJpH>4x4C z4_(3yD(XSnL9;xd^awH@bc`%0=V^eA*#bX-7IYvj$Xd_=wVf?0JRs@rJt`n&FZTID z8atq)UO`R((}>1S1uUb1PWu5lqxl76i6p3Kho-#d7vlUapsBEz54af^T)Ls2?uL50 zdkW;-fo^Cy4mv0pWK4I6N(H}wlLII_fzD4w&#jtsR6s|Zf`-UJr`LgUALxWqkn#9m4Io;L3yAM11&TL50nVJ@csAy|4S8a z(5bD^GZH|15n(Mj%^nqyxo~@rw}6kR1J_%i>vceVOR#@hRKOwE&EawIAv5SU^@Bg` zL1Eqivbhs-HWDb#KsLXyJkH6`aS$5A9X%@bObiUoKa%*TKn_D{_z5~Spq#&TF=X5j zl#H5x- zf{GUCx>!?iDav_*zvUfhsP!l)hU0|O&S1bnzv_ZEms(D8~)3=A&{moR{eA5d{~7_@2v zrX75`B9cR2x*|C)t(zgOvqdEi;yh5&V<-_x>xPJ=9X!B+Bnyd20*(hQwgfpIWFp9^ zVEzu!&MBDV!TTAyVFxflwp+Z|vKYzn+acD2v|~6PoDiTP+sy!)gg|z>KzECZ4HE+c z{|ST)QO*W6YeCKiX$IK~=I;PC$6?L}bqhi5W3UWp1Q`_Oe-|N~4YeAi8^hI?K#drv zt3A3IKx#m}%Z5GR+|Ez}-v0m+@!$ua0|sI?!14}(5C?B_05uvw$pkk4y#q9T26H-S zX9mdWV3pldz&jydI3YP5lm$Ung4~7Sb}_Kqdmt4^H={?lCwQF^xVH>C)){gLASCIi zf$}Q2iwrt_9;A`~ga`jgP}zs3o~T#>udnZgY}Ew04b0yG%H%Mwf%ZFqyatx(o&w(g z@FIO7B33|NgE**11>ywQxuPv9Ag{g#Zvoo@F1C63b|TMAe%Jw@nY;>SGVK2bnwb;@ z^-q+&L5;XQ;L}!LsJVdZ&4VDjV6{4Af5#8lT96Vq(Dp2dHPHD;=yo8GCQwm`-s5-_ z4QiTC0r%QLeh2N`cx?kKIbga#{T6Ut1Fogv8Op&IPb z7RZ@(sCsr)gZ#BS3N&^G*^>gA^z`s(KA-?%IDi_lpxGqDZ;;~y7@0vK0P2nO_CQWQ z>jWKm0^>9v)BqLO5gyGuKw%=yLwF68j+t`zX?c9ih#_Ehlc z1Yt)BkIqT~SR)fM-v_c7<0u|aK7FuQvT zq-p|f<^W~6?kSMt*Fc*gLFQ?G@{l~i-@F-Ge?Xd^pnL%`8Du$lG@}zT1_6pkP%;F` zgN_pg1(^jXsesm3fIJLR?Ame4@MPyXM^Gd(xO80f==|=(zx23E$3@S}4?s1Ph2ytF z4xaq3KYaLIk9jt}1lLr}w-`J+KY8*yUGU&{`tG6l(L?b9sAii2*>c7D@;1n|&3nL; zh42X)JCNm&bP7o&Xz6kn;uJWCNJt6=H`_r6pg~FkP@ppKw}6Mmdt1O`R*x@fgT5+fjT{g9^F2M9BAS2!YKmcdr&_Sl*1t7 zC(v0_=n_UyFZXr%OC|6ng~+x+Vi(e!^ni43A>%HPFb73Eh>vC`WKH(V7>LE-!5iq# zSx~rwEQ5s|m<>yi&5$I>=+S%uT=Rg_VK=l>33Vwbb-z^n3+nEJvJ-Lw@IWgjYC*-s zlrScEVmTKsOV4tb>AT8)*N?H0Zk$>fI3kM9^DQK;6rR` z9YI4j;4Yj3x^>`F6jedifp@-u-3#6V16s-mOfXWhZTy;Wv>Y%}C&~PEB zECDYq=!6UjcDAVe0Vg(SB7+uw2F*XpVY{z5Hbi&0sOZ1u*ia3o!D~q}Km(MZEt>kE zv9(Otv=T1}=C3M!64!$C-A1f{$NjrH0fX#p3t3MjV4cDSg>zvkFb3#Jja zfwl+9BWW%0=!V<2F9aN`pfcw$C`o}G)a?-fIcCxWyw4OQ2AcNcZ&?SfQ$SZ2gIYGA z!UQd)fabbhWP!|tDF8({sLcQlD_9BWp#U{s0aP4-MmxdfENEvZhyhj$3IR(je z@QT73REgvC9-@|zf_e`$E&(zO)OrKQ0w_A55eVAH*Bt^`(eD8YOOQ=Su>-2lz?~z| zfq(F|2ghAh3LvhBE%$;Bu~vX`aRDf6RlJCv4(iN9ybTLYu)mIF?&fz++#ah8f5(1_p+m(ni7HW3xaKjypi2FAly2#cU5mZTA!v4^Uqla(@;m zdxHAGAZLNv)*X;Lq+mBK@wcRcFXn+Y-1b14!`)lJw_3f}!32~;6~Dgwg;pyd*$Uikd}`F{mtsVYbwBn}$o0X1ZHnX1F)NHifz z#J~p_fJ4jhfJfsIP_!S0i!p8hjf{fD5J3g4vO!HPu#>hxoCG@57BTGx>J5O3G4Mc| zW(#=aY^SOgB=|tXXpm;)3n!2-L3^0NZP+a;pbLvY$q1w$lvpA6JZ(_{T`B|$PteF4 zC_KS;1c90zAbrPMR6t``kX#61gGQ7=dZ613`CGPt3-A^dumPZZmzfwCx>z_^9`J8F z0WuKOdeadoF9ul-VuO|qE36G*`2PST0ve_~=D^7KIuyLcni3L2o4|6%ob?OKyoE$rg00jRSIjh?tu0Cp|jr1VCXAa#M!~E661vCPom0+uERe7(j6Xbw+~?haPNXuH_AQ_7UM9 zuqlwcroc>4&k&>+w3rt}gC?RtNes54uM@iJ4!@Vdqj$&yp}l*+U8NUlDf~b% zY>clNj<3#i`JfBJx3#E%XvB`Jd7!)8_Nag;=!GV% z@Ecu0T0z&EfFxnpnt-lno{)vB0l20Skfd3kLZdG}cdo51==? zf@}w0Zqm*IKBODO$9cI)8{}@jR*-ewEs#slxAmxiv_j6Z1#dY61v%&ndsvWHARG)5 z28At12o$EEumv5Y05-4(d;uaT@qvUv(kMX=yU`UCZjf6}@*z$JQC2M~AXh>5PJuRq zft+g9q5_hG2tqIDWI0XYm7IzK=qBIv4W7Wic-V4?0F70@w0BsvIG)AxX9 z@j-bH+-U>d%LTFnbYv_u1o990uOe8h90IuMh&@Pg2xybUxIdDL7m;%q5>MB z1D)Zb@fvh9F@#rKB8A|APGM%^ZvyQad3g`)G^kTRWe~KH2yzOjp#|=u^+L`}H2k&; zwAlQ`gf~C`gRF;iX!bxlK%jYDuwKYvG@$SYb(laM8BqTMHg$_-@tsGvLjlMt&^<`VF^k)VYipji{p)H7%n0W|dtnrZ+wurGjzKYAb+iGUW6LT0sn zIz0+NCoO@E6Ld%bvB5?PIs|~&prwlZf({NKHpmEmfgbQVLi_@dGmiNM=75iSLfhZ- z;?PP?hL_8seKwHSKwbrn)F6U{!K0hyghzLU#0$~eprbYqf>I%wO~9K>K;q!U0-N{;t#Sv&AZRWd6#Bh;z@x~p zn0odKdrX1)@!<9zNDt_|BybQynx#a<6lgUYD5gM#FDRxU*TpP>E|;7GT`oBVJlYS6 zDbOfCD5gN8p`czANI!Uo7G!5WET+K5L1PMRBs8W#a}BVV0vQ2{DX@_OP%8wWRtlo* z#CjpIf)l!r`8DViQBW7J1JW;sb@LGWu|gfg9m5<$9YcaaCyRh?i0)BY0*d6$Ju09% z4NwOhBS1$+;BC*;a;(4v26 z!Qj~40(Lmkh=?92szFJ-dka_!WEsek7v&4U+aEv|%tt%MIL120ImX9=2OL4OW(^gA z4E*383&?XI&m*6S>a#_?{%x z8K8^FLFEnjR`SLpXud~ys|OlGQ&hka-VK?H>vBi{9n{#O;tq-j(BKst0|UB4*V#jc z5-mV#I$Kl>Kr~dXNAm&jr9YsVvxYrjV;D;0;IRhZy}`X9x}!y9Iw;?BZ>WYb89*rn z66{k{Kt_NLNCXF2j|wOjAcxJv2a-$eK*QFMknunpKn5xAa$o_C$sjuqIy4NrTD_}9 zMV}44p-&z~1!ABW=7CaU(3lQrX+()8!i7+Kv5iLGv1MX_Y36Tv30~;~Ro;y-9@>Qm z-PjIFW}sB*(e01{iaqd*B6v!)1zgU6W<^04a~giz6#<$UdHobp^num&_CPM}1fAal z3M?4E`CtI3Xa-MeLZ(LD+2?#uw(^?ygw`Wl1dQ8 zPy!xC0o_9c$}x~}M#OdZsG;N`0rHsx_@X1wiE5BLKN~6%K)nVPMjM6_TaY1;AO#(= z{{pljNB}jw!6sUxnFv0%AFLa+ zNtu9&pyU8bZ44#Wpq3QK76$(5kk$@yX{y5-vOCTIbQ2Y*W;@=Zq5)%r=Gj1jVcDVr zlIL%Z;sgy_LU^DAr2q+NuoFrHKu!ROz?}e|kA-ABNIFH!^GNft8$j*=?V14D2O1;< z(Z^dq&Ken7eyVE2RKb_-<1LN}!T0!0@n z!+U^p7kDIH;l=gGpy42hcOVi6K||~ch%yi+H$?@Kx*(|y&Fe4rTY<{u7KnwQDU!%ZIDpyh8wW z1V(7TioXTiqkvYTpppc1bv)P-mJ=S}w4nehut4k5p`|`(bOc-wf>iY)#wtKfKhTYE z0U+__gWz%wyjlQKg7cLcf)YDe1azA#xCGw=S@7O1?$KS&;n7_$fWES23Rpo`iwfxa zWl#lwz@zh_2k4ka@FE)q1_mP!(1DFV_RTg3X4oYQnhW{E4%*8LYT|hA6b7X=1_t35 zo8N-21%PF~7RU$-=%PuGYrChYfP4iCrv?xo)O-WeEh><)Dk$CAq5>JAfzjag0uCUh z-Fv`NFMfYyV6b5T?XiMJ9yG$yBTF9?S)fAjg`_zsvZkm&hFMxvK*KICw}P8t(5wqD zD|*0pwt#{WxgG$IfOhj-1TB;Rt>OR`JfKB4AZPKfKLrv7-9ZQ%nuN^Yf`==Px2Q-U z| z(4gt*1E@FN+oJ-eJA1$jj6mrK%m*KN&;#x~K^IE#lq!PU1nO{rN(o437dBG`KLG(E z0IKPk85mI0jW)=e*wPIv2bOeG{TemhfL+zuq5>KEfzqJ0G$3DqW~IS2I9-58u)FuD zfJU@mJbq7Vx?nVgq>CFM6G7bu zx2Qnq9u-hM3Tj7y-HAvjpwSy>)PP57Kyz52QUzLogTz1~2#N%dQjp0YF%Tc*Q`o9< z$Y9VG@E{P#77!nnP@YwQcA&uu;}U|kD`GLg8)HbIfX9+R<08<#Y>-L>R@8&54p15d zWdUgM52|=xw*3UH-~l-mTIs^4ui=id1*LA}@*7FD0(7!l;f2W^TvaqI&qB&*ND&HO zceo3@IH=qRsd^>B^Y4uyo}U4V$j%m(32@JYnocm!gW27XCKgN#)ZBVG0d#mL_(%e% z=i%OYnac%fbb*u6@fH=(-1UFZRgB;oKmb$&fTp`)aSNW(KHj1Nn$iX<>xQsl)`7%9 zlW-t){Jnf!Nd0WcMnq7O1uN<{^5{H|wy$K1N&(n|kQr}~-QXE;(5=GEke1r978TGu z00XEq4V~};t!xJM3>dn&4th5bWQH3q2Huwmn(YQ9DUiLO1zyms2AYNk)vDl0^W%^iesDGe ztyq9M8LYLl1w0E6G6HNos2dBJ*$24*)N6$BK;CibSfirH%)sE%u|!1|Oz%O8TMMV{?e2nBJnI4AM45 zMF~Xps3?M{7RZVKSSbWr833;2Tfi$CKw%AD-~pZIg^h*Xx(?|Of|DMo@d4JeMFliU z2`V>0dWabN0?l=T#=bz)^PsUW$n^da@bo^YI|rJ{1a;>i8&n|E$Dr^APeXU_0Z*4h z$G*UWhMg7QQA)6J(6KMDkQ#cBHpOB?3(MfK&AgiM#(n z21usv0hG8mI1oG~4O)N$3KGy%J~%1D zdL#@W%fTfPXcQSbZvh&!1I=H&V17Yr{jLbLH2`EAsAUP-H4_V}v5-n9&}cKbblMI& z@{;`03A9rklt@74fzKBLbz49+I_&CW@Vyd{6>muDU}sW+2kSLkAX|Y!r4wl64x}Aa zI)U5|ax#<;x-kP}1bB)9R62pyv$UvyTmUMaAUu$Fz@-xtv~&W~dsKA5^cEFuCTQsd zI=gU=iY8ci4*1#)aOni*_o#pxn=TzKDym@RdsI}w^cEFmkhUo*N+7C7MG-_n9S?FS zsG|wpIM_7>TxdCTZ2^~IE?qs~BF&|13b=%G>6!yB@w(|GDL}g*0k54ce7=ux=_iqswzeNgGyqXM}M zrU$av3N-fy3efHzNO9i@o123!=;{QmV1$ZwdPsoMCS=ae19Yw`$fynv{w|0W9UgpL z5U03wIPk)nj}9FbJfP$Y8glON;0BohDuZ1*GPqni5;$EtDmYv^3fLVwGT1sISY0|I zSX?>+m|Z$Nm>fC^7(2ilXOX)hkgNgehJea`&}tOT7VumRXo)CjlMT2+1I<{%k`L&b zb5QbuZOeg91H#%FAU&Yw7-)(KR2P8w-90J+%nS@K7Ue;jETF6nDpf#hXS#a8DYT3r+{M~6t|#+0Ez}sx^U@g0jCpCT!YdLD6YZjsB4ajIw*BQl9@x-8c4G1+M=Qb zN^V_yz^M`x*WgszwMIo3q`XH(50t)oRK9`i>`~E&@V-K050rI4Z5YTB)gBda_5iJK zfy#rX*F2!H310UDjZLunPH=33jDo}_$O=eof;{Nbk-_WIk-+29QNiufQNZQUk-^y! z!Qs*o!S2!#z~<87!RpfCz~azR!R*jcz|;}I*ueoS&`D`@muo=EN67vl$aW*hH;WRft`xFuC+uB;z+O*(%2u&XI(vz zA$|`Qm^p*;PR*4IM_9AFN*DaPwg}Y31Z5w{(O51j8r?oBJRtW7fZPK)q8GG=+5j{y2%c009Z3P&+fV=s zm<&(=By{?y#30X{*@6~qgAYLi=|FESAdbb!R0ZXR5Eb}wLLdhp25phBfZGl^k?}qK9R{DA^P?C#7Fj9+;TJ;r{4LSoj!%t>!iy)| zpdExQ(Bo%dG-yZ)w9REFXod^C)rz98fv}ZwG;&?=S^?a>5JS z34{$?0m@*YV-7%cC-k5Q&`=VHk92tzXvf2goJoWY1eNw6FN10L(YlaBMnGN$6(=Bh zP&x5pH$ekKR4PF6T>;AE8K45X0934icS;nzkeN)#J>V-KKm_dI@(7T2@KL-GFKUsr zgEvXZH2)}oX15Y)Xd;1KOtcHMd*sEfb^rgrHU?#W4^Xis15yMo-oQr@g3oPq0M*N& zV#62Qi(pZCG4&)O>7}8S?9mbAdLujfh|j(0(Jyig$UVe$qTLvr>H>YyjxU2bKuQ;R9Zk28?cRiS)kMbifp*^ z(X6%tl`Y9QtOgl{)#^#mdc_0mnHCk$WFjG(G(k4~lmTxk0wqK6ni@#pgGcN^fealw z0ga-9#)rUzs`&;ojwDQJ=k z)K_~^0tz%xg##L&0PRo&(TztSfe7`HgA+dklKHBy6+ta~&^$OO1Aqb!Q(KrUZQH9y9>Dr$7Rtw?_oz>mA@YfZRR^9_0s3y@1@(y$7Vjyr4^KnIt5e*f^|CMfWsfdC3w zP|WOvNPB$$2yy{P1T>rpqG1b3_**2w;e;ssz$pP|c;$kO1}o{E0+~qzt)v5mPiKn? zcw~6QwN18nBTnxJjV`7tDtas zF$olt&|m}|>(#IaT(mKisK9-WT<}0$0!>Z2ps`_?3&FJrXd@-4K%$pxz9qkcUmX_wE5tdcSzi0-9rn>@Wox3<+p2hW2q>^%1Gz>sEJrS`SJgYg3>(|~&d z5F^0M&TAeA-`RT{{3Qk2BA$5(qVEDo=>@QRwt&|L>;ifA#nMhr@Bj(KXD!fH8t8;V zL<!5faYUJ>l!-gEC3nk1Fu8^_y3@W3PT6+J(^z#m*_%tfV+j@bM`

#g64sYn}3wSjKsCx;T3Ixp-!sbHH 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 YU=Tz~J1{X2-5DVYq8Mb*G@x-A0P!{*ivR!s 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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 z 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
m48_I)?Rx1i0? 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ñ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≶hAgq7v#(}_X|98HG!E7`)fd%3v{yIiwW(& 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

#g64sYn}3wSjKsCx;T3Ixp-!sbHH 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 YU=Tz~J1{X2-5DVYq8Mb*G@x-A0P!{*ivR!s 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)