From 231ce2da59cd93b4e8d8a90daca2d2111fc3ecf8 Mon Sep 17 00:00:00 2001 From: Jon Lipstate Date: Fri, 29 Aug 2025 12:41:38 -0700 Subject: [PATCH 001/162] windows i386 support --- base/runtime/core_builtin.odin | 7 ++++++- base/runtime/entry_windows.odin | 14 +++++++++++++- core/testing/signal_handler_libc.odin | 16 ++++++++++++---- src/linker.cpp | 6 +++++- src/llvm_backend.cpp | 10 +++++++++- src/main.cpp | 5 +++++ 6 files changed, 50 insertions(+), 8 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 3a51d71fb..33b600c3e 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -54,7 +54,12 @@ container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: type when !NO_DEFAULT_TEMP_ALLOCATOR { - @thread_local global_default_temp_allocator_data: Default_Temp_Allocator + when ODIN_ARCH == .i386 && ODIN_OS == .Windows { + // Thread-local storage is problematic on Windows i386 + global_default_temp_allocator_data: Default_Temp_Allocator + } else { + @thread_local global_default_temp_allocator_data: Default_Temp_Allocator + } } @(builtin, disabled=NO_DEFAULT_TEMP_ALLOCATOR) diff --git a/base/runtime/entry_windows.odin b/base/runtime/entry_windows.odin index 8eb4cc7d1..dc8e9b82c 100644 --- a/base/runtime/entry_windows.odin +++ b/base/runtime/entry_windows.odin @@ -28,7 +28,19 @@ when ODIN_BUILD_MODE == .Dynamic { return true } } else when !ODIN_TEST && !ODIN_NO_ENTRY_POINT { - when ODIN_ARCH == .i386 || ODIN_NO_CRT { + when ODIN_ARCH == .i386 && !ODIN_NO_CRT { + // Windows i386 with CRT: libcmt provides mainCRTStartup which calls _main + // Note: "c" calling convention adds underscore prefix automatically on i386 + @(link_name="main", linkage="strong", require) + main :: proc "c" (argc: i32, argv: [^]cstring) -> i32 { + args__ = argv[:argc] + context = default_context() + #force_no_inline _startup_runtime() + intrinsics.__entry_point() + #force_no_inline _cleanup_runtime() + return 0 + } + } else when ODIN_NO_CRT { @(link_name="mainCRTStartup", linkage="strong", require) mainCRTStartup :: proc "system" () -> i32 { context = default_context() diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index 4fc9552ae..961f5c7ce 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -24,10 +24,18 @@ import "core:terminal/ansi" @(private="file") stop_test_passed: 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", thread_local) -local_test_index_set: bool +when ODIN_ARCH == .i386 && ODIN_OS == .Windows { + // Thread-local storage is problematic on Windows i386 + @(private="file") + local_test_index: libc.sig_atomic_t + @(private="file") + local_test_index_set: bool +} else { + @(private="file", thread_local) + local_test_index: libc.sig_atomic_t + @(private="file", thread_local) + local_test_index_set: bool +} // Windows does not appear to have a SIGTRAP, so this is defined here, instead // of in the libc package, just so there's no confusion about it being diff --git a/src/linker.cpp b/src/linker.cpp index 41333a3c9..333cb792e 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -281,7 +281,11 @@ try_cross_linking:; link_settings = gb_string_append_fmt(link_settings, " /NOENTRY"); } } else { - link_settings = gb_string_append_fmt(link_settings, " /ENTRY:mainCRTStartup"); + // For i386 with CRT, libcmt provides the entry point + // For other cases or no_crt, we need to specify the entry point + if (!(build_context.metrics.arch == TargetArch_i386 && !build_context.no_crt)) { + link_settings = gb_string_append_fmt(link_settings, " /ENTRY:mainCRTStartup"); + } } if (build_context.build_paths[BuildPath_Symbols].name != "") { diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index ff17e9c10..f2d09f400 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2686,8 +2686,15 @@ gb_internal lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *star params->Tuple.variables[1] = alloc_entity_param(nullptr, make_token_ident("fdwReason"), t_u32, false, true); params->Tuple.variables[2] = alloc_entity_param(nullptr, make_token_ident("lpReserved"), t_rawptr, false, true); call_cleanup = false; - } else if (build_context.metrics.os == TargetOs_windows && (build_context.metrics.arch == TargetArch_i386 || build_context.no_crt)) { + } else if (build_context.metrics.os == TargetOs_windows && build_context.no_crt) { name = str_lit("mainCRTStartup"); + } else if (build_context.metrics.os == TargetOs_windows && build_context.metrics.arch == TargetArch_i386 && !build_context.no_crt) { + // Windows i386 with CRT: libcmt expects _main (main with underscore prefix) + name = str_lit("main"); + has_args = true; + slice_init(¶ms->Tuple.variables, permanent_allocator(), 2); + params->Tuple.variables[0] = alloc_entity_param(nullptr, make_token_ident("argc"), t_i32, false, true); + params->Tuple.variables[1] = alloc_entity_param(nullptr, make_token_ident("argv"), t_ptr_cstring, false, true); } else if (is_arch_wasm()) { name = str_lit("_start"); call_cleanup = false; @@ -3162,6 +3169,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { String link_name = e->Procedure.link_name; if (e->pkg->kind == Package_Runtime) { if (link_name == "main" || + link_name == "_main" || link_name == "DllMain" || link_name == "WinMain" || link_name == "wWinMain" || diff --git a/src/main.cpp b/src/main.cpp index db4dee080..c4646bc9f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3617,6 +3617,11 @@ int main(int arg_count, char const **arg_ptr) { // print_usage_line(0, "%.*s 32-bit is not yet supported for this platform", LIT(args[0])); // return 1; // } + + // Warn about Windows i386 thread-local storage limitations + if (build_context.metrics.arch == TargetArch_i386 && build_context.metrics.os == TargetOs_windows) { + gb_printf_err("Warning: Thread-local storage is disabled on Windows i386.\n"); + } // Check chosen microarchitecture. If not found or ?, print list. bool print_microarch_list = true; From f6bf88d184349cadcdd0c65476921b349510c757 Mon Sep 17 00:00:00 2001 From: FourteenBrush <74827262+FourteenBrush@users.noreply.github.com> Date: Thu, 4 Sep 2025 00:38:51 +0200 Subject: [PATCH 002/162] Add `CancelIoEx` to kernel32.odin --- core/sys/windows/kernel32.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 114e70b41..99064df60 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -413,6 +413,7 @@ foreign kernel32 { lpBytesLeftThisMessage: ^u32, ) -> BOOL --- CancelIo :: proc(handle: HANDLE) -> BOOL --- + CancelIoEx :: proc(hFile: HANDLE, lpOverlapped: LPOVERLAPPED) -> BOOL --- GetOverlappedResult :: proc( hFile: HANDLE, lpOverlapped: LPOVERLAPPED, From e0c4c5336241cb3106910bec64369888b937132b Mon Sep 17 00:00:00 2001 From: Jon Lipstate Date: Wed, 3 Sep 2025 22:32:33 -0700 Subject: [PATCH 003/162] add tls when we have crt --- base/runtime/core_builtin.odin | 4 ++-- core/testing/signal_handler_libc.odin | 4 ++-- src/main.cpp | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 33b600c3e..7e96c6784 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -54,8 +54,8 @@ container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: type when !NO_DEFAULT_TEMP_ALLOCATOR { - when ODIN_ARCH == .i386 && ODIN_OS == .Windows { - // Thread-local storage is problematic on Windows i386 + when ODIN_ARCH == .i386 && ODIN_OS == .Windows && ODIN_NO_CRT { + // Thread-local storage doesn't work on Windows i386 without CRT global_default_temp_allocator_data: Default_Temp_Allocator } else { @thread_local global_default_temp_allocator_data: Default_Temp_Allocator diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index 961f5c7ce..7c50fbb09 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -24,8 +24,8 @@ import "core:terminal/ansi" @(private="file") stop_test_passed: libc.sig_atomic_t @(private="file") stop_test_alert: libc.sig_atomic_t -when ODIN_ARCH == .i386 && ODIN_OS == .Windows { - // Thread-local storage is problematic on Windows i386 +when ODIN_ARCH == .i386 && ODIN_OS == .Windows && ODIN_NO_CRT { + // Thread-local storage doesn't work on Windows i386 without CRT @(private="file") local_test_index: libc.sig_atomic_t @(private="file") diff --git a/src/main.cpp b/src/main.cpp index c4646bc9f..198706de2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3619,8 +3619,9 @@ int main(int arg_count, char const **arg_ptr) { // } // Warn about Windows i386 thread-local storage limitations - if (build_context.metrics.arch == TargetArch_i386 && build_context.metrics.os == TargetOs_windows) { - gb_printf_err("Warning: Thread-local storage is disabled on Windows i386.\n"); + if (build_context.metrics.arch == TargetArch_i386 && build_context.metrics.os == TargetOs_windows && build_context.no_crt) { + gb_printf_err("Warning: Thread-local storage is not supported on Windows i386 with -no-crt.\n"); + gb_printf_err(" Multi-threaded code will not work correctly.\n"); } // Check chosen microarchitecture. If not found or ?, print list. From 57bc45ae30736a891e4b65c7047a091e53cf60e3 Mon Sep 17 00:00:00 2001 From: Jon Lipstate Date: Wed, 3 Sep 2025 22:51:28 -0700 Subject: [PATCH 004/162] revert to working build --- base/runtime/core_builtin.odin | 4 ++-- core/testing/signal_handler_libc.odin | 4 ++-- src/main.cpp | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 7e96c6784..33b600c3e 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -54,8 +54,8 @@ container_of :: #force_inline proc "contextless" (ptr: $P/^$Field_Type, $T: type when !NO_DEFAULT_TEMP_ALLOCATOR { - when ODIN_ARCH == .i386 && ODIN_OS == .Windows && ODIN_NO_CRT { - // Thread-local storage doesn't work on Windows i386 without CRT + when ODIN_ARCH == .i386 && ODIN_OS == .Windows { + // Thread-local storage is problematic on Windows i386 global_default_temp_allocator_data: Default_Temp_Allocator } else { @thread_local global_default_temp_allocator_data: Default_Temp_Allocator diff --git a/core/testing/signal_handler_libc.odin b/core/testing/signal_handler_libc.odin index 7c50fbb09..961f5c7ce 100644 --- a/core/testing/signal_handler_libc.odin +++ b/core/testing/signal_handler_libc.odin @@ -24,8 +24,8 @@ import "core:terminal/ansi" @(private="file") stop_test_passed: libc.sig_atomic_t @(private="file") stop_test_alert: libc.sig_atomic_t -when ODIN_ARCH == .i386 && ODIN_OS == .Windows && ODIN_NO_CRT { - // Thread-local storage doesn't work on Windows i386 without CRT +when ODIN_ARCH == .i386 && ODIN_OS == .Windows { + // Thread-local storage is problematic on Windows i386 @(private="file") local_test_index: libc.sig_atomic_t @(private="file") diff --git a/src/main.cpp b/src/main.cpp index 198706de2..c4646bc9f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3619,9 +3619,8 @@ int main(int arg_count, char const **arg_ptr) { // } // Warn about Windows i386 thread-local storage limitations - if (build_context.metrics.arch == TargetArch_i386 && build_context.metrics.os == TargetOs_windows && build_context.no_crt) { - gb_printf_err("Warning: Thread-local storage is not supported on Windows i386 with -no-crt.\n"); - gb_printf_err(" Multi-threaded code will not work correctly.\n"); + if (build_context.metrics.arch == TargetArch_i386 && build_context.metrics.os == TargetOs_windows) { + gb_printf_err("Warning: Thread-local storage is disabled on Windows i386.\n"); } // Check chosen microarchitecture. If not found or ?, print list. From 9aabe7526250045fdc914379de74488d92c123c2 Mon Sep 17 00:00:00 2001 From: FourteenBrush <74827262+FourteenBrush@users.noreply.github.com> Date: Fri, 5 Sep 2025 00:34:44 +0200 Subject: [PATCH 005/162] Add `ERROR_NOT_FOUND` Returned by `CancelIoEx` when cancelled number or io completions was 0. Was for some reason defined in `core:os`, but not in win32 pkg. Ref: https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--1000-1299- --- core/sys/windows/winerror.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/sys/windows/winerror.odin b/core/sys/windows/winerror.odin index 05ab3d028..fee04fdc4 100644 --- a/core/sys/windows/winerror.odin +++ b/core/sys/windows/winerror.odin @@ -225,6 +225,7 @@ ERROR_ENVVAR_NOT_FOUND : DWORD : 203 ERROR_OPERATION_ABORTED : DWORD : 995 ERROR_IO_PENDING : DWORD : 997 ERROR_NO_UNICODE_TRANSLATION : DWORD : 1113 +ERROR_NOT_FOUND : DWORD : 1168 ERROR_TIMEOUT : DWORD : 1460 ERROR_DATATYPE_MISMATCH : DWORD : 1629 ERROR_UNSUPPORTED_TYPE : DWORD : 1630 From cfde843778c865b2fc90b05f63e9143c6b9cd59f Mon Sep 17 00:00:00 2001 From: Michael Lee Date: Mon, 8 Sep 2025 09:08:07 -0500 Subject: [PATCH 006/162] [core:image/png] use .Image_Dimensions_Too_Large --- core/image/png/png.odin | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 87efcf9b5..ef3d617eb 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -250,10 +250,14 @@ read_header :: proc(ctx: ^$C) -> (image.PNG_IHDR, Error) { header := (^image.PNG_IHDR)(raw_data(c.data))^ // Validate IHDR using header - if width == 0 || height == 0 || u128(width) * u128(height) > image.MAX_DIMENSIONS { + if width == 0 || height == 0 { return {}, .Invalid_Image_Dimensions } + if u128(width) * u128(height) > image.MAX_DIMENSIONS { + return {}, .Image_Dimensions_Too_Large + } + if compression_method != 0 { return {}, compress.General_Error.Unknown_Compression_Method } From 2bbd0a45c0828db0cbc1f089198170813b1c3c74 Mon Sep 17 00:00:00 2001 From: Yuriy Grynevych Date: Mon, 8 Sep 2025 17:57:28 +0300 Subject: [PATCH 007/162] [core:time] time_js: tick_now(): Use f64 (was f32) as a return type of odin_env.tick_now(). --- core/time/time_js.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/time/time_js.odin b/core/time/time_js.odin index 9175fbfe9..0e021dc88 100644 --- a/core/time/time_js.odin +++ b/core/time/time_js.odin @@ -24,7 +24,7 @@ _sleep :: proc "contextless" (d: Duration) { _tick_now :: proc "contextless" () -> Tick { foreign odin_env { - tick_now :: proc "contextless" () -> f32 --- + tick_now :: proc "contextless" () -> f64 --- } return Tick{i64(tick_now()*1e6)} } From d704c45c2417da5fa1aef36262b6adbfdd19411a Mon Sep 17 00:00:00 2001 From: Hisham Aburaqibah Date: Wed, 15 Jan 2025 15:11:09 +0200 Subject: [PATCH 008/162] core/image: some jpegs have APP13 or COM markers after SOI --- core/image/general.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/image/general.odin b/core/image/general.odin index 336b41d25..1662cf14e 100644 --- a/core/image/general.odin +++ b/core/image/general.odin @@ -147,7 +147,7 @@ which_bytes :: proc(data: []byte) -> Which_File_Type { return .JPEG case s[:3] == "\xff\xd8\xff": switch s[3] { - case 0xdb, 0xee, 0xe1, 0xe0: + case 0xdb, 0xee, 0xe1, 0xe0, 0xfe, 0xed: return .JPEG } switch { From a6f18c336772a735a70b439c7ffb687e3fb92efb Mon Sep 17 00:00:00 2001 From: Hisham Aburaqibah Date: Thu, 16 Jan 2025 11:11:39 +0200 Subject: [PATCH 009/162] image/jpeg: implement jpeg decoding for baseline and extended sequential jpegs --- core/image/common.odin | 135 ++++- core/image/jpeg/jpeg.odin | 928 +++++++++++++++++++++++++++++++++++ core/image/jpeg/jpeg_js.odin | 3 + core/image/jpeg/jpeg_os.odin | 18 + examples/all/all_main.odin | 13 +- 5 files changed, 1089 insertions(+), 8 deletions(-) create mode 100644 core/image/jpeg/jpeg.odin create mode 100644 core/image/jpeg/jpeg_js.odin create mode 100644 core/image/jpeg/jpeg_os.odin diff --git a/core/image/common.odin b/core/image/common.odin index 690ebc045..6d67115a1 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -64,6 +64,7 @@ Image_Metadata :: union #shared_nil { ^QOI_Info, ^TGA_Info, ^BMP_Info, + ^JPEG_Info, } @@ -112,8 +113,7 @@ Image_Option: `.alpha_drop_if_present` If the image has an alpha channel, drop it. - You may want to use `.alpha_ - tiply` in this case. + You may want to use `.alpha_premultiply` in this case. NOTE: For PNG, this also skips handling of the tRNS chunk, if present, unless you select `alpha_premultiply`. @@ -163,6 +163,7 @@ Error :: union #shared_nil { PNG_Error, QOI_Error, BMP_Error, + JPEG_Error, compress.Error, compress.General_Error, @@ -575,6 +576,136 @@ TGA_Info :: struct { extension: Maybe(TGA_Extension), } + +/* + JPEG-specific +*/ +JFIF_Magic := [?]byte{0x4A, 0x46, 0x49, 0x46} // "JFIF" +JFXX_Magic := [?]byte{0x4A, 0x46, 0x58, 0x58} // "JFXX" + +JPEG_Error :: enum { + None = 0, + Duplicate_SOI_Marker, + Invalid_JFXX_Extension_Code, + Encountered_SOS_Before_SOF, + Invalid_Quantization_Table_Precision, + Invalid_Quantization_Table_Index, + Invalid_Huffman_Coefficient_Type, + Invalid_Huffman_Table_Index, + Unsupported_Frame_Type, + Invalid_Frame_Bit_Depth_Combo, + Invalid_Sampling_Factor, + Unsupported_12_Bit_Depth, + Multiple_SOS_Markers, + Encountered_RST_Marker_Outside_ECS, + Extra_Data_After_SOS, // Image seemed to have decoded okay, but there's more data after SOS + Invalid_Thumbnail_Size, +} + +JFIF_Unit :: enum byte { + None = 0, + Dots_Per_Inch = 1, + Dots_Per_Centimeter = 2, +} + +JFIF_APP0 :: struct { + version: u16be, + units: JFIF_Unit, + x_density: u16be, + y_density: u16be, + x_thumbnail: byte, + y_thumbnail: byte, + thumbnail: []RGB_Pixel `fmt:"-"`, + greyscale_thumbnail: bool, +} + +JFXX_APP0 :: struct { + extension_code: JFXX_Extension_Code, + x_thumbnail: int, + y_thumbnail: int, + thumbnail: []byte `fmt:"-"`, +} + +JFXX_Extension_Code :: enum u8 { + Thumbnail_JPEG = 0x10, + Thumbnail_1_Byte_Palette = 0x11, + Thumbnail_3_Byte_RGB = 0x13, +} + +JPEG_Marker :: enum u8 { + SOF0 = 0xC0, + SOF1 = 0xC1, + SOF2 = 0xC2, + SOF3 = 0xC3, + DHT = 0xC4, + SOF5 = 0xC5, + SOF6 = 0xC6, + SOF7 = 0xC7, + JPG = 0xC8, + SOF9 = 0xC9, + SOF10 = 0xCA, + SOF11 = 0xCB, + DAC = 0xCC, + SOF13 = 0xCD, + SOF14 = 0xCE, + SOF15 = 0xCF, + RST0 = 0xD0, + RST1 = 0xD1, + RST2 = 0xD2, + RST3 = 0xD3, + RST4 = 0xD4, + RST5 = 0xD5, + RST6 = 0xD6, + RST7 = 0xD7, + SOI = 0xD8, + EOI = 0xD9, + SOS = 0xDA, + DQT = 0xDB, + DNL = 0xDC, + DRI = 0xDD, + DHP = 0xDE, + EXP = 0xDF, + APP0 = 0xE0, + APP1 = 0xE1, + APP2 = 0xE2, + APP3 = 0xE3, + APP4 = 0xE4, + APP5 = 0xE5, + APP6 = 0xE6, + APP7 = 0xE7, + APP8 = 0xE8, + APP9 = 0xE9, + APP10 = 0xEA, + APP11 = 0xEB, + APP12 = 0xEC, + APP13 = 0xED, + APP14 = 0xEE, + APP15 = 0xEF, + JPG0 = 0xF0, + JPG1 = 0xF1, + JPG2 = 0xF2, + JPG3 = 0xF3, + JPG4 = 0xF4, + JPG5 = 0xF5, + JPG6 = 0xF6, + JPG7 = 0xF7, + JPG8 = 0xF8, + JPG9 = 0xF9, + JPG10 = 0xFA, + JPG11 = 0xFB, + JPG12 = 0xFC, + JPG13 = 0xFD, + COM = 0xFE, + TEM = 0x01, +} + +JPEG_Info :: struct { + jfif_app0: Maybe(JFIF_APP0), + jfxx_app0: Maybe(JFXX_APP0), + comments: [dynamic]string, + //exif: Maybe(Exif), +} + // Function to help with image buffer calculations compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) { size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height diff --git a/core/image/jpeg/jpeg.odin b/core/image/jpeg/jpeg.odin new file mode 100644 index 000000000..53320cb38 --- /dev/null +++ b/core/image/jpeg/jpeg.odin @@ -0,0 +1,928 @@ +package jpeg + +import "core:bytes" +import "core:compress" +import "core:fmt" +import "core:math" +import "core:mem" +import "core:image" +import "core:slice" +import "core:strings" + +Image :: image.Image +Error :: image.Error +Options :: image.Options + +HUFFMAN_MAX_SYMBOLS :: 162 +HUFFMAN_MAX_BITS :: 16 +// 768 bytes of 24-bit RGB values. +THUMBNAIL_PALETTE_SIZE :: 768 +BLOCK_SIZE :: 8 +COEFFICIENT_COUNT :: BLOCK_SIZE * BLOCK_SIZE + +Coefficient :: enum u8 { + DC, + AC, +} + +Component :: enum u8 { + Y = 1, + Cb = 2, + Cr = 3, +} + +HuffmanTable :: struct { + symbols: [HUFFMAN_MAX_SYMBOLS]byte, + codes: [HUFFMAN_MAX_SYMBOLS]u32, + offsets: [HUFFMAN_MAX_BITS + 1]byte, +} + +QuantizationTable :: [COEFFICIENT_COUNT]u16be + +ColorComponent :: struct { + dc_table_idx: u8, + ac_table_idx: u8, + quantization_table_idx: u8, + v_sampling_factor: int, + h_sampling_factor: int, +} + +// 8x8 block of pixels +Block :: [Component][COEFFICIENT_COUNT]i16 + +@(private="file") +zigzag := [?]byte{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, +} + +@(optimization_mode="favor_size", private="file") +refill_msb :: #force_inline proc(z: ^compress.Context_Memory_Input, width := i8(48)) { + refill := u64(width) + b := u64(0) + + if z.num_bits > refill { + return + } + + for { + if len(z.input_data) != 0 { + b = u64(z.input_data[0]) + + if len(z.input_data) > 1 { + next := u64(z.input_data[1]) + + if b == 0xFF { + if next == 0x00 { + // 0x00 is used as a stuffing to indicate that the 0xFF is part of the data and not + // the beginning of a marker + z.input_data = z.input_data[2:] + } else if next >= cast(u64)image.JPEG_Marker.RST0 && next <= cast(u64)image.JPEG_Marker.RST7 { + // Skip any RSTn markers if we encounter them + b = u64(z.input_data[2]) + z.input_data = z.input_data[3:] + } + } else { + z.input_data = z.input_data[1:] + } + } else { + z.input_data = z.input_data[1:] + } + } else { + b = 0 + } + + z.code_buffer |= ((b << 56) >> u8(z.num_bits)) + z.num_bits += 8 + if z.num_bits > refill { + break + } + } +} + +@(optimization_mode="favor_size", private="file") +consume_bits_msb :: #force_inline proc(z: ^compress.Context_Memory_Input, width: u8) { + z.code_buffer <<= width + z.num_bits -= u64(width) +} + +@(private="file") +byte_align :: #force_inline proc(z: ^compress.Context_Memory_Input) { + skip := z.num_bits % 8 + consume_bits_msb(z, cast(u8)skip) +} + +@(optimization_mode="favor_size", private="file") +peek_bits_msb :: #force_inline proc(z: ^compress.Context_Memory_Input, width: u8) -> u32 { + if z.num_bits < u64(width) { + refill_msb(z) + } + return u32((z.code_buffer &~ (max(u64) >> width)) >> (64 - width)) +} + +@(optimization_mode="favor_size", private="file") +read_bits_msb :: #force_inline proc(z: ^compress.Context_Memory_Input, width: u8) -> u32 { + k := #force_inline peek_bits_msb(z, width) + #force_inline consume_bits_msb(z, width) + return k +} + +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 +} + +@(private="file") +get_symbol :: proc(ctx: ^$C, huffman_table: HuffmanTable) -> byte { + possible_code: u32 = 0 + + for i in 0.. (img: ^Image, err: Error) { + context.allocator = allocator + options := options + + // Precalculate IDCT scaling factors + m0 := 2.0 * math.cos_f32(1.0 / 16.0 * 2.0 * math.PI) + m1 := 2.0 * math.cos_f32(2.0 / 16.0 * 2.0 * math.PI) + m3 := 2.0 * math.cos_f32(2.0 / 16.0 * 2.0 * math.PI) + m5 := 2.0 * math.cos_f32(3.0 / 16.0 * 2.0 * math.PI) + m2 := m0 - m5 + m4 := m0 + m5 + + s0 := math.cos_f32(0.0 / 16.0 * math.PI) / math.sqrt_f32(8.0) + s1 := math.cos_f32(1.0 / 16.0 * math.PI) / 2.0 + s2 := math.cos_f32(2.0 / 16.0 * math.PI) / 2.0 + s3 := math.cos_f32(3.0 / 16.0 * math.PI) / 2.0 + s4 := math.cos_f32(4.0 / 16.0 * math.PI) / 2.0 + s5 := math.cos_f32(5.0 / 16.0 * math.PI) / 2.0 + s6 := math.cos_f32(6.0 / 16.0 * math.PI) / 2.0 + s7 := math.cos_f32(7.0 / 16.0 * math.PI) / 2.0 + + 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} + } + + first := compress.read_u8(ctx) or_return + soi := cast(image.JPEG_Marker)compress.read_u8(ctx) or_return + if first != 0xFF && soi != .SOI { + return img, .Invalid_Signature + } + + img = new(Image) or_return + img.which = .JPEG + + expect_EOI := false + huffman: [Coefficient][4]HuffmanTable + quantization: [4]QuantizationTable + color_components: [Component]ColorComponent + restart_interval: int + // Image width and height in MCUs + mcu_width: int + mcu_height: int + // Image width and height in blocks + block_width: int + block_height: int + blocks: []Block + defer delete(blocks) + + loop: for { + first = compress.read_u8(ctx) or_return + if first == 0xFF { + marker := cast(image.JPEG_Marker)compress.read_u8(ctx) or_return + if expect_EOI && marker != .EOI { + return img, .Extra_Data_After_SOS + } + #partial switch marker { + case cast(image.JPEG_Marker)0xFF: + // If we encounter multiple FF bytes then just skip them + continue + case .SOI: + return img, .Duplicate_SOI_Marker + case .APP0: + ident := make([dynamic]byte, 0, 16, context.temp_allocator) or_return + length := cast(int)((compress.read_data(ctx, u16be) or_return) - 2) + for { + b := compress.read_u8(ctx) or_return + if b == 0x00 { + break + } + append(&ident, b) + } + if slice.equal(ident[:], image.JFIF_Magic[:]) { + version := compress.read_data(ctx, u16be) or_return + units := cast(image.JFIF_Unit)(compress.read_u8(ctx) or_return) + x_density := compress.read_data(ctx, u16be) or_return + y_density := compress.read_data(ctx, u16be) or_return + x_thumbnail := cast(int)compress.read_u8(ctx) or_return + y_thumbnail := cast(int)compress.read_u8(ctx) or_return + thumbnail: []image.RGB_Pixel + + if x_thumbnail * y_thumbnail != 0 { + greyscale_thumbnail := false + thumbnail_size := x_thumbnail * y_thumbnail * 3 + // According to the JFIF spec, the thumbnail should always be made of RGB pixels. + // But some jpegs encode single-channel thumbnails. + if thumbnail_size != length - 14 && thumbnail_size / 3 == length - 14 { + thumbnail_size = x_thumbnail * y_thumbnail + greyscale_thumbnail = true + } else { + return img, .Invalid_Thumbnail_Size + } + thumb_pixels := slice.reinterpret([]image.RGB_Pixel, compress.read_slice_from_memory(ctx, x_thumbnail * y_thumbnail) or_return) + + if .return_metadata in options { + thumbnail = make([]image.RGB_Pixel, x_thumbnail * y_thumbnail) or_return + copy(thumbnail, thumb_pixels) + + info: ^image.JPEG_Info + if img.metadata == nil { + info = new(image.JPEG_Info) or_return + } else { + info = img.metadata.(^image.JPEG_Info) + } + info.jfif_app0 = image.JFIF_APP0{ + version, + units, + x_density, + y_density, + cast(u8)x_thumbnail, + cast(u8)y_thumbnail, + thumbnail, + greyscale_thumbnail, + } + img.metadata = info + } + } + } else if slice.equal(ident[:], image.JFXX_Magic[:]) { + extension_code := cast(image.JFXX_Extension_Code)compress.read_u8(ctx) or_return + thumbnail: []byte + + switch extension_code { + // We return the JPEG-compressed bytes for this type of thumbnail. + // It's up to the user if they want to decode it by checking the extension code + // and calling image.load() on the thumbnail. + // Not sure where to document that though, maybe it's better if the thumbnail is always raw pixel data. + case .Thumbnail_JPEG: + // +1 for the NUL byte + thumbnail_len := length - (size_of(image.JFXX_Magic) + 1 + size_of(image.JFXX_Extension_Code)) + thumbnail_jpeg := compress.read_slice(ctx, thumbnail_len) or_return + + if .return_metadata in options { + thumbnail = make([]byte, thumbnail_len) or_return + copy(thumbnail, thumbnail_jpeg) + + info: ^image.JPEG_Info + if img.metadata == nil { + info = new(image.JPEG_Info) or_return + } else { + info = img.metadata.(^image.JPEG_Info) + } + info.jfxx_app0 = image.JFXX_APP0{ + extension_code, + 0, + 0, + thumbnail, + } + img.metadata = info + } + case .Thumbnail_3_Byte_RGB: + x_thumbnail := cast(int)compress.read_u8(ctx) or_return + y_thumbnail := cast(int)compress.read_u8(ctx) or_return + pixels := compress.read_slice(ctx, x_thumbnail * y_thumbnail * 3) or_return + + if .return_metadata in options { + thumbnail = make([]byte, x_thumbnail * y_thumbnail * 3) or_return + copy(thumbnail, pixels) + + info: ^image.JPEG_Info + if img.metadata == nil { + info = new(image.JPEG_Info) or_return + } else { + info = img.metadata.(^image.JPEG_Info) + } + info.jfxx_app0 = image.JFXX_APP0{ + extension_code, + x_thumbnail, + y_thumbnail, + thumbnail, + } + img.metadata = info + } + case .Thumbnail_1_Byte_Palette: // NOTE: NOT TESTED. Couldn't find a jpeg to test this with. + x_thumbnail := cast(int)compress.read_u8(ctx) or_return + y_thumbnail := cast(int)compress.read_u8(ctx) or_return + palette := slice.reinterpret([]image.RGB_Pixel, compress.read_slice(ctx, THUMBNAIL_PALETTE_SIZE / 3) or_return) + old_pixels := compress.read_slice(ctx, x_thumbnail * y_thumbnail) or_return + + if .return_metadata in options { + pixels := make([]byte, x_thumbnail * y_thumbnail * 3) or_return + for i in 0.. 0 { + precision_and_index := compress.read_u8(ctx) or_return + precision := precision_and_index >> 4 + index := precision_and_index & 0xF + + if precision != 0 && precision != 1 { + return img, .Invalid_Quantization_Table_Precision + } + + if index < 0 || index > 3 { + return img, .Invalid_Quantization_Table_Index + } + + // When precision is 0, we read 64 u8s. + // when it's 1, we read 64 u16s. + table_bytes := 64 + if precision == 1 { + table_bytes = 128 + table := compress.read_slice(ctx, table_bytes) or_return + for v, i in slice.reinterpret([]u16be, table) { + quantization[index][i] = v + } + } else { + table := compress.read_slice(ctx, table_bytes) or_return + for v, i in table { + quantization[index][i] = cast(u16be)v + } + } + + length -= table_bytes + 1 + } + case .DHT: + length := (compress.read_data(ctx, u16be) or_return) - 2 + + for length > 0 { + type_index := compress.read_u8(ctx) or_return + type := cast(Coefficient)((type_index >> 4) & 0xF) + index := type_index & 0xF + + if type != .DC && type != .AC { + return img, .Invalid_Huffman_Coefficient_Type + } + + if index < 0 || index > 3 { + return img, .Invalid_Huffman_Table_Index + } + + lengths := compress.read_slice(ctx, HUFFMAN_MAX_BITS) or_return + num_symbols := 0 + for length, i in lengths { + num_symbols += cast(int)length + huffman[type][index].offsets[i + 1] = cast(u8)num_symbols + } + + symbols := compress.read_slice(ctx, num_symbols) or_return + copy(huffman[type][index].symbols[:], symbols) + + length -= cast(u16be)(1 + HUFFMAN_MAX_BITS + num_symbols) + + code: u32 = 0 + for i in 0.. .Cr { + fmt.println("Found unknown component ID:", id) + return img, .Image_Does_Not_Adhere_to_Spec + } + + h_v_factors := compress.read_u8(ctx) or_return + horizontal_sampling := h_v_factors >> 4 + vertical_sampling := h_v_factors & 0xF + + // TODO: spec says the range for the sampling factors is 1-4 + // We only support 1,2 for now. + if horizontal_sampling < 1 || horizontal_sampling > 2 { + return img, .Invalid_Sampling_Factor + } + if vertical_sampling < 1 || vertical_sampling > 2 { + return img, .Invalid_Sampling_Factor + } + + if id == .Y { + if horizontal_sampling == 2 && mcu_width % 2 == 1 { + block_width += 1 + } + if vertical_sampling == 2 && mcu_height % 2 == 1 { + block_height += 1 + } + } else { + if horizontal_sampling != 1 && vertical_sampling != 1 { + return img, .Invalid_Sampling_Factor + } + } + + quantization_table_idx := compress.read_u8(ctx) or_return + + if quantization_table_idx < 0 || quantization_table_idx > 3 { + return img, .Invalid_Quantization_Table_Index + } + + color_components[id].quantization_table_idx = quantization_table_idx + color_components[id].v_sampling_factor = cast(int)vertical_sampling + color_components[id].h_sampling_factor = cast(int)horizontal_sampling + } + case .SOF2: // Progressive DCT + unimplemented("SOF2") + case .SOF3: // Lossless (sequential) + fallthrough + case .SOF5: // Differential sequential DCT + fallthrough + case .SOF6: // Differential progressive DCT + fallthrough + case .SOF7: // Differential lossless (sequential) + fallthrough + case .SOF9: // Extended sequential DCT, Arithmetic coding + fallthrough + case .SOF10: // Progressive DCT, Arithmetic coding + fallthrough + case .SOF11: // Lossless (sequential), Arithmetic coding + fallthrough + case .SOF13: // Differential sequential DCT, Arithmetic coding + fallthrough + case .SOF14: // Differential progressive DCT, Arithmetic coding + fallthrough + case .SOF15: // Differential lossless (sequential), Arithmetic coding + fmt.println(marker) + return img, .Unsupported_Frame_Type + case .SOS: + if img.channels == 0 && img.depth == 0 && img.width == 0 && img.height == 0 { + return img, .Encountered_SOS_Before_SOF + } + + if .do_not_decompress_image in options { + return img, nil + } + + // Length + compress.read_data(ctx, u16be) or_return + num_components := compress.read_u8(ctx) or_return + if num_components != 1 && num_components != 3 { + return img, .Invalid_Number_Of_Channels + } + + for _ in 0.. .Cr { + return img, .Image_Does_Not_Adhere_to_Spec + } + + // high 4 is DC, low 4 is AC + coefficient_indices := compress.read_u8(ctx) or_return + dc_table_idx := coefficient_indices >> 4 + ac_table_idx := coefficient_indices & 0xF + + if (dc_table_idx < 0 || dc_table_idx > 3) || (ac_table_idx < 0 || ac_table_idx > 3) { + return img, .Invalid_Huffman_Table_Index + } + + color_components[component_id].dc_table_idx = dc_table_idx + color_components[component_id].ac_table_idx = ac_table_idx + } + // TODO: These aren't used for sequential DCT, only progressive and lossless. + Ss := compress.read_u8(ctx) or_return + _ = Ss + Se := compress.read_u8(ctx) or_return + _ = Se + Ah_Al := compress.read_u8(ctx) or_return + _ = Ah_Al + + blocks = make([]Block, block_height * block_width) or_return + + previous_dc: [Component]i16 + + luma_v_sampling_factor := color_components[.Y].v_sampling_factor + luma_h_sampling_factor := color_components[.Y].h_sampling_factor + + restart_interval *= luma_v_sampling_factor * luma_h_sampling_factor + #no_bounds_check for y := 0; y < mcu_height; y += luma_v_sampling_factor { + for x := 0; x < mcu_width; x += luma_h_sampling_factor { + blk := y * block_width + x + + if restart_interval != 0 && blk % restart_interval == 0 { + previous_dc[.Y] = 0 + previous_dc[.Cb] = 0 + previous_dc[.Cr] = 0 + byte_align(ctx) + } + for c in 1..=img.channels { + c := cast(Component)c + for v in 0.. 11 { + return img, .Corrupt + } + + dc_coeff := cast(i16)read_bits_msb(ctx, length) + + if length != 0 && dc_coeff < (1 << (length - 1)) { + dc_coeff -= (1 << length) - 1 + } + mcu[0] = (dc_coeff + previous_dc[c]) * cast(i16)quantization_table[0] + previous_dc[c] = dc_coeff + previous_dc[c] + + for i := 1; i < COEFFICIENT_COUNT; i += 1 { + // High nibble is amount of 0s to skip. + // Low nibble is length of coeff. + symbol := get_symbol(ctx, ac_table) + + // Special symbol used to indicate + // that the rest of the MCU is filled with 0s + if symbol == 0x00 { + continue h_loop + } + + amnt_zeros := int(symbol >> 4) + ac_coeff_len := symbol & 0xF + ac_coeff: i16 = 0 + + if i + amnt_zeros >= COEFFICIENT_COUNT || ac_coeff_len > 10 { + return img, .Corrupt + } + + i += amnt_zeros + + ac_coeff = cast(i16)read_bits_msb(ctx, ac_coeff_len) + if ac_coeff < (1 << (ac_coeff_len - 1)) { + ac_coeff -= (1 << ac_coeff_len) - 1 + } + + mcu[zigzag[i]] = ac_coeff * cast(i16)quantization_table[i] + } + } + } + } + + for c in 1..=img.channels { + c := cast(Component)c + + for v in 0..= 0; v -= 1 { + for h := luma_h_sampling_factor - 1; h >= 0; h -= 1 { + y_blk := &blocks[(y + v) * block_width + (x + h)] + + for j := BLOCK_SIZE - 1; j >= 0; j -= 1 { + for k := BLOCK_SIZE - 1; k >= 0; k -= 1 { + i := j * BLOCK_SIZE + k + cbcrPixelRow := j / luma_v_sampling_factor + 4 * v + cbcrPixelColumn := k / luma_h_sampling_factor + 4 * h + cbcrPixel := cbcrPixelRow * BLOCK_SIZE + cbcrPixelColumn + + r := cast(i16)math.clamp(cast(f32)y_blk[.Y][i] + 1.402 * cast(f32)cbcr_blk[.Cr][cbcrPixel] + 128, 0, 255) + g := cast(i16)math.clamp(cast(f32)y_blk[.Y][i] - 0.344 * cast(f32)cbcr_blk[.Cb][cbcrPixel] - 0.714 * cast(f32)cbcr_blk[.Cr][cbcrPixel] + 128, 0, 255) + b := cast(i16)math.clamp(cast(f32)y_blk[.Y][i] + 1.772 * cast(f32)cbcr_blk[.Cb][cbcrPixel] + 128, 0, 255) + + y_blk[.Y][i] = r + y_blk[.Cb][i] = g + y_blk[.Cr][i] = b + } + } + } + } + } + } + + if resize(&img.pixels.buf, img.width * img.height * img.channels) != nil { + return img, .Unable_To_Allocate_Or_Resize + } + + out := mem.slice_data_cast([]image.RGB_Pixel, img.pixels.buf[:]) + for y 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 + } +} diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index d7b58dfca..1e9047399 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -76,12 +76,13 @@ package all @(require) import "core:hash" @(require) import "core:hash/xxhash" -@(require) import "core:image" -@(require) import "core:image/bmp" -@(require) import "core:image/netpbm" -@(require) import "core:image/png" -@(require) import "core:image/qoi" -@(require) import "core:image/tga" +import image "core:image" +import bmp "core:image/bmp" +import netpbm "core:image/netpbm" +import png "core:image/png" +import qoi "core:image/qoi" +import tga "core:image/tga" +import jpeg "core:image/jpeg" @(require) import "core:io" @(require) import "core:log" From 8644f3bebabf462a42af6ad82c4b9cab8f3d8b4c Mon Sep 17 00:00:00 2001 From: Hisham Aburaqibah Date: Thu, 16 Jan 2025 16:21:50 +0200 Subject: [PATCH 010/162] image/jpeg: better pack APP0 structs --- core/image/common.odin | 12 ++++++------ core/image/jpeg/jpeg.odin | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/image/common.odin b/core/image/common.odin index 6d67115a1..5279166ef 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -610,19 +610,19 @@ JFIF_Unit :: enum byte { JFIF_APP0 :: struct { version: u16be, - units: JFIF_Unit, x_density: u16be, y_density: u16be, - x_thumbnail: byte, - y_thumbnail: byte, - thumbnail: []RGB_Pixel `fmt:"-"`, + units: JFIF_Unit, + x_thumbnail: u8, + y_thumbnail: u8, greyscale_thumbnail: bool, + thumbnail: []RGB_Pixel `fmt:"-"`, } JFXX_APP0 :: struct { extension_code: JFXX_Extension_Code, - x_thumbnail: int, - y_thumbnail: int, + x_thumbnail: u8, + y_thumbnail: u8, thumbnail: []byte `fmt:"-"`, } diff --git a/core/image/jpeg/jpeg.odin b/core/image/jpeg/jpeg.odin index 53320cb38..945c51877 100644 --- a/core/image/jpeg/jpeg.odin +++ b/core/image/jpeg/jpeg.odin @@ -270,13 +270,13 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } info.jfif_app0 = image.JFIF_APP0{ version, - units, x_density, y_density, + units, cast(u8)x_thumbnail, cast(u8)y_thumbnail, - thumbnail, greyscale_thumbnail, + thumbnail, } img.metadata = info } @@ -330,8 +330,8 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } info.jfxx_app0 = image.JFXX_APP0{ extension_code, - x_thumbnail, - y_thumbnail, + cast(u8)x_thumbnail, + cast(u8)y_thumbnail, thumbnail, } img.metadata = info @@ -359,8 +359,8 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } info.jfxx_app0 = image.JFXX_APP0{ extension_code, - x_thumbnail, - y_thumbnail, + cast(u8)x_thumbnail, + cast(u8)y_thumbnail, pixels, } img.metadata = info From 694593c5f27fd952b1c80917fa27d35023c290b6 Mon Sep 17 00:00:00 2001 From: Hisham Aburaqibah Date: Thu, 16 Jan 2025 16:22:09 +0200 Subject: [PATCH 011/162] image/jpeg: more bounds checking and skip malformed APP0 Also increase the maximum huffman symbols to 176 --- core/image/common.odin | 1 + core/image/jpeg/jpeg.odin | 40 ++++++++++++++++++++++++--------------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/core/image/common.odin b/core/image/common.odin index 5279166ef..a12760265 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -600,6 +600,7 @@ JPEG_Error :: enum { Encountered_RST_Marker_Outside_ECS, Extra_Data_After_SOS, // Image seemed to have decoded okay, but there's more data after SOS Invalid_Thumbnail_Size, + Huffman_Symbols_Exceeds_Max, } JFIF_Unit :: enum byte { diff --git a/core/image/jpeg/jpeg.odin b/core/image/jpeg/jpeg.odin index 945c51877..f675f90e7 100644 --- a/core/image/jpeg/jpeg.odin +++ b/core/image/jpeg/jpeg.odin @@ -13,7 +13,7 @@ Image :: image.Image Error :: image.Error Options :: image.Options -HUFFMAN_MAX_SYMBOLS :: 162 +HUFFMAN_MAX_SYMBOLS :: 176 HUFFMAN_MAX_BITS :: 16 // 768 bytes of 24-bit RGB values. THUMBNAIL_PALETTE_SIZE :: 768 @@ -75,21 +75,21 @@ refill_msb :: #force_inline proc(z: ^compress.Context_Memory_Input, width := i8( if len(z.input_data) != 0 { b = u64(z.input_data[0]) - if len(z.input_data) > 1 { + if len(z.input_data) > 1 && b == 0xFF { next := u64(z.input_data[1]) - if b == 0xFF { - if next == 0x00 { - // 0x00 is used as a stuffing to indicate that the 0xFF is part of the data and not - // the beginning of a marker - z.input_data = z.input_data[2:] - } else if next >= cast(u64)image.JPEG_Marker.RST0 && next <= cast(u64)image.JPEG_Marker.RST7 { - // Skip any RSTn markers if we encounter them + if next == 0x00 { + // 0x00 is used as a stuffing to indicate that the 0xFF is part of the data and not + // the beginning of a marker + z.input_data = z.input_data[2:] + } else if next >= cast(u64)image.JPEG_Marker.RST0 && next <= cast(u64)image.JPEG_Marker.RST7 { + // Skip any RSTn markers if we encounter them + if len(z.input_data) > 2 { b = u64(z.input_data[2]) z.input_data = z.input_data[3:] + } else { + b = 0 } - } else { - z.input_data = z.input_data[1:] } } else { z.input_data = z.input_data[1:] @@ -237,6 +237,12 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a append(&ident, b) } if slice.equal(ident[:], image.JFIF_Magic[:]) { + if length != 14 { + // Malformed APP0. Skip it + compress.read_slice(ctx, length - len(ident) - 1) or_return + continue + } + version := compress.read_data(ctx, u16be) or_return units := cast(image.JFIF_Unit)(compress.read_u8(ctx) or_return) x_density := compress.read_data(ctx, u16be) or_return @@ -437,13 +443,17 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } lengths := compress.read_slice(ctx, HUFFMAN_MAX_BITS) or_return - num_symbols := 0 + num_symbols: u8 = 0 for length, i in lengths { - num_symbols += cast(int)length - huffman[type][index].offsets[i + 1] = cast(u8)num_symbols + num_symbols += length + huffman[type][index].offsets[i + 1] = num_symbols } - symbols := compress.read_slice(ctx, num_symbols) or_return + if num_symbols > HUFFMAN_MAX_SYMBOLS { + return img, .Huffman_Symbols_Exceeds_Max + } + + symbols := compress.read_slice(ctx, cast(int)num_symbols) or_return copy(huffman[type][index].symbols[:], symbols) length -= cast(u16be)(1 + HUFFMAN_MAX_BITS + num_symbols) From 57a92b14cc1621efbf0eef61acf48b1b760917b1 Mon Sep 17 00:00:00 2001 From: IllusionMan1212 Date: Fri, 31 Jan 2025 14:34:05 +0200 Subject: [PATCH 012/162] jpeg: support images that encode zero-based component ids --- core/image/jpeg/jpeg.odin | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/core/image/jpeg/jpeg.odin b/core/image/jpeg/jpeg.odin index f675f90e7..d81b2a3ba 100644 --- a/core/image/jpeg/jpeg.odin +++ b/core/image/jpeg/jpeg.odin @@ -200,6 +200,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a img.which = .JPEG expect_EOI := false + zero_based_components := false huffman: [Coefficient][4]HuffmanTable quantization: [4]QuantizationTable color_components: [Component]ColorComponent @@ -520,12 +521,17 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a for _ in 0.. .Cr { - fmt.println("Found unknown component ID:", id) return img, .Image_Does_Not_Adhere_to_Spec } @@ -606,6 +612,9 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a for _ in 0.. .Cr { return img, .Image_Does_Not_Adhere_to_Spec } From cb820eea4d77c2b06ac8c2fdb01fdcfad0053d38 Mon Sep 17 00:00:00 2001 From: IllusionMan1212 Date: Sat, 1 Feb 2025 23:01:03 +0200 Subject: [PATCH 013/162] jpeg: extract Exif data --- core/image/common.odin | 10 ++++- core/image/jpeg/jpeg.odin | 87 ++++++++++++++++++++++++++++++++----- core/image/png/helpers.odin | 4 +- core/image/png/png.odin | 8 ---- 4 files changed, 87 insertions(+), 22 deletions(-) diff --git a/core/image/common.odin b/core/image/common.odin index a12760265..0e5668e50 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -67,6 +67,13 @@ Image_Metadata :: union #shared_nil { ^JPEG_Info, } +Exif :: struct { + byte_order: enum { + little_endian, + big_endian, + }, + data: []u8 `fmt:"-"`, +} /* @@ -582,6 +589,7 @@ TGA_Info :: struct { */ JFIF_Magic := [?]byte{0x4A, 0x46, 0x49, 0x46} // "JFIF" JFXX_Magic := [?]byte{0x4A, 0x46, 0x58, 0x58} // "JFXX" +Exif_Magic := [?]byte{0x45, 0x78, 0x69, 0x66} // "Exif" JPEG_Error :: enum { None = 0, @@ -704,7 +712,7 @@ JPEG_Info :: struct { jfif_app0: Maybe(JFIF_APP0), jfxx_app0: Maybe(JFXX_APP0), comments: [dynamic]string, - //exif: Maybe(Exif), + exif: [dynamic]Exif, } // Function to help with image buffer calculations diff --git a/core/image/jpeg/jpeg.odin b/core/image/jpeg/jpeg.odin index d81b2a3ba..31bb11a13 100644 --- a/core/image/jpeg/jpeg.odin +++ b/core/image/jpeg/jpeg.odin @@ -2,7 +2,6 @@ package jpeg import "core:bytes" import "core:compress" -import "core:fmt" import "core:math" import "core:mem" import "core:image" @@ -19,6 +18,7 @@ HUFFMAN_MAX_BITS :: 16 THUMBNAIL_PALETTE_SIZE :: 768 BLOCK_SIZE :: 8 COEFFICIENT_COUNT :: BLOCK_SIZE * BLOCK_SIZE +SEGMENT_MAX_SIZE :: 65533 Coefficient :: enum u8 { DC, @@ -235,7 +235,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a if b == 0x00 { break } - append(&ident, b) + append(&ident, b) or_return } if slice.equal(ident[:], image.JFIF_Magic[:]) { if length != 14 { @@ -343,7 +343,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } img.metadata = info } - case .Thumbnail_1_Byte_Palette: // NOTE: NOT TESTED. Couldn't find a jpeg to test this with. + case .Thumbnail_1_Byte_Palette: // NOTE(illusionman1212): NOT TESTED. Couldn't find a jpeg to test this with. x_thumbnail := cast(int)compress.read_u8(ctx) or_return y_thumbnail := cast(int)compress.read_u8(ctx) or_return palette := slice.reinterpret([]image.RGB_Pixel, compress.read_slice(ctx, THUMBNAIL_PALETTE_SIZE / 3) or_return) @@ -380,17 +380,78 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a compress.read_slice(ctx, length - len(ident) - 1) or_return continue } - // case .APP1: // Exif metadata - // unimplemented("APP1") + case .APP1: // Metadata + length := cast(int)((compress.read_data(ctx, u16be) or_return) - 2) + if .return_metadata not_in options { + compress.read_slice(ctx, length) or_return + continue + } + info: ^image.JPEG_Info + if img.metadata == nil { + info = new(image.JPEG_Info) or_return + } else { + info = img.metadata.(^image.JPEG_Info) + } + + ident := make([dynamic]byte, 0, 16, context.temp_allocator) or_return + for { + b := compress.read_u8(ctx) or_return + if b == 0x00 { + break + } + append(&ident, b) or_return + } + + if slice.equal(ident[:], image.Exif_Magic[:]) { + // Padding byte according to section 4.7.2.2 in Exif spec 3.0 + compress.read_u8(ctx) or_return + + exif: image.Exif + peek := compress.peek_data(ctx, [4]byte) or_return + if peek[0] == 'M' && peek[1] == 'M' { + exif.byte_order = .big_endian + if peek[2] != 0 || peek[3] != 42 { + // - 2 for the NUL byte and padding byte + compress.read_slice(ctx, length - len(ident) - 2) or_return + continue + } + } else if peek[0] == 'I' && peek[1] == 'I' { + exif.byte_order = .little_endian + if peek[2] != 42 || peek[3] != 0 { + compress.read_slice(ctx, length - len(ident) - 2) or_return + continue + } + } else { + // If we can't determine the endianness then this Exif data is likely a continuation of the previous + // APP1 Exif data + + // We only treat it as such if a previous Exif entry exists and its data length is the max + if len(info.exif) > 0 && len(info.exif[len(info.exif) - 1].data) == SEGMENT_MAX_SIZE - len(ident) - 2 { + exif.byte_order = info.exif[len(info.exif) - 1].byte_order + } else { + compress.read_slice(ctx, length - len(ident) - 2) or_return + continue + } + } + + // - 2 for the NUL byte and padding byte + data := compress.read_slice(ctx, length - len(ident) - 2) or_return + exif.data = make([]byte, len(data)) or_return + copy(exif.data, data) + + append(&info.exif, exif) or_return + img.metadata = info + } else { + // - 1 for the NUL byte + compress.read_slice(ctx, length - len(ident) - 1) or_return + continue + } case .COM: length := (compress.read_data(ctx, u16be) or_return) - 2 comment := string(compress.read_slice(ctx, cast(int)length) or_return) if .return_metadata in options { if info, ok := img.metadata.(^image.JPEG_Info); ok { - if info.comments == nil { - info.comments = make([dynamic]string, 0, 8, allocator) or_return - } - append(&info.comments, strings.clone(comment)) + append(&info.comments, strings.clone(comment)) or_return } } case .DQT: @@ -504,7 +565,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a // how many lines in the frame we have. // ISO/IEC 10918-1: 1993. // Section B.2.5 - if width == 0 || height == 0 { + if img.width == 0 || img.height == 0 || img.width * img.height > image.MAX_DIMENSIONS { return img, .Invalid_Image_Dimensions } @@ -592,7 +653,6 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a case .SOF14: // Differential progressive DCT, Arithmetic coding fallthrough case .SOF15: // Differential lossless (sequential), Arithmetic coding - fmt.println(marker) return img, .Unsupported_Frame_Type case .SOS: if img.channels == 0 && img.depth == 0 && img.width == 0 && img.height == 0 { @@ -936,6 +996,11 @@ destroy :: proc(img: ^Image) { } delete(v.comments) + for exif in v.exif { + delete(exif.data) + } + delete(v.exif) + free(v) } free(img) diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index a9495ed4d..97e70226c 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -366,7 +366,7 @@ chrm :: proc(c: image.PNG_Chunk) -> (res: cHRM, ok: bool) { return } -exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) { +exif :: proc(c: image.PNG_Chunk) -> (res: image.Exif, ok: bool) { ok = true @@ -396,4 +396,4 @@ exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) { General helper functions */ -compute_buffer_size :: image.compute_buffer_size \ No newline at end of file +compute_buffer_size :: image.compute_buffer_size diff --git a/core/image/png/png.odin b/core/image/png/png.odin index ef3d617eb..3516fc8d3 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -138,14 +138,6 @@ Text :: struct { text: string, } -Exif :: struct { - byte_order: enum { - little_endian, - big_endian, - }, - data: []u8, -} - iCCP :: struct { name: string, profile: []u8, From 14bf730a2c33f9d0680026106c94c01c2ed7d017 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 8 Sep 2025 17:17:14 +0200 Subject: [PATCH 014/162] Make `_register` contextless --- core/image/jpeg/jpeg.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/image/jpeg/jpeg.odin b/core/image/jpeg/jpeg.odin index 31bb11a13..e67b104e3 100644 --- a/core/image/jpeg/jpeg.odin +++ b/core/image/jpeg/jpeg.odin @@ -1007,6 +1007,6 @@ destroy :: proc(img: ^Image) { } @(init, private) -_register :: proc() { +_register :: proc "contextless" () { image.register(.JPEG, load_from_bytes, destroy) } From 5b600671312253277be1cf653520dfd3d896713c Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 8 Sep 2025 17:25:55 +0200 Subject: [PATCH 015/162] Fix `examples/all` --- examples/all/all_main.odin | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 1e9047399..205eb2a3c 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -76,13 +76,13 @@ package all @(require) import "core:hash" @(require) import "core:hash/xxhash" -import image "core:image" -import bmp "core:image/bmp" -import netpbm "core:image/netpbm" -import png "core:image/png" -import qoi "core:image/qoi" -import tga "core:image/tga" -import jpeg "core:image/jpeg" +@(require) import "core:image" +@(require) import "core:image/bmp" +@(require) import "core:image/netpbm" +@(require) import "core:image/png" +@(require) import "core:image/qoi" +@(require) import "core:image/tga" +@(require) import "core:image/jpeg" @(require) import "core:io" @(require) import "core:log" From 2f59e0175eba5efc8c5f1360f3c87e3dc3c20b93 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 8 Sep 2025 17:44:58 +0200 Subject: [PATCH 016/162] Address some naming issues --- core/image/jpeg/jpeg.odin | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/core/image/jpeg/jpeg.odin b/core/image/jpeg/jpeg.odin index e67b104e3..6da601c89 100644 --- a/core/image/jpeg/jpeg.odin +++ b/core/image/jpeg/jpeg.odin @@ -31,15 +31,15 @@ Component :: enum u8 { Cr = 3, } -HuffmanTable :: struct { +Huffman_Table :: struct { symbols: [HUFFMAN_MAX_SYMBOLS]byte, codes: [HUFFMAN_MAX_SYMBOLS]u32, offsets: [HUFFMAN_MAX_BITS + 1]byte, } -QuantizationTable :: [COEFFICIENT_COUNT]u16be +Quantization_Table :: [COEFFICIENT_COUNT]u16be -ColorComponent :: struct { +Color_Component :: struct { dc_table_idx: u8, ac_table_idx: u8, quantization_table_idx: u8, @@ -143,7 +143,7 @@ load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context } @(private="file") -get_symbol :: proc(ctx: ^$C, huffman_table: HuffmanTable) -> byte { +get_symbol :: proc(ctx: ^$C, huffman_table: Huffman_Table) -> byte { possible_code: u32 = 0 for i in 0.. image.MAX_DIMENSIONS { + if img.width == 0 || img.height == 0 { return img, .Invalid_Image_Dimensions } + if u128(img.width) * u128(img.height) > image.MAX_DIMENSIONS { + return img, .Image_Dimensions_Too_Large + } + // TODO: Some JPEGs use CMYK as the color model which means there will be 4 components if components != 1 && components != 3 { return img, .Invalid_Number_Of_Channels @@ -919,15 +923,15 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a for j := BLOCK_SIZE - 1; j >= 0; j -= 1 { for k := BLOCK_SIZE - 1; k >= 0; k -= 1 { i := j * BLOCK_SIZE + k - cbcrPixelRow := j / luma_v_sampling_factor + 4 * v - cbcrPixelColumn := k / luma_h_sampling_factor + 4 * h - cbcrPixel := cbcrPixelRow * BLOCK_SIZE + cbcrPixelColumn + cbcr_pixel_row := j / luma_v_sampling_factor + 4 * v + cbcr_pixel_column := k / luma_h_sampling_factor + 4 * h + cbcr_pixel := cbcr_pixel_row * BLOCK_SIZE + cbcr_pixel_column - r := cast(i16)math.clamp(cast(f32)y_blk[.Y][i] + 1.402 * cast(f32)cbcr_blk[.Cr][cbcrPixel] + 128, 0, 255) - g := cast(i16)math.clamp(cast(f32)y_blk[.Y][i] - 0.344 * cast(f32)cbcr_blk[.Cb][cbcrPixel] - 0.714 * cast(f32)cbcr_blk[.Cr][cbcrPixel] + 128, 0, 255) - b := cast(i16)math.clamp(cast(f32)y_blk[.Y][i] + 1.772 * cast(f32)cbcr_blk[.Cb][cbcrPixel] + 128, 0, 255) + r := cast(i16)math.clamp(cast(f32)y_blk[.Y][i] + 1.402 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255) + g := cast(i16)math.clamp(cast(f32)y_blk[.Y][i] - 0.344 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] - 0.714 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255) + b := cast(i16)math.clamp(cast(f32)y_blk[.Y][i] + 1.772 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] + 128, 0, 255) - y_blk[.Y][i] = r + y_blk[.Y][i] = r y_blk[.Cb][i] = g y_blk[.Cr][i] = b } @@ -1009,4 +1013,4 @@ destroy :: proc(img: ^Image) { @(init, private) _register :: proc "contextless" () { image.register(.JPEG, load_from_bytes, destroy) -} +} \ No newline at end of file From 2de4918fb321c746570af831f9cab713bd48ecad Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 8 Sep 2025 18:18:08 +0200 Subject: [PATCH 017/162] Add basic test for JPG using Odin emblem --- tests/core/download_assets.py | 6 ++-- tests/core/image/test_core_image.odin | 48 +++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/tests/core/download_assets.py b/tests/core/download_assets.py index fc4a71cdc..f6f5396c8 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', 'BMP'] +TEST_SUITES = ['PNG', 'XML', 'BMP', 'JPG'] DOWNLOAD_BASE_PATH = sys.argv[1] + "/{}" ASSETS_BASE_URL = "https://raw.githubusercontent.com/odin-lang/test-assets/master/{}/{}" HMAC_KEY = "https://odin-lang.org" @@ -280,7 +280,9 @@ HMAC_DIGESTS = { 'rletopdown.bmp': "37500893aad0b40656aa80fd5c7c5f9b35d033018b8070d8b1d7baeb34c90f90462288b13295204b90aa3e5c9be797d22a328e3714ab259334e879a09a3de175", 'shortfile.bmp': "be3ffade7999304f00f9b7d152b5b27811ad1166d0fd43004392467a28f44b6a4ec02a23c0296bacd4f02f8041cd824b9ca6c9fc31fed27e36e572113bb47d73", - 'unicode.xml': "e0cdc94f07fdbb15eea811ed2ae6dcf494a83d197dafe6580c740270feb0d8f5f7146d4a7d4c2d2ea25f8bd9678bc986123484b39399819a6b7262687959d1ae", + 'emblem-1024.jpg': "d7b7e3ffaa5cda04c667e3742752091d78e02aa2d3c7a63406af679ce810a0a86666b10fcab12cc7ead2fadf2f6c2e1237bc94f892a62a4c218e18a20f96dbe4", + + 'unicode.xml': "e0cdc94f07fdbb15eea811ed2ae6dcf494a83d197dafe6580c740270feb0d8f5f7146d4a7d4c2d2ea25f8bd9678bc986123484b39399819a6b7262687959d1ae", } def try_download_file(url, out_file): diff --git a/tests/core/image/test_core_image.odin b/tests/core/image/test_core_image.odin index 8f6091481..ca737973d 100644 --- a/tests/core/image/test_core_image.odin +++ b/tests/core/image/test_core_image.odin @@ -19,6 +19,7 @@ import pbm "core:image/netpbm" import "core:image/png" import "core:image/qoi" import "core:image/tga" +import "core:image/jpeg" import "core:bytes" import "core:hash" @@ -28,6 +29,7 @@ import "core:time" TEST_SUITE_PATH_PNG :: ODIN_ROOT + "tests/core/assets/PNG" TEST_SUITE_PATH_BMP :: ODIN_ROOT + "tests/core/assets/BMP" +TEST_SUITE_PATH_JPG :: ODIN_ROOT + "tests/core/assets/JPG" I_Error :: image.Error @@ -2360,6 +2362,52 @@ run_bmp_suite :: proc(t: ^testing.T, suite: []Test) { return } +// JPG test image +Basic_JPG_Tests := []Test{ + { + "emblem-1024", { + {Default, nil, {1024, 1024, 3, 8}, 0x_46a29e0f}, + }, + }, +} + +@test +jpeg_test_basic :: proc(t: ^testing.T) { + run_jpg_suite(t, Basic_JPG_Tests) +} + +run_jpg_suite :: proc(t: ^testing.T, suite: []Test) { + for file in suite { + test_file := strings.concatenate({TEST_SUITE_PATH_JPG, "/", file.file, ".jpg"}, context.allocator) + defer delete(test_file) + + for test in file.tests { + img, err := jpeg.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. + 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) + + // Save to BMP file to check load + test_bmp := strings.concatenate({TEST_SUITE_PATH_JPG, "/", file.file, ".bmp"}, context.temp_allocator) + + save_err := bmp.save(test_bmp, img) + testing.expectf(t, save_err == nil, "expected saving to BMP in memory not to raise error, got %v", save_err) + } + bmp.destroy(img) + } + } + return +} + @test will_it_blend :: proc(t: ^testing.T) { Pixel :: image.RGB_Pixel From fa36c6a5f532c87eddf79afc47e13bb8821e86ee Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 8 Sep 2025 18:19:21 +0200 Subject: [PATCH 018/162] Add JPG test assets to .gitignore --- tests/core/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/core/.gitignore b/tests/core/.gitignore index 2a4e21679..40bf8bf19 100644 --- a/tests/core/.gitignore +++ b/tests/core/.gitignore @@ -1,4 +1,5 @@ *.bmp *.zip *.png +*.jpg math_big_test_library.* \ No newline at end of file From 9abc3f67b52860f3fe85bfdbcd3bc014a4651ad5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 9 Sep 2025 10:36:22 +0100 Subject: [PATCH 019/162] Fix constant procedure parameters when passing literals --- src/check_expr.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 542a2afa1..e005b0bd0 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6473,7 +6473,14 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } if (e && e->kind == Entity_Constant && is_type_proc(e->type)) { - if (o->mode != Addressing_Constant) { + bool ok = false; + if (o->mode == Addressing_Constant) { + ok = true; + } else if (o->value.kind == ExactValue_Procedure) { + ok = true; + } + + if (!ok) { if (show_error) { error(o->expr, "Expected a constant procedure value for the argument '%.*s'", LIT(e->token.string)); } @@ -11423,6 +11430,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast o->mode = Addressing_Value; o->type = type; + o->value = exact_value_procedure(node); case_end; case_ast_node(te, TernaryIfExpr, node); From dd9fceaae15e4fa597d0ffbaa12bdaeee26ae637 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 9 Sep 2025 13:53:43 +0200 Subject: [PATCH 020/162] Make progressive JPEGs return a proper error Add progressive JPEG file to test suite and test that loading it returns the expected `Unsupported_Frame_Type` error. This JPEG variant will hopefully be supported in the future, but we should at least return an error rather than use `unsupported()`. --- core/image/jpeg/jpeg.odin | 2 +- tests/core/download_assets.py | 535 +++++++++++++------------- tests/core/image/test_core_image.odin | 10 +- 3 files changed, 277 insertions(+), 270 deletions(-) diff --git a/core/image/jpeg/jpeg.odin b/core/image/jpeg/jpeg.odin index 6da601c89..58c6dff50 100644 --- a/core/image/jpeg/jpeg.odin +++ b/core/image/jpeg/jpeg.odin @@ -637,7 +637,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a color_components[id].h_sampling_factor = cast(int)horizontal_sampling } case .SOF2: // Progressive DCT - unimplemented("SOF2") + fallthrough case .SOF3: // Lossless (sequential) fallthrough case .SOF5: // Differential sequential DCT diff --git a/tests/core/download_assets.py b/tests/core/download_assets.py index f6f5396c8..22324dff0 100644 --- a/tests/core/download_assets.py +++ b/tests/core/download_assets.py @@ -13,276 +13,277 @@ ASSETS_BASE_URL = "https://raw.githubusercontent.com/odin-lang/test-assets/ma HMAC_KEY = "https://odin-lang.org" HMAC_HASH = hashlib.sha3_512 HMAC_DIGESTS = { - 'basi0g01.png': "eb26159a783a4dcf27878754e34db6960e992072fc565b12360c894746bc6369101df579e3b9a5c478167f0435e89efb2ab5484056f66c7104f07267f043c82d", - 'basi0g02.png': "f0f74d33ee4b3d3c9f1f4aacf8b0620a33cf1d5c6e9f409e11b4a6e95f2ab1c9159c1bd0bcf6f9553397bf670f4c92712b703d496665d4c6e88fdaf6a0c98620", - 'basi0g04.png': "7b81665f353ff6347304b761d26b4f1baa6118daefc4790f4b0e690150a4e6306514f2aef87e2c39e83e56a0b704df987b10a5366244da58697cda4d5d2745c1", - 'basi0g08.png': "6c809574674494d10eb04c58191c4e0b35813c45962ecafe36a9fe5ba44e5d74ba098e0a524a30c1508eb2dad601f04d5230dceb61a307ef1120f07a8bcbc92f", - 'basi0g16.png': "dd439069fb704b8deff3de822314c012e91a0bd84984d2e79888c6394b78557a0ae6197a7b0a4d64eb4e16fc758946c4f1f13d0d06e512ed9743c4a355b02488", - 'basi2c08.png': "e851fb030f88ead2541a01591882dde5fd9f5dea6bb2b11b374394ed86abc7289e72c4c90808b261aceac13111d3c05b39033168b42ffd1c7ea367314b3a8dc5", - 'basi2c16.png': "e0f81dd459860f7eaf04a2c54932547f41cdc9903a1295a26cc4a2087fd79c98a2e5d5e74f22345dba08b1eb12f3f53c9b904c88b756cbae0c19f9d680e2f1e8", - 'basi3p01.png': "aa21cbcd5d64e6e88056af907509f3b98b00d105198ea2a669fcb25b6966f06d5e4e3afc160b2382935e8ab8e7ccf61733424dfb39b9e864306b82f8355bbb58", - 'basi3p02.png': "d6182370236630d42fd75eed7dc54079ddb7fa9bd601eba22a8dc3331dce31b9a9465027145c29bd56cf778ab0ac029df6aa6481ef65c9464a1ce1d89423faa5", - 'basi3p04.png': "2b35fb98884da370f25833d25f0ab4b188cc26440321e95519131c675818c128b2029d38b3477ef94d88052ec6e9f8b1f7410c5576ce0b47b2fb7a5ba771ce48", - 'basi3p08.png': "7415890831958ca34c73de3e33a9c88980e788d10c983c3a00967886f05053d359499bf0bad386af8ee1bfdb73f666e1d541b0ab682c6092ebdbad3d93e145b1", - 'basi4a08.png': "22ab23a3070d9f1316e8c65b333745e4abf6dcbe63d9d0ada7f6881604b498a92e5b49eb8c1e32f453f4319d94d3d0f48441bac2e79ea528fd7ace0dbbacea9f", - 'basi4a16.png': "2b1436a619f91cf4045e18acf03201b5e9c7774fc77e4c3f38d668984d98543fe64978e80854f30adfecd89b71632945250508bea9eb1ce8e9145e3e55bdeece", - 'basi6a08.png': "aea929866edc5e607d7dc90fcd871f4b6d548abb379b9427fce75a773705e0d8c3ed03bb64e530482b72973eb09844638db5db93666ca1605ff6bbaf194f70e8", - 'basi6a16.png': "29b6ce7c2469e24587b87559f85551334c51bd9d7867f4066c0f7dcb01d710e11e87ee8654c91dc986454cd70f8bd6021e2086ea40e48a28d68a9bdf2283573f", - 'basn0g01.png': "52da610af6f97b03fd8e9f8e52276227f57a97e2bc7e47ae9484b2a37472330f8f5ecef2d6de7fde8f7eb8f144f233bdcf8a3d716c5d193e44d9590af2709cea", - 'basn0g02.png': "676c727faf143af658ed6c6333b15e50387c566c1846a4dda636ed4a2dbb1c0d6d32f0db635479f6851047a32c6d9c52898cfda004ea986853f3ee13750ed90a", - 'basn0g04.png': "123a6652b56f5b858373836509f6b7433221e6ff4eb9ce8a68d0d12bfb27cad0c1bb0cb159523ef1ae5997cb07f994bfbfeabcf0a813aa83495f8269a7a6c4e5", - 'basn0g08.png': "ad5ff30613a462a9b61242d42bb7c854b8247d80a1d03a3919dfdfe8a91c8a962ff51932b2411f714f1be7b803cbd4605c1d4441882cad7e8f44dd8d927198a4", - 'basn0g16.png': "40a96ed10ad5cc58180701e0bb6d6f36c1752c8b8329a80be2960535ad9e168469efbd8f3dc825b57bf65e78a3b61835f19697f2a22511f79c06ce2c6a0034c0", - 'basn2c08.png': "fd92ba13d2038c632fd8bdd3e33971cc69f561bee1eb04ff7920625c6771e0cf7fdda29e48b8a6ca71702c0624ebbca232793f28bb2073b0247d30ce0c549ad5", - 'basn2c16.png': "f6b3cfbc70ae502edbeabde828823dc079dc4e9a586a7fc326c70caec3033124121e3ee2e6481e6ce966efcdef9376cb3ec3edb8e677e91563f510bd6f6e83e6", - 'basn3p01.png': "d262c97549cd1d43e36f5990dac051ead80f1456693c8b1e19a8a48796195831a6ddd5c4a0ac44d22fee51ce4d9392c1c40a0be05571b1b2a961691636d69cd3", - 'basn3p02.png': "a00e375e1fbcc23de38b3b11a0fda4d0204f55473f74e37e0bb5c5f391b37c019293ccddbb99196b9457cd0b7fe5b9e34a55dc6b8167b6909b38813e37a95e46", - 'basn3p04.png': "1a6ab4c567e75b20ee6cdb400dd1f7764a05679c35010e6144fede5a55767dd1861df8e6240e1e59d4166e25d972ff05a41cdfd527818b6ec960a0f9d57d0939", - 'basn3p08.png': "b8f99f6410cc1ac0ceb366e93fc1008f45fb2499e39c489fafa143e8e881f334735e118eec93dde687bf4ea524b4548741c520d770ba6d7071e8fdce576644de", - 'basn4a08.png': "4b7758d4fce3bba95ef3d09cff0ab28c21b8bf568156730fd4e3bcea1c1c2d3ff77f53fe3551f617bd01c8c96e929eb20bf04f5c1a84f37c3cd6bacd4730e6eb", - 'basn4a16.png': "aa0c411f8bb15fc801b072f1048de2fd08761fe245ad267e8503eef8ffe1c39be845e99c04245771afe9b102328129f6375869a4e6cec60eeca641b3e5df3a8b", - 'basn6a08.png': "78d7c811c5463ce53a50874baed840ba70c811513e0ff98fda15a08f01ac6a7c7139a9979cb3688451af6b7e2981699be3c27a386d77af5e60d1acc9e23cb103", - 'basn6a16.png': "e1ad0ed2bf5774b2733d23f2ea9570c6edc933298572933010144ef07bfe6a7878f7367a76f2affbf9845d5b69ea3c84b885f9415e18e752fb1bf21d0dd9d9a2", - 'bgai4a08.png': "22ab23a3070d9f1316e8c65b333745e4abf6dcbe63d9d0ada7f6881604b498a92e5b49eb8c1e32f453f4319d94d3d0f48441bac2e79ea528fd7ace0dbbacea9f", - 'bgai4a16.png': "2b1436a619f91cf4045e18acf03201b5e9c7774fc77e4c3f38d668984d98543fe64978e80854f30adfecd89b71632945250508bea9eb1ce8e9145e3e55bdeece", - 'bgan6a08.png': "78d7c811c5463ce53a50874baed840ba70c811513e0ff98fda15a08f01ac6a7c7139a9979cb3688451af6b7e2981699be3c27a386d77af5e60d1acc9e23cb103", - 'bgan6a16.png': "e1ad0ed2bf5774b2733d23f2ea9570c6edc933298572933010144ef07bfe6a7878f7367a76f2affbf9845d5b69ea3c84b885f9415e18e752fb1bf21d0dd9d9a2", - 'bgbn4a08.png': "4fb9b1023c8c7accb93cf42bd345846c787735df01f7db0df4233737f144e2d5348afaededcdd8c2bcf2e53ce00e2ff095c91de14d7818b4b41c396ab85c9137", - 'bggn4a16.png': "302d7c02ddb0be62aeffeeda46957102c13a2701b841a757486cb3eec7b5456cfabc6ee1954f334ad525badc7c607621a72559b01ce338a0f39e2426d0a03fce", - 'bgwn6a08.png': "d380c477775d102288b43c387dbd7b931f85520f2f6151e3ccc5a25764169312d98fcebf69fb86d3d12520edc3de9bfced559ddf73366f0679e52defb9116424", - 'bgyn6a16.png': "b688d395552eb2a8506fa539ee9159b40a57d02c81860bcf76176213504eb10bdd89de7ce9da452e43f2a46c15290755cdc4816738620b27aed45b3ea7f7ee84", - 'ccwn2c08.png': "c4d58269ef360e42870cce5fb0b1134bfe5706f172c5ff916a338978798fc541a463ab4fdb7c91c424fa385f0ac5d6f576967f178b858d7503f609b2daeacb64", - 'ccwn3p08.png': "d89becf7d24dc57c931a8af1b9dc03ae10302785cc67028cec7255e1aa0ffb0d508d894023a98c10a24cf145aa60b93f977acb8231c8fee9d3f0934484757c30", - 'cdfn2c08.png': "a5faf9cfd284d3750ce2c2f8e15d0ff1bca09da4a9d46b8797c7a1a9f2ebe79d7a7a552bad3c3c049d6186460e81e1e7f9b94e7bb98a3c79d2516f6fb7ac6aa6", - 'cdhn2c08.png': "396eec14e76c00d22ee153171243ac531dcf7adad830948dfd5d6f0a84155734b90407e74f8c1ab8cfb0af5084c03f511530739751f8da070379c826545d0237", - 'cdsn2c08.png': "051b0defa4363ceb3fbc44f460f99a10cdd7c15797ea6d8aa62513b8b6fc5000fe542899db464db30eac56d0c9cd2a5d3778c6962f3aaa85a20881951a7f963a", - 'cdun2c08.png': "946074446fb99f0b790146df0f6e362c68e79b46ec7cbc4ce3d3dc9f75f66e58715caac4b3d7aa8fe8360c32abd2702e7228f9c03b4ba2a0d7f2f970a7ba8f32", - 'ch1n3p04.png': "6680e895195295989850129bcf9d0d0c47efca1c3bc363e4ea822c688707955cdcd07ae92e6e213ee388a62e14b51e036f9a9e075ad7b0f736b0cbc5cc4a108d", - 'ch2n3p08.png': "778ba389d9d0bcbe188e2501c7c1d047f1287179b3760b43da0053f0853e77a0575c7179a02f2442b83efa6cc0951b32d09bd7ea3b6848f261f10b8127e4b0e0", - 'cm0n0g04.png': "9e7e94d201a1222383294bd60e2627207b7b99c0bd80aecd677a1b7d8b372de4981b781fb794811d29a4b2c4db4f5a40c953039c31084f8f3de9aff13fbd1dc9", - 'cm7n0g04.png': "33bb0ffdfe54d655683e4e1bd6d963b9a35a6500129b5dd574245e78971de8d19f27b86a76d42056716b90c2bfc950241bfdc6c4b52ee0e125571de36ba61904", - 'cm9n0g04.png': "fd59386d15a1a324c5ff32f574484c1209afe4ad99f7dc12dce45b2a9bfefd33311ec93211fea3a8e61d1ad2f98220b0c15d151be304bdaa7f88126d54299f68", - 'cs3n2c16.png': "93ca3eeba9aef67de15f943fb2383d208ad1a68518ab78c9fa5ea306ad077b789e873f9e453f0354535167aa4e3d3fec715df05e827bf510948bf0e6fa0dacad", - 'cs3n3p08.png': "e8ddabbf0b17db03bcd7ff0c61d08cc971b3b88e3b3ecd2d4a296da8cf7d7708fa5b42ff3d5813d936ab7f5b4bafbf5842fa4c0fcf0cff15258548d2d26882f3", - 'cs5n2c08.png': "4e3eae53e8c27d4c58d8ad8cdff2c06b00a61d1528a41f5566694f10ef33c4a66f40d3d9fc247731c3ba6dfb734c9f2f90ef031c64486fbdf8a825e354fd67b9", - 'cs5n3p08.png': "31ee0f7f789c1203a9b0e81da34960a47017f3fce37bf2b711738ebd535bb3003ef60c2b0b1fca16587a59955c1ae24b9d186d7ead1d3ece6ada2bbfbc5aee85", - 'cs8n2c08.png': "11bbc80c3168f632deb4453f9199c7994255a0f4f2d7d2d2b06a00931fda4c56985b5a2ca8e969893ff2d65bf330c3d3148b80cbf72aacfe0054b612e6ae05e6", - 'cs8n3p08.png': "49ae3a9050487f6cae826407d421f32a8c67977dd057a6e36f1b2e1ec6abdf5cd46c8929ef4d7b565921ed6c9ae0dde89e243714fdf1510503c7d7b8af6d66e3", - 'ct0n0g04.png': "b6a66fda9ad82cb3287a1ef54702926204bbd4186969cdc2e0741a28dae1869ed76939199f44fd7dd78332753cb8b20a53c4bf740e353f2cc4247870cf576848", - 'ct1n0g04.png': "7cdbaebb6be5d9165972b5fa24f2b64ab437f5337f0548f3d212be8aa95e5dee6ff9abd76ec232faafedc4a41ab8825221b6ced2ecab81c1b3ab20042ceb81a3", - 'cten0g04.png': "648769788c3eb4acd06784dc428bab9e7868328aa3cfe3718ee6656e13e40c4502ca5ac1fe6d4baccb666d4a4f6b3a5d3c97d489f9919ae0d26c2dcdc62e526c", - 'ctfn0g04.png': "c40768807f13dee8dd80ab012bf82b7e8551cb20bde3c7b5c4c45ed19c764cb981945ef036292bd9a2838d34b1b52284295133fea326aea9fc391708bf1d1fbe", - 'ctgn0g04.png': "e0cb7a2d983f1c1c38054b02aabeec657e3f85dba3a52b4878798664c8c6db77cef84d01f042368de6e77cd07b7c2cc6e324aa70bd44c2c8a3642f0cc8fc6a65", - 'cthn0g04.png': "b8614c14bc8c32bb3b29d3b0ac9f3d04c0fcaa40818ab86866e2a8ba1e0c6a87a989f499f208a776d47bff8ab09bfa09352d52089b3cd5bf599faf27627a630b", - 'ctjn0g04.png': "02e2e0a5fa8054e7c7e1c583945da06fb31eff9a3fdd1a7c9f76ba3333f20ce60aec8fafe07f608e9d65a6543e97bab0ff8ae66f3995e8035c05a7c53168ca9a", - 'ctzn0g04.png': "ac35974bd1182e339327245ffa1c0afde634b3a67ee9d6e4e22deee983d6d31054e7843cf1720087b88f6510ed70276e9b0bf43deaf861bd16ceb166140352f9", - 'emblem-1024.png': "5b2e174847274069fc8f4e30c32870ddc7c0e3616a3f04fd41583543aa99ac6d9b7d5a63ed1da672c551b4d193568bb58ddac5e87a101b73367c7c3b01e36fa9", - 'exif2c08.png': "e81b85aa36c9aab55754dd8b73d42497c38eddf9ff3c2981529eb62993d8c0ab33d1b5146a350dd8a1c528d42a967733b2f86248ac615e0254fed53d66d0d895", - 'f00n0g08.png': "fa362f0262ce522297ce52bd7d18b1dc28a2eee3e099b4104c2fb6bdc3fbce0b70303e6df31b709b4033b41e8acec29c81d456c67cde741243c9a54f1ba17f17", - 'f00n2c08.png': "040e95e05152c4bb30608556c828f453c46253c7e0ab18984076cde29b5b6afea7b6658cb019a2723af02a5aabadb1af3b1190655256f3477cd54b9ebdd4df91", - 'f01n0g08.png': "5c60761dc803de1509ef33b4e0ee350d15488d73f4b4e2f93311beb19667626f42d91af39873cd078617c5a2e7c46cceb831d53299c3ff2e7486b43a02ce4967", - 'f01n2c08.png': "a2762a0c4905d05469178176966e86ed4f14c3fcf5c88336209cb923d76e73acc94c619947be87b9183afc20bcd002ab2cc84d81897ec8fc0a4bdeb02a2b7863", - 'f02n0g08.png': "adf640a6f60662ad1b47b5666d2ce86489c685284e50d47344af7cda37f4a42697b5e249f5140413bb3c3e563cca09b2693dcea8bd9ad9adad1adf654ce00bb1", - 'f02n2c08.png': "5f70f19d318ff1d9de4c2f728d83952b12b59bfebac213766ef835dd19f8214f56fa92318041eff25e720503542d067bc9fdf91d86016713c68eb1c52870bad4", - 'f03n0g08.png': "742efed3d25ada0449ed60e4fe1dc94abbe494870536946810cc0eef5c3bfbebefd9bb6d1a482cd5e2c2ee0f34a74b2ea796772254d05accb6739c1ab4d19cdd", - 'f03n2c08.png': "adfac7d6dcc7354467cb3f65742e30ecd44a09b5913ffb656664c9c4730e9cfab9c5e0df2ff8995d412b1ea9cd30ec061eead939546bf795e50a2d49cd10bf92", - 'f04n0g08.png': "9f229c609e87e6c3b83fa82f4f59d7b6494b70c7f85cc807b3611a12d5c59953f11c163228f2bb8b2e7ce77ad204e0da099ab06f8fc480d3ab0ca99fef83c23d", - 'f04n2c08.png': "2dd8b926d1f370b3d3ebaa982491fbe446f3c22b89e635c76eeab60c9ccd3ee7cf39773108d2cec647e480d40de88c4173369b4a12a3fccfba9e4040752cfb4b", - 'f99n0g04.png': "b41996ddf1b770d01900c309cfd2f96d6e60df8313cfda3585a731f32166aa447bcf66ef4af907d6417528024961408d06ca0894fbd6a63fd47ee5a9ed541b51", - 'g03n0g16.png': "02a49b9735063a4d1888b550ed961251b150794422b00ca3800ebba09cd55bdcba15cc8dfe0c41e5a99b2f4d3976b32b1cd08aba92f3dfc3960c71e28ec6cb51", - 'g03n2c08.png': "70b3d643218e033959c51279439d80982f338016b4355aff12be51e7bc59a79f5a5d519c0fa5f13db072cc667fb9635654766af2a11ea25c1b6673f554ceb1f4", - 'g03n3p04.png': "7bfab2aef6f2c04063c438a8f5cda38f3221892ec22a83ab3d3441a900b97c0a7184336ffc2e0c6748e4d931fc80855eed5d6b208ba6ac0709ef02bfbab3e8f2", - 'g04n0g16.png': "9a1d60c89bd63e1f2c81adf1ce7025b7f465e00f0c5142d8a67b5a92e2b042d41a352704969f237883e376bf50c911db196d46f8c28c30ef5763e1e717710c8a", - 'g04n2c08.png': "8e482c99cbc90e6e4aa678e5dbdc39f5b432d740ff260ab1ec04ffa199d97ed2e06491cf88eb0bbbda2f4133f174cf3741474305d9645e0268916aaaab43299d", - 'g04n3p04.png': "d95a52bdc83db74dd19aaa56b40bcbcea268492498c7b2348882a4a5ea70c2b258462c3d5cef85e5372188f21001f719afeb2fac635006486c8fc84b96cc4622", - 'g05n0g16.png': "75954c2e19aa2ba43f7011adecded403035ecb21595a5d116896d2bfc1b71eac42c9b6f008a748cd21b70004921832b3e0dafa1fc0fda4c51ad44cd2b4d782c9", - 'g05n2c08.png': "272baa8dc73cbd63f4fb9545288cfdfcee544e266c9074b73b401895188a123960a6000959f9c034f7b5abe2bfa5e6185253447bac82e9fbfa9634d7e9981d95", - 'g05n3p04.png': "81105d25df2bbbdce60e90cb9a7d0784326bc3c2857fdca4f1f3ef7ab301627685057b97b8d0c0c725d92bc0b48aa1b156dadf5e302ca35917f0b407689ba054", - 'g07n0g16.png': "cb6babe4e25f4cc7e0c3602d9f0e9e538f46a4c4e40420c29521799c528da31afd07b614ae64fbc0e9d0904c9f3e7395b31452abdc69c6a03bf1b5817035b669", - 'g07n2c08.png': "c61e8f936afda939d6497687597d3381485b7cc00716101fc4d6f3abf44df63659b762c77ee252a8735f8e0be0809961db17abbd42c5deab5bad10d0b6b28a55", - 'g07n3p04.png': "8151fa9e9ccd1991a05ea129a7363e23de80688c65d0554c7f3142ed7099d01d82d8ef122986693718f221202657ff965b1b6387a237b36ae741e3d4ea693280", - 'g10n0g16.png': "28ad93f3bebed928c3b0bb4beecaa1b1b55e480c7922d2a36c8f92fc0de012adab523c42f22871b82c6683a7ae74864ed1c35a76cfad92dc6692be0b78c22f50", - 'g10n2c08.png': "4b922386b48c0dd51aee3852da4e52c5c4f3eabe3fcb000e4cfdf0c0e8b27d6a6f77fed944932726fe884f468ac24d89c5c46d65db99be1ae797bc4bbee4cd01", - 'g10n3p04.png': "9b106a0db8ea7e4bc3876beed0f13d83b6f3d6d7d7211441379dd8d18db080f5dc81c6d15312cc3c8ae569b991852ad48f167229dec033f1a974d2a95c83cb3c", - 'g25n0g16.png': "0910ee601a4c4cb546c3bf2b5e8a799d199e34e1434c58c75ddad17f2b295920cf3dde9fe05eee19689705f76b11474b4edc73f4ff346b6ab2d02ec40a13d3cb", - 'g25n2c08.png': "080ed57fd4185c00c6c70e48c27612621393a6ef3987aefd1481611f74d0aebc58d1a90386e4e7bb0321eca961e97cd403a7e727f7324196c5a3cf0e07bffce3", - 'g25n3p04.png': "1deb4281d9792af858e715c39ef7e6756f3004845d87f52054efc9f02c679b2b1b6c2a548a5e6a3f31604b115469ba4bb81510470a47ff9a22b133427375a8db", - 'logo-slim.png': "0104624a95b1b8a97bb5013927cb8fbe330a8c9e7197814147702702cd1d44cdde956404786bb0e69927c6b03ed8031bab566599b1291b995ce747f7cb2135eb", - 'oi1n0g16.png': "40a96ed10ad5cc58180701e0bb6d6f36c1752c8b8329a80be2960535ad9e168469efbd8f3dc825b57bf65e78a3b61835f19697f2a22511f79c06ce2c6a0034c0", - 'oi1n2c16.png': "f6b3cfbc70ae502edbeabde828823dc079dc4e9a586a7fc326c70caec3033124121e3ee2e6481e6ce966efcdef9376cb3ec3edb8e677e91563f510bd6f6e83e6", - 'oi2n0g16.png': "aab1bbfc7b711ba66260985bd8bda6ffaa1e09a0a546b2fe8869b508d823a9e5a412ba6337355a81d87f010e9db8c95eee5f5084b21ddd5cca5d7db7725603b0", - 'oi2n2c16.png': "8d1dd257f3b1bf44ef0bf38338a1e3f2ff78670ac779959751a56231109ca1ed82414f50a221aa45333263073e1e1a950ac8df8e5318e2ed695422d49400c540", - 'oi4n0g16.png': "7675dfa4cff547c3029b21cc892b90a8647199dc9290add932535b04b4eaadaedbc338704f98a075f4690b87815eec8cc0f90a3dbaa2a7809aa03f238ebc5815", - 'oi4n2c16.png': "66313c9d4731e2a9fa006ee9e43c6ac4a3034ce2826c6213f67bfa349bb8bb3cfd8acd2c4553fd1fcd4b2f43993a54539ec8b7fb7f07b3a406adfe919f17b065", - 'oi9n0g16.png': "5615cd4c277be96b21031827d08c8fb2971fda090a0031fd34594f77f7cc558bc68917542597a32bc39c0cd29f7759f0f0429203aa285c163bba8c8f78894e72", - 'oi9n2c16.png': "e2184c00b952a3aa53078a5f995715fa683718a9cbe42705fae3a7c77b236ee6ad706f6dbf2c243aeea17e2c53e84724907724634545e065dd16ff400f5677ab", - 'PngSuite.png': "af1d473e986b3f5cb1006340f8f99156e980e2f8c9f804f0e27c57a51d0dec332a81d99c6e50162f63792d89da44857a6072f8431aa735f1e8f86c06520eff4e", - 'pp0n2c16.png': "82ee5674861b5f9bdab6cf41910d980fc33b5188e191b8892b051dc3b3bbceb4c6d421cbed7bc9b71a9a14db43991c0458ed2cbe5166939f1cbb1e20e445e8b1", - 'pp0n6a08.png': "71c0d4c87b6b2644a4529cd240c8d583c25d45e79fd8bb71ca850dada37e46571052b2da28b926a8194169b82da505c9a3e3a701816d91ac968ad6ff41132b70", - 'ps1n0g08.png': "b273e94c2f826640b10b27b50c605be06541b397ea1289be6f4ea821dff6c7b4b209ec8817a315db442de04c975b7cfecba07b51c6cec965ab9062c05ba04edd", - 'ps1n2c16.png': "833993b81801432eb3a9dbca81f4fdd8ab720c76f8d99a2d298fa1dc4ebd19759c158dd10a68f690f999ea643f62fb7e779e916df86aeb05c43bc4db62aa520b", - 'ps2n0g08.png': "4abea03946d751f03e1438e5987ae691607e528c577db52e893051b54a5d147969dd965e44aaf5540fea75baad873327d76aa71a07c81de31f04f3b781d01e3c", - 'ps2n2c16.png': "bfea51d7be49c04293de034c7649472d34becb663169608490916795fe988df7b812ff8950a3f8d896ee27756effcf9fb525d8d455226cf3965595ee85188581", - 's01i3p01.png': "18b405c902977d2553aab1738a3c00e602e40d11c121a7007c9b7b4cc499aba1fab12fc12aecbdbd5b5cd2638ec5ca5157b5d8fce5dad3cdc3bea02b0904c9cf", - 's01n3p01.png': "8e3e1c9754ebde8c687355b80eee4bf0e0ee8e37b39b784f1997cd2eb60ece7f077b40c8027b0d7f4b7e8f242c22173a244f4e599db33985187439bd918d4486", - 's02i3p01.png': "3e26a8cf35e3df1650adc1656f2d3e7c9b5e1c7932826cf792cbe2cc538c1283ff145f66e4682d459bb0b4f2a2f9ce7209f1997e336e06ca49a83f75270ccad1", - 's02n3p01.png': "d34685ade19ee550ba52e81657627832cb9ba3655f6fe336ebb5fdc4d35c380e819efc1556e1bd1ca2b4e456727dbc5312eb513e7254dfd057a25963e57f0ff4", - 's03i3p01.png': "818a2c91d6c0f96b13806d945d1fc1b4c64f17a9b6a2ede2658c84e20269cd80289312a163761165f0d99e1824f283844f1b4696af314c05714fd349acf7609e", - 's03n3p01.png': "fe847a62eb64bf9db4d9751600b19333f057b626f2f2eb80ee78a3374ae562944c2ebc9c2712f3bbf862798ef9205f38297cad44e8df5dcd96c158ca2e9d9233", - 's04i3p01.png': "1c63332a0d2f31b3a6457038ec2b8c5b4bd3192320fb1c44873947375dcaa028ea07151853e53c6f3fff6878cf90adedea35ae819e5a1b2dbf92399b29464d56", - 's04n3p01.png': "1312d9f3e5bb07b6cbf0c6798c3ae9b0a6cebae465ae6d4ba115ef585616b3b1f7723c6481fcfaa00626d6c62e4fbff7023faf1e92eedeb5354b9805542d43b6", - 's05i3p02.png': "13225a16a79579c4040cdc03f11c71d8589ab322a4f3926e32d9499fe4ca8503a43870e532b2da2597a6c3dd89c3904428cf89c25a8637d40bb3a353017cad5e", - 's05n3p02.png': "d07f2ef30c06a29b9c5c6a52ce1485a3bb9dacb5f119c2829c4cfd69c8fa272d6065eab21a5acd016bdb4f4c724214e8a737c2b76e0f0151e6afcbe5586a37ad", - 's06i3p02.png': "b386f0387e8496849b6d325e710440f7937a403b123886a52af8327e69e411fd632db19daaecfe2e712d49279630515d7217533112acde73cb429e2cfa465b63", - 's06n3p02.png': "df0968c77975f350a86fc09c047d9e3a0a2b71890a0ae67bdfedb23e7f0ce303ff883a463668e10df1dcf2e57cea8cf1fc7c836f5d13a73c1ecd8d66284fc0c5", - 's07i3p02.png': "0f67816f188ce11f15d0f345d2695f1920a322d84be3f332d542c5d20d10a9cef2cebf1e8ae778b25677beeda229c06221eb3450b851ea0e8cd102e5cad14e66", - 's07n3p02.png': "0af60c935e2da2a8a3173edf614085ad0df095c882a140437bdf93ecd5494cfeb5c068dc7ea4d49e181c67d26dd9cf8c969bb2d95f2fbce212ddf5817dd7f642", - 's08i3p02.png': "3204dd0879b6b1284f197a0437e674c4280adad180d4a1d37dff1555be1924d6234dad5d02266aa575d782c53fa52fd4580d452482ba939c732c09709075306f", - 's08n3p02.png': "135039621ce2079465ef49c1b7e5a5aeab299993d2bac864d822ff0669843df1e733a729ca5226ec05c94ddfebdcb2f4b78699fb3adb741db993d9757b4ca71c", - 's09i3p02.png': "dc4d8ed06a81fcd604f949f43e7d5f482c3ad9efd113d220b709de38ca0ca1a340f4cce7e929ca6323390b4ea7c4399cf2f1f2e34c1ef1b38c8296b51d7e8f4b", - 's09n3p02.png': "96b8204f7008899993d8d277ff42c8eb7c85e7cd522cae2e9cf28b8e10e3904c7ec2e3227372f397f6c5a381875dd747a42409471a29ad5049240417ec1441f3", - 's32i3p04.png': "a4de495c70f54eeccd115492a0dfe1cbbcf7f308343eacf6a57515d7a248b356a2639f5e88246d630c8713724cfd7ab1d5d388c677f026a1c5c9b4270a837c67", - 's32n3p04.png': "757009a2fdf19d1187d85930eeb4e75c6831387348fd8e618d1ef971c27d2682b2464b4160a743a6c6d712553fd7fe1f0e5d78226e348f270093495297ec83bf", - 's33i3p04.png': "19259eaf76a42fec949a21c4c55715247a027c2b59ba4bd0f45fdea1f3406cb475c33213b662fd64089b697a6cd25700364f76923dc2531fa0b39546242b3356", - 's33n3p04.png': "3f42b5f3896c1a47fe5dff9921d56234b4326dcffb70a7c6b1f0e7193a6f8f898438635f3b1cfef92c59d7ab827e2a3f40671d97c8547c8e7063a0076c97fba9", - 's34i3p04.png': "db0f7fe138fd732eae4a5c1b8d013167eb3be4afa86141b3e6ad096eabec67644e3108189311b3ce2c78f891d1ea7f5dfa1c30be59509edf123696064eecd00c", - 's34n3p04.png': "e35d9338637ae4c7a1c74a0e73a5c4ba405c2b0d36efd92512b7572d418c25e7d06361be2a8d16323c2708ff3921da24b76be5a41bd3cccf09c27ad472078261", - 's35i3p04.png': "9f0602e1160b81c2e88fc30daf295ff29c4f6faa5a3391c69d895621e7ab76e2071ced6f5a798e294f69621bb32272070ff4ceeeb1436b1cf03640417961a9d1", - 's35n3p04.png': "f12e6a7364073b7a010db0cf952989f5f5da20c592b0256efabb5e82e96d928dce7695c2a0af0749ddfa2739e3ca985463eb0f4a7fb6b8ab0638d65079fc6265", - 's36i3p04.png': "a2d0374bba6fb665deda98b1bd0ef767243b7a974b9f00c78d54bdbfed2c00decf6faef95816ef711efdc359247142be08ce2b86f7860b7384948ba394b4dd21", - 's36n3p04.png': "e200a6869dba1fc9e2eeeff6e686e02313c366a990bfb5582f66dba486251392a465db03806092543008e1130624f1d16cde61613d0ae2a9589941f69a1608db", - 's37i3p04.png': "555f1f7ec2d8ddb58ced7162a4dec315780be13b7cb9ed6d8978046c7fbcb91b4ecf6fcc5c99bd5b8b8d348e4cd202d882a31dec1ecd25156af8968c4a06fbff", - 's37n3p04.png': "d28b3f442b481214774d543924ab07b75fee5916262e6404992962184ea0059ca34fb32d14076642ecd83a3063966ea80676e3165759632782ffd844702cb74e", - 's38i3p04.png': "d4fe0179effbf28f7d28c0192de614512870367cec14ca04d86eb9fff9d6864b1074a68144023244e46cb69ad601e81632c36eb2ed307a32a3c37b7683f882d5", - 's38n3p04.png': "19d23ca8b9b44d26bc9173a69d1d8332e4e7b2ce6e6422195e71dfd0701158023253b130a831a035b2fce0a1556004e8de74e893caeea3d12410909e6e649831", - 's39i3p04.png': "f0c783232707d288b086398565918079e510677742bb5dab09b4520687a06809ae328d24508ebd8f5c26cf85105300ba98e55c29e3773579929839ed9ddbf7fa", - 's39n3p04.png': "39763c31a17edf2751442cf45ae3cc315623ed5b04141bbced2ee8d233485b2d1a2cc0678d60a1c84e3d89a7513634bfd9c82b58754cdfe95885f7a54ff18e2c", - 's40i3p04.png': "d33b29e6669accf1325e8212c555a6cb1d8413c445140aecb62eb6b40912743ef7c6298ad4bc2e232bc448ddaba37f79fa4d25e0cc8e3d53a5ebc93f8f984555", - 's40n3p04.png': "463eb60a2f0884fc368ea0650c5b7b6469c1429e5f006a6537e4aac721094bbd57baa6bcfe4025939089e27b344d7077ff09618e9ce3a5bd40ee3fe5a0e33ce0", - 'tbbn0g04.png': "24fb55d9fed351946869552edb15bf20a2e7a050094ca4b51ebd135958f8b77401e0e9b18618de0cbd18f835e16df24dc57ef094d3b456f0eb9f73c31748ce2a", - 'tbbn2c16.png': "ee98ae3a1cb4a851830ee3e8f4112d08bd74c2a05b778ce51102780ba9d7170c5200c9cc4e1a5c0833618ebe137595eb8fc0591b8798bb6e4e94c234f8cbba3b", - 'tbbn3p08.png': "ed325815b8525d3a5fa37c968b7c85946add7c08abd1c66d86e0264951cfa5a377078b910a78cc664ec2c43d352093c2609b8d01c9572f4ba922d98264e55c04", - 'tbgn2c16.png': "e6da4190d4cea04b0002d598d68c0536448899dd8fca4931b44bc8ea6d47e6946c55ebc5d2049936db2f7f52caef124648d25a360e838571f5f7cb2795eb3657", - 'tbgn3p08.png': "865358d285a8d8682a16b050d9c1b7492042e1a217b0b5753ad28ae1a698dd77150f8cd6510f345157e1efa1411b4e4d5938a81024c150c9d5e76e423105d9c8", - 'tbrn2c08.png': "cc3cd3b8b6cc9920914e0184bdedcf9e59975db3f99c39b93ea1202cf6e11689eb966fc40ae2caca8ef224a8aac03723142b58b701ca2a02fd73a62e379f7a6b", - 'tbwn0g16.png': "fa8359bf8cdac3ab9ac54cd4f1e96b1469ff3e3494102be179b5c7394a4e37953ef54f5e1ccffe771b4f028225ec164b410b0d08f82224ed93a654433feb14fe", - 'tbwn3p08.png': "a0e10707fe9df085596a724561ba6c80662f87095ba479b2345c985f4beb2933f7767fe3d07b30002591259dc14012f4b26fa9b503cc75fd644def7c27c9f2e6", - 'tbyn3p08.png': "9920b40b016c4a05a094c28a989d06e108e2ebd5d72c2a6ddca359b68ad95238b0b6dca0f60b78c56b551a2e44f924d5a443071bda6f6e31669b0a78c09b18af", - 'tm3n3p02.png': "e9910a8ccb78a10980c55c6ca7115757b48075e42c14a83aece92a7f85eee538d5e1d9301efc443a867628993ceff1c13aebcc574641ad85eec48ae05df9997c", - 'tp0n0g08.png': "57fbf8a07060565c23918420952d1feb506ce5b1bdb25f22d41ee1de4ce9655c6dde04b9643d2491b59efba229b7e0cacd6799010a89005802c28a300a4e578e", - 'tp0n2c08.png': "eed3f6c18bd81d96870a47c737574746293c71c517b6a5ba88afdad5ef89fa30d975717f8a8f84ec16cb98390353934cbdd6e4b099e86f0ca3fe8d50fa07bb11", - 'tp0n3p08.png': "9650347d0f4a1a6596828f48766949947148952c93c4899541324a48289ea558c5d7bd1a076ac0e32f71d197e478ffc418e424c52b3fcacd2f94eaae98a566c3", - 'tp1n3p08.png': "fbd0f987d98d96f85679ec237c05925032e8d7783551b3f3cffb1b7db6ebdb3afb61214460386b2a3a011826bbda8340757ba22727325eea9fe518c136969f24", - 'xc1n0g08.png': "6aa3e16f11e82378fa7d3767a785840960f2c152338374e3f64f94eb0799e1f29637a1a66435b6dcdbffb9208efbff65a70e17e80f1e05b3038a837dcf79312c", - 'xc9n2c08.png': "7e0d4285bc67345e092656f04b235b4fc273c7892e26dea339dc2bb8df6348d8477f7e6573fe4f40ede1c94b49a60d46ef47ff4f7327fb12cd6de3737e8b5e4c", - 'xcrn0g04.png': "f971b781eb229ada882bb333bbcce956740f9761581d68ba95b49d998b89499bdd4598c4155e72240d6a5b74c04d7081fe284f78fd3f85f2550a3d2171f6efbb", - 'xcsn0g01.png': "9e1d234c3775920546ab9f9eeabdb75457bd823d48ed677fdfe61a95c02c65730182ba7e84209bfbf59de5d1bea533ca57c90ff6a49ea6d5749636f949bdc16a", - 'xd0n2c08.png': "6458d4b8f10a01ff86207496fbf38b25c35857cc537e9e11085fd5fa7befe956e707e6c1c61bdd76a393a816fdc916e2003fcecf5b33dfeb53c4bb071110eaf0", - 'xd3n2c08.png': "fdf261941945d1231ff3b8e91db063ac5a52e8e56f1fc80c5c51149ed255a4eebb4351194d76ae156bdff1eaf5180184c2aa2dd27e4129498279360e00d7db4e", - 'xd9n2c08.png': "b7a3a47c0863bde4ae8aa5ef5fe5d43f8e29abb413dc002553b63d6a4c1fb79eaee66011f1f45887c218aef3fcd63fda847a1b1bc1975252c3b4fcad553c7b70", - 'xdtn0g01.png': "788ed6f9e7ed64e5f4dfa8b62dd88f6f37f7abd1c8e3abf4db2d1c46f0344e7ba638f6721e69ebffa19cf6536f7685f485e9eb548a3cae55131963da05950acd", - 'xhdn0g08.png': "1f06044e4607902e7bd8ba291bf2b9bbb855a881b4a7dde6a21e9f5f9b74f0f43f55a8aa31fa6d1c225d0dc891744943c1544aa17836b3ecf900f041b1cca23c", - 'xlfn0g04.png': "bf3467f8aa9d35f7ae17cd59c3b2d5f6bf110b71afdb3a501ee88306647d669e8419e78b93b995651193a9ff99c849f82530a0bd410cec445feffe36c8df8dd1", - 'xs1n0g01.png': "d1bae1e471886f1661354f23b3564a34c9e1b076bd158ba129388aeb1f287d39ba647269b955bf3cc81f2b962ac6a3eb1a887a8c85243574e84778e48096cf88", - 'xs2n0g01.png': "abd509aa6253d8367980e4f6f4d96bb4b3732287601a7bd3dfb0fa4c70dc3908937c16e6459961421409c74f9952b4693ed1d352dc002bd120a57ba5407cac1f", - 'xs4n0g01.png': "015a1bdc1f878435c3de87feed442e467b3c96f3db3134d1e23653b6759a3a3d68f05325d15992ecce18ec5c00cab7e430c1965ccf36b434d8c9f9a4e8194bfe", - 'xs7n0g01.png': "03dab5037599a58f25dcbd1be556ff60dba08ed3e0e92e5791c53e98041b0ef174a9ebdf75fd997696521ca96a19ca1bb9a5eff929f1a7c0dc1a7d3598d07d04", - 'z00n2c08.png': "9a65f94ebb3614b65e093534a7763f55687144fc9a82e2680e7cad9863d72947a0de3dfe57468e11b578234b8460485e8ee895c681c391d140d84b15c7a40f41", - 'z03n2c08.png': "847fd249d190ccd8ec54afc910afaecf007db7ea5753c18eec8de654b159f4cdf99b70bdd55ec276ce5340fd2ede5290468fe4c6029b5e4c0825ca881a75bde3", - 'z06n2c08.png': "94268c1998de1f4304d24219e31175def7375cc26e2bbfc7d1ac20465a42fae49bcc8ff7626873138b537588e8bce21b6d5e1373efaade1f83cae455334074aa", - 'z09n2c08.png': "3cbb1bb58d78ecc9dd5568a8e9093ba020b63449ef3ab102f98fac4220fc9619feaa873336a25f3c1ad99cfb3e5d32bcfe52d966bc8640d1d5ba4e061741743e", + 'basi0g01.png': "eb26159a783a4dcf27878754e34db6960e992072fc565b12360c894746bc6369101df579e3b9a5c478167f0435e89efb2ab5484056f66c7104f07267f043c82d", + 'basi0g02.png': "f0f74d33ee4b3d3c9f1f4aacf8b0620a33cf1d5c6e9f409e11b4a6e95f2ab1c9159c1bd0bcf6f9553397bf670f4c92712b703d496665d4c6e88fdaf6a0c98620", + 'basi0g04.png': "7b81665f353ff6347304b761d26b4f1baa6118daefc4790f4b0e690150a4e6306514f2aef87e2c39e83e56a0b704df987b10a5366244da58697cda4d5d2745c1", + 'basi0g08.png': "6c809574674494d10eb04c58191c4e0b35813c45962ecafe36a9fe5ba44e5d74ba098e0a524a30c1508eb2dad601f04d5230dceb61a307ef1120f07a8bcbc92f", + 'basi0g16.png': "dd439069fb704b8deff3de822314c012e91a0bd84984d2e79888c6394b78557a0ae6197a7b0a4d64eb4e16fc758946c4f1f13d0d06e512ed9743c4a355b02488", + 'basi2c08.png': "e851fb030f88ead2541a01591882dde5fd9f5dea6bb2b11b374394ed86abc7289e72c4c90808b261aceac13111d3c05b39033168b42ffd1c7ea367314b3a8dc5", + 'basi2c16.png': "e0f81dd459860f7eaf04a2c54932547f41cdc9903a1295a26cc4a2087fd79c98a2e5d5e74f22345dba08b1eb12f3f53c9b904c88b756cbae0c19f9d680e2f1e8", + 'basi3p01.png': "aa21cbcd5d64e6e88056af907509f3b98b00d105198ea2a669fcb25b6966f06d5e4e3afc160b2382935e8ab8e7ccf61733424dfb39b9e864306b82f8355bbb58", + 'basi3p02.png': "d6182370236630d42fd75eed7dc54079ddb7fa9bd601eba22a8dc3331dce31b9a9465027145c29bd56cf778ab0ac029df6aa6481ef65c9464a1ce1d89423faa5", + 'basi3p04.png': "2b35fb98884da370f25833d25f0ab4b188cc26440321e95519131c675818c128b2029d38b3477ef94d88052ec6e9f8b1f7410c5576ce0b47b2fb7a5ba771ce48", + 'basi3p08.png': "7415890831958ca34c73de3e33a9c88980e788d10c983c3a00967886f05053d359499bf0bad386af8ee1bfdb73f666e1d541b0ab682c6092ebdbad3d93e145b1", + 'basi4a08.png': "22ab23a3070d9f1316e8c65b333745e4abf6dcbe63d9d0ada7f6881604b498a92e5b49eb8c1e32f453f4319d94d3d0f48441bac2e79ea528fd7ace0dbbacea9f", + 'basi4a16.png': "2b1436a619f91cf4045e18acf03201b5e9c7774fc77e4c3f38d668984d98543fe64978e80854f30adfecd89b71632945250508bea9eb1ce8e9145e3e55bdeece", + 'basi6a08.png': "aea929866edc5e607d7dc90fcd871f4b6d548abb379b9427fce75a773705e0d8c3ed03bb64e530482b72973eb09844638db5db93666ca1605ff6bbaf194f70e8", + 'basi6a16.png': "29b6ce7c2469e24587b87559f85551334c51bd9d7867f4066c0f7dcb01d710e11e87ee8654c91dc986454cd70f8bd6021e2086ea40e48a28d68a9bdf2283573f", + 'basn0g01.png': "52da610af6f97b03fd8e9f8e52276227f57a97e2bc7e47ae9484b2a37472330f8f5ecef2d6de7fde8f7eb8f144f233bdcf8a3d716c5d193e44d9590af2709cea", + 'basn0g02.png': "676c727faf143af658ed6c6333b15e50387c566c1846a4dda636ed4a2dbb1c0d6d32f0db635479f6851047a32c6d9c52898cfda004ea986853f3ee13750ed90a", + 'basn0g04.png': "123a6652b56f5b858373836509f6b7433221e6ff4eb9ce8a68d0d12bfb27cad0c1bb0cb159523ef1ae5997cb07f994bfbfeabcf0a813aa83495f8269a7a6c4e5", + 'basn0g08.png': "ad5ff30613a462a9b61242d42bb7c854b8247d80a1d03a3919dfdfe8a91c8a962ff51932b2411f714f1be7b803cbd4605c1d4441882cad7e8f44dd8d927198a4", + 'basn0g16.png': "40a96ed10ad5cc58180701e0bb6d6f36c1752c8b8329a80be2960535ad9e168469efbd8f3dc825b57bf65e78a3b61835f19697f2a22511f79c06ce2c6a0034c0", + 'basn2c08.png': "fd92ba13d2038c632fd8bdd3e33971cc69f561bee1eb04ff7920625c6771e0cf7fdda29e48b8a6ca71702c0624ebbca232793f28bb2073b0247d30ce0c549ad5", + 'basn2c16.png': "f6b3cfbc70ae502edbeabde828823dc079dc4e9a586a7fc326c70caec3033124121e3ee2e6481e6ce966efcdef9376cb3ec3edb8e677e91563f510bd6f6e83e6", + 'basn3p01.png': "d262c97549cd1d43e36f5990dac051ead80f1456693c8b1e19a8a48796195831a6ddd5c4a0ac44d22fee51ce4d9392c1c40a0be05571b1b2a961691636d69cd3", + 'basn3p02.png': "a00e375e1fbcc23de38b3b11a0fda4d0204f55473f74e37e0bb5c5f391b37c019293ccddbb99196b9457cd0b7fe5b9e34a55dc6b8167b6909b38813e37a95e46", + 'basn3p04.png': "1a6ab4c567e75b20ee6cdb400dd1f7764a05679c35010e6144fede5a55767dd1861df8e6240e1e59d4166e25d972ff05a41cdfd527818b6ec960a0f9d57d0939", + 'basn3p08.png': "b8f99f6410cc1ac0ceb366e93fc1008f45fb2499e39c489fafa143e8e881f334735e118eec93dde687bf4ea524b4548741c520d770ba6d7071e8fdce576644de", + 'basn4a08.png': "4b7758d4fce3bba95ef3d09cff0ab28c21b8bf568156730fd4e3bcea1c1c2d3ff77f53fe3551f617bd01c8c96e929eb20bf04f5c1a84f37c3cd6bacd4730e6eb", + 'basn4a16.png': "aa0c411f8bb15fc801b072f1048de2fd08761fe245ad267e8503eef8ffe1c39be845e99c04245771afe9b102328129f6375869a4e6cec60eeca641b3e5df3a8b", + 'basn6a08.png': "78d7c811c5463ce53a50874baed840ba70c811513e0ff98fda15a08f01ac6a7c7139a9979cb3688451af6b7e2981699be3c27a386d77af5e60d1acc9e23cb103", + 'basn6a16.png': "e1ad0ed2bf5774b2733d23f2ea9570c6edc933298572933010144ef07bfe6a7878f7367a76f2affbf9845d5b69ea3c84b885f9415e18e752fb1bf21d0dd9d9a2", + 'bgai4a08.png': "22ab23a3070d9f1316e8c65b333745e4abf6dcbe63d9d0ada7f6881604b498a92e5b49eb8c1e32f453f4319d94d3d0f48441bac2e79ea528fd7ace0dbbacea9f", + 'bgai4a16.png': "2b1436a619f91cf4045e18acf03201b5e9c7774fc77e4c3f38d668984d98543fe64978e80854f30adfecd89b71632945250508bea9eb1ce8e9145e3e55bdeece", + 'bgan6a08.png': "78d7c811c5463ce53a50874baed840ba70c811513e0ff98fda15a08f01ac6a7c7139a9979cb3688451af6b7e2981699be3c27a386d77af5e60d1acc9e23cb103", + 'bgan6a16.png': "e1ad0ed2bf5774b2733d23f2ea9570c6edc933298572933010144ef07bfe6a7878f7367a76f2affbf9845d5b69ea3c84b885f9415e18e752fb1bf21d0dd9d9a2", + 'bgbn4a08.png': "4fb9b1023c8c7accb93cf42bd345846c787735df01f7db0df4233737f144e2d5348afaededcdd8c2bcf2e53ce00e2ff095c91de14d7818b4b41c396ab85c9137", + 'bggn4a16.png': "302d7c02ddb0be62aeffeeda46957102c13a2701b841a757486cb3eec7b5456cfabc6ee1954f334ad525badc7c607621a72559b01ce338a0f39e2426d0a03fce", + 'bgwn6a08.png': "d380c477775d102288b43c387dbd7b931f85520f2f6151e3ccc5a25764169312d98fcebf69fb86d3d12520edc3de9bfced559ddf73366f0679e52defb9116424", + 'bgyn6a16.png': "b688d395552eb2a8506fa539ee9159b40a57d02c81860bcf76176213504eb10bdd89de7ce9da452e43f2a46c15290755cdc4816738620b27aed45b3ea7f7ee84", + 'ccwn2c08.png': "c4d58269ef360e42870cce5fb0b1134bfe5706f172c5ff916a338978798fc541a463ab4fdb7c91c424fa385f0ac5d6f576967f178b858d7503f609b2daeacb64", + 'ccwn3p08.png': "d89becf7d24dc57c931a8af1b9dc03ae10302785cc67028cec7255e1aa0ffb0d508d894023a98c10a24cf145aa60b93f977acb8231c8fee9d3f0934484757c30", + 'cdfn2c08.png': "a5faf9cfd284d3750ce2c2f8e15d0ff1bca09da4a9d46b8797c7a1a9f2ebe79d7a7a552bad3c3c049d6186460e81e1e7f9b94e7bb98a3c79d2516f6fb7ac6aa6", + 'cdhn2c08.png': "396eec14e76c00d22ee153171243ac531dcf7adad830948dfd5d6f0a84155734b90407e74f8c1ab8cfb0af5084c03f511530739751f8da070379c826545d0237", + 'cdsn2c08.png': "051b0defa4363ceb3fbc44f460f99a10cdd7c15797ea6d8aa62513b8b6fc5000fe542899db464db30eac56d0c9cd2a5d3778c6962f3aaa85a20881951a7f963a", + 'cdun2c08.png': "946074446fb99f0b790146df0f6e362c68e79b46ec7cbc4ce3d3dc9f75f66e58715caac4b3d7aa8fe8360c32abd2702e7228f9c03b4ba2a0d7f2f970a7ba8f32", + 'ch1n3p04.png': "6680e895195295989850129bcf9d0d0c47efca1c3bc363e4ea822c688707955cdcd07ae92e6e213ee388a62e14b51e036f9a9e075ad7b0f736b0cbc5cc4a108d", + 'ch2n3p08.png': "778ba389d9d0bcbe188e2501c7c1d047f1287179b3760b43da0053f0853e77a0575c7179a02f2442b83efa6cc0951b32d09bd7ea3b6848f261f10b8127e4b0e0", + 'cm0n0g04.png': "9e7e94d201a1222383294bd60e2627207b7b99c0bd80aecd677a1b7d8b372de4981b781fb794811d29a4b2c4db4f5a40c953039c31084f8f3de9aff13fbd1dc9", + 'cm7n0g04.png': "33bb0ffdfe54d655683e4e1bd6d963b9a35a6500129b5dd574245e78971de8d19f27b86a76d42056716b90c2bfc950241bfdc6c4b52ee0e125571de36ba61904", + 'cm9n0g04.png': "fd59386d15a1a324c5ff32f574484c1209afe4ad99f7dc12dce45b2a9bfefd33311ec93211fea3a8e61d1ad2f98220b0c15d151be304bdaa7f88126d54299f68", + 'cs3n2c16.png': "93ca3eeba9aef67de15f943fb2383d208ad1a68518ab78c9fa5ea306ad077b789e873f9e453f0354535167aa4e3d3fec715df05e827bf510948bf0e6fa0dacad", + 'cs3n3p08.png': "e8ddabbf0b17db03bcd7ff0c61d08cc971b3b88e3b3ecd2d4a296da8cf7d7708fa5b42ff3d5813d936ab7f5b4bafbf5842fa4c0fcf0cff15258548d2d26882f3", + 'cs5n2c08.png': "4e3eae53e8c27d4c58d8ad8cdff2c06b00a61d1528a41f5566694f10ef33c4a66f40d3d9fc247731c3ba6dfb734c9f2f90ef031c64486fbdf8a825e354fd67b9", + 'cs5n3p08.png': "31ee0f7f789c1203a9b0e81da34960a47017f3fce37bf2b711738ebd535bb3003ef60c2b0b1fca16587a59955c1ae24b9d186d7ead1d3ece6ada2bbfbc5aee85", + 'cs8n2c08.png': "11bbc80c3168f632deb4453f9199c7994255a0f4f2d7d2d2b06a00931fda4c56985b5a2ca8e969893ff2d65bf330c3d3148b80cbf72aacfe0054b612e6ae05e6", + 'cs8n3p08.png': "49ae3a9050487f6cae826407d421f32a8c67977dd057a6e36f1b2e1ec6abdf5cd46c8929ef4d7b565921ed6c9ae0dde89e243714fdf1510503c7d7b8af6d66e3", + 'ct0n0g04.png': "b6a66fda9ad82cb3287a1ef54702926204bbd4186969cdc2e0741a28dae1869ed76939199f44fd7dd78332753cb8b20a53c4bf740e353f2cc4247870cf576848", + 'ct1n0g04.png': "7cdbaebb6be5d9165972b5fa24f2b64ab437f5337f0548f3d212be8aa95e5dee6ff9abd76ec232faafedc4a41ab8825221b6ced2ecab81c1b3ab20042ceb81a3", + 'cten0g04.png': "648769788c3eb4acd06784dc428bab9e7868328aa3cfe3718ee6656e13e40c4502ca5ac1fe6d4baccb666d4a4f6b3a5d3c97d489f9919ae0d26c2dcdc62e526c", + 'ctfn0g04.png': "c40768807f13dee8dd80ab012bf82b7e8551cb20bde3c7b5c4c45ed19c764cb981945ef036292bd9a2838d34b1b52284295133fea326aea9fc391708bf1d1fbe", + 'ctgn0g04.png': "e0cb7a2d983f1c1c38054b02aabeec657e3f85dba3a52b4878798664c8c6db77cef84d01f042368de6e77cd07b7c2cc6e324aa70bd44c2c8a3642f0cc8fc6a65", + 'cthn0g04.png': "b8614c14bc8c32bb3b29d3b0ac9f3d04c0fcaa40818ab86866e2a8ba1e0c6a87a989f499f208a776d47bff8ab09bfa09352d52089b3cd5bf599faf27627a630b", + 'ctjn0g04.png': "02e2e0a5fa8054e7c7e1c583945da06fb31eff9a3fdd1a7c9f76ba3333f20ce60aec8fafe07f608e9d65a6543e97bab0ff8ae66f3995e8035c05a7c53168ca9a", + 'ctzn0g04.png': "ac35974bd1182e339327245ffa1c0afde634b3a67ee9d6e4e22deee983d6d31054e7843cf1720087b88f6510ed70276e9b0bf43deaf861bd16ceb166140352f9", + 'emblem-1024.png': "5b2e174847274069fc8f4e30c32870ddc7c0e3616a3f04fd41583543aa99ac6d9b7d5a63ed1da672c551b4d193568bb58ddac5e87a101b73367c7c3b01e36fa9", + 'exif2c08.png': "e81b85aa36c9aab55754dd8b73d42497c38eddf9ff3c2981529eb62993d8c0ab33d1b5146a350dd8a1c528d42a967733b2f86248ac615e0254fed53d66d0d895", + 'f00n0g08.png': "fa362f0262ce522297ce52bd7d18b1dc28a2eee3e099b4104c2fb6bdc3fbce0b70303e6df31b709b4033b41e8acec29c81d456c67cde741243c9a54f1ba17f17", + 'f00n2c08.png': "040e95e05152c4bb30608556c828f453c46253c7e0ab18984076cde29b5b6afea7b6658cb019a2723af02a5aabadb1af3b1190655256f3477cd54b9ebdd4df91", + 'f01n0g08.png': "5c60761dc803de1509ef33b4e0ee350d15488d73f4b4e2f93311beb19667626f42d91af39873cd078617c5a2e7c46cceb831d53299c3ff2e7486b43a02ce4967", + 'f01n2c08.png': "a2762a0c4905d05469178176966e86ed4f14c3fcf5c88336209cb923d76e73acc94c619947be87b9183afc20bcd002ab2cc84d81897ec8fc0a4bdeb02a2b7863", + 'f02n0g08.png': "adf640a6f60662ad1b47b5666d2ce86489c685284e50d47344af7cda37f4a42697b5e249f5140413bb3c3e563cca09b2693dcea8bd9ad9adad1adf654ce00bb1", + 'f02n2c08.png': "5f70f19d318ff1d9de4c2f728d83952b12b59bfebac213766ef835dd19f8214f56fa92318041eff25e720503542d067bc9fdf91d86016713c68eb1c52870bad4", + 'f03n0g08.png': "742efed3d25ada0449ed60e4fe1dc94abbe494870536946810cc0eef5c3bfbebefd9bb6d1a482cd5e2c2ee0f34a74b2ea796772254d05accb6739c1ab4d19cdd", + 'f03n2c08.png': "adfac7d6dcc7354467cb3f65742e30ecd44a09b5913ffb656664c9c4730e9cfab9c5e0df2ff8995d412b1ea9cd30ec061eead939546bf795e50a2d49cd10bf92", + 'f04n0g08.png': "9f229c609e87e6c3b83fa82f4f59d7b6494b70c7f85cc807b3611a12d5c59953f11c163228f2bb8b2e7ce77ad204e0da099ab06f8fc480d3ab0ca99fef83c23d", + 'f04n2c08.png': "2dd8b926d1f370b3d3ebaa982491fbe446f3c22b89e635c76eeab60c9ccd3ee7cf39773108d2cec647e480d40de88c4173369b4a12a3fccfba9e4040752cfb4b", + 'f99n0g04.png': "b41996ddf1b770d01900c309cfd2f96d6e60df8313cfda3585a731f32166aa447bcf66ef4af907d6417528024961408d06ca0894fbd6a63fd47ee5a9ed541b51", + 'g03n0g16.png': "02a49b9735063a4d1888b550ed961251b150794422b00ca3800ebba09cd55bdcba15cc8dfe0c41e5a99b2f4d3976b32b1cd08aba92f3dfc3960c71e28ec6cb51", + 'g03n2c08.png': "70b3d643218e033959c51279439d80982f338016b4355aff12be51e7bc59a79f5a5d519c0fa5f13db072cc667fb9635654766af2a11ea25c1b6673f554ceb1f4", + 'g03n3p04.png': "7bfab2aef6f2c04063c438a8f5cda38f3221892ec22a83ab3d3441a900b97c0a7184336ffc2e0c6748e4d931fc80855eed5d6b208ba6ac0709ef02bfbab3e8f2", + 'g04n0g16.png': "9a1d60c89bd63e1f2c81adf1ce7025b7f465e00f0c5142d8a67b5a92e2b042d41a352704969f237883e376bf50c911db196d46f8c28c30ef5763e1e717710c8a", + 'g04n2c08.png': "8e482c99cbc90e6e4aa678e5dbdc39f5b432d740ff260ab1ec04ffa199d97ed2e06491cf88eb0bbbda2f4133f174cf3741474305d9645e0268916aaaab43299d", + 'g04n3p04.png': "d95a52bdc83db74dd19aaa56b40bcbcea268492498c7b2348882a4a5ea70c2b258462c3d5cef85e5372188f21001f719afeb2fac635006486c8fc84b96cc4622", + 'g05n0g16.png': "75954c2e19aa2ba43f7011adecded403035ecb21595a5d116896d2bfc1b71eac42c9b6f008a748cd21b70004921832b3e0dafa1fc0fda4c51ad44cd2b4d782c9", + 'g05n2c08.png': "272baa8dc73cbd63f4fb9545288cfdfcee544e266c9074b73b401895188a123960a6000959f9c034f7b5abe2bfa5e6185253447bac82e9fbfa9634d7e9981d95", + 'g05n3p04.png': "81105d25df2bbbdce60e90cb9a7d0784326bc3c2857fdca4f1f3ef7ab301627685057b97b8d0c0c725d92bc0b48aa1b156dadf5e302ca35917f0b407689ba054", + 'g07n0g16.png': "cb6babe4e25f4cc7e0c3602d9f0e9e538f46a4c4e40420c29521799c528da31afd07b614ae64fbc0e9d0904c9f3e7395b31452abdc69c6a03bf1b5817035b669", + 'g07n2c08.png': "c61e8f936afda939d6497687597d3381485b7cc00716101fc4d6f3abf44df63659b762c77ee252a8735f8e0be0809961db17abbd42c5deab5bad10d0b6b28a55", + 'g07n3p04.png': "8151fa9e9ccd1991a05ea129a7363e23de80688c65d0554c7f3142ed7099d01d82d8ef122986693718f221202657ff965b1b6387a237b36ae741e3d4ea693280", + 'g10n0g16.png': "28ad93f3bebed928c3b0bb4beecaa1b1b55e480c7922d2a36c8f92fc0de012adab523c42f22871b82c6683a7ae74864ed1c35a76cfad92dc6692be0b78c22f50", + 'g10n2c08.png': "4b922386b48c0dd51aee3852da4e52c5c4f3eabe3fcb000e4cfdf0c0e8b27d6a6f77fed944932726fe884f468ac24d89c5c46d65db99be1ae797bc4bbee4cd01", + 'g10n3p04.png': "9b106a0db8ea7e4bc3876beed0f13d83b6f3d6d7d7211441379dd8d18db080f5dc81c6d15312cc3c8ae569b991852ad48f167229dec033f1a974d2a95c83cb3c", + 'g25n0g16.png': "0910ee601a4c4cb546c3bf2b5e8a799d199e34e1434c58c75ddad17f2b295920cf3dde9fe05eee19689705f76b11474b4edc73f4ff346b6ab2d02ec40a13d3cb", + 'g25n2c08.png': "080ed57fd4185c00c6c70e48c27612621393a6ef3987aefd1481611f74d0aebc58d1a90386e4e7bb0321eca961e97cd403a7e727f7324196c5a3cf0e07bffce3", + 'g25n3p04.png': "1deb4281d9792af858e715c39ef7e6756f3004845d87f52054efc9f02c679b2b1b6c2a548a5e6a3f31604b115469ba4bb81510470a47ff9a22b133427375a8db", + 'logo-slim.png': "0104624a95b1b8a97bb5013927cb8fbe330a8c9e7197814147702702cd1d44cdde956404786bb0e69927c6b03ed8031bab566599b1291b995ce747f7cb2135eb", + 'oi1n0g16.png': "40a96ed10ad5cc58180701e0bb6d6f36c1752c8b8329a80be2960535ad9e168469efbd8f3dc825b57bf65e78a3b61835f19697f2a22511f79c06ce2c6a0034c0", + 'oi1n2c16.png': "f6b3cfbc70ae502edbeabde828823dc079dc4e9a586a7fc326c70caec3033124121e3ee2e6481e6ce966efcdef9376cb3ec3edb8e677e91563f510bd6f6e83e6", + 'oi2n0g16.png': "aab1bbfc7b711ba66260985bd8bda6ffaa1e09a0a546b2fe8869b508d823a9e5a412ba6337355a81d87f010e9db8c95eee5f5084b21ddd5cca5d7db7725603b0", + 'oi2n2c16.png': "8d1dd257f3b1bf44ef0bf38338a1e3f2ff78670ac779959751a56231109ca1ed82414f50a221aa45333263073e1e1a950ac8df8e5318e2ed695422d49400c540", + 'oi4n0g16.png': "7675dfa4cff547c3029b21cc892b90a8647199dc9290add932535b04b4eaadaedbc338704f98a075f4690b87815eec8cc0f90a3dbaa2a7809aa03f238ebc5815", + 'oi4n2c16.png': "66313c9d4731e2a9fa006ee9e43c6ac4a3034ce2826c6213f67bfa349bb8bb3cfd8acd2c4553fd1fcd4b2f43993a54539ec8b7fb7f07b3a406adfe919f17b065", + 'oi9n0g16.png': "5615cd4c277be96b21031827d08c8fb2971fda090a0031fd34594f77f7cc558bc68917542597a32bc39c0cd29f7759f0f0429203aa285c163bba8c8f78894e72", + 'oi9n2c16.png': "e2184c00b952a3aa53078a5f995715fa683718a9cbe42705fae3a7c77b236ee6ad706f6dbf2c243aeea17e2c53e84724907724634545e065dd16ff400f5677ab", + 'PngSuite.png': "af1d473e986b3f5cb1006340f8f99156e980e2f8c9f804f0e27c57a51d0dec332a81d99c6e50162f63792d89da44857a6072f8431aa735f1e8f86c06520eff4e", + 'pp0n2c16.png': "82ee5674861b5f9bdab6cf41910d980fc33b5188e191b8892b051dc3b3bbceb4c6d421cbed7bc9b71a9a14db43991c0458ed2cbe5166939f1cbb1e20e445e8b1", + 'pp0n6a08.png': "71c0d4c87b6b2644a4529cd240c8d583c25d45e79fd8bb71ca850dada37e46571052b2da28b926a8194169b82da505c9a3e3a701816d91ac968ad6ff41132b70", + 'ps1n0g08.png': "b273e94c2f826640b10b27b50c605be06541b397ea1289be6f4ea821dff6c7b4b209ec8817a315db442de04c975b7cfecba07b51c6cec965ab9062c05ba04edd", + 'ps1n2c16.png': "833993b81801432eb3a9dbca81f4fdd8ab720c76f8d99a2d298fa1dc4ebd19759c158dd10a68f690f999ea643f62fb7e779e916df86aeb05c43bc4db62aa520b", + 'ps2n0g08.png': "4abea03946d751f03e1438e5987ae691607e528c577db52e893051b54a5d147969dd965e44aaf5540fea75baad873327d76aa71a07c81de31f04f3b781d01e3c", + 'ps2n2c16.png': "bfea51d7be49c04293de034c7649472d34becb663169608490916795fe988df7b812ff8950a3f8d896ee27756effcf9fb525d8d455226cf3965595ee85188581", + 's01i3p01.png': "18b405c902977d2553aab1738a3c00e602e40d11c121a7007c9b7b4cc499aba1fab12fc12aecbdbd5b5cd2638ec5ca5157b5d8fce5dad3cdc3bea02b0904c9cf", + 's01n3p01.png': "8e3e1c9754ebde8c687355b80eee4bf0e0ee8e37b39b784f1997cd2eb60ece7f077b40c8027b0d7f4b7e8f242c22173a244f4e599db33985187439bd918d4486", + 's02i3p01.png': "3e26a8cf35e3df1650adc1656f2d3e7c9b5e1c7932826cf792cbe2cc538c1283ff145f66e4682d459bb0b4f2a2f9ce7209f1997e336e06ca49a83f75270ccad1", + 's02n3p01.png': "d34685ade19ee550ba52e81657627832cb9ba3655f6fe336ebb5fdc4d35c380e819efc1556e1bd1ca2b4e456727dbc5312eb513e7254dfd057a25963e57f0ff4", + 's03i3p01.png': "818a2c91d6c0f96b13806d945d1fc1b4c64f17a9b6a2ede2658c84e20269cd80289312a163761165f0d99e1824f283844f1b4696af314c05714fd349acf7609e", + 's03n3p01.png': "fe847a62eb64bf9db4d9751600b19333f057b626f2f2eb80ee78a3374ae562944c2ebc9c2712f3bbf862798ef9205f38297cad44e8df5dcd96c158ca2e9d9233", + 's04i3p01.png': "1c63332a0d2f31b3a6457038ec2b8c5b4bd3192320fb1c44873947375dcaa028ea07151853e53c6f3fff6878cf90adedea35ae819e5a1b2dbf92399b29464d56", + 's04n3p01.png': "1312d9f3e5bb07b6cbf0c6798c3ae9b0a6cebae465ae6d4ba115ef585616b3b1f7723c6481fcfaa00626d6c62e4fbff7023faf1e92eedeb5354b9805542d43b6", + 's05i3p02.png': "13225a16a79579c4040cdc03f11c71d8589ab322a4f3926e32d9499fe4ca8503a43870e532b2da2597a6c3dd89c3904428cf89c25a8637d40bb3a353017cad5e", + 's05n3p02.png': "d07f2ef30c06a29b9c5c6a52ce1485a3bb9dacb5f119c2829c4cfd69c8fa272d6065eab21a5acd016bdb4f4c724214e8a737c2b76e0f0151e6afcbe5586a37ad", + 's06i3p02.png': "b386f0387e8496849b6d325e710440f7937a403b123886a52af8327e69e411fd632db19daaecfe2e712d49279630515d7217533112acde73cb429e2cfa465b63", + 's06n3p02.png': "df0968c77975f350a86fc09c047d9e3a0a2b71890a0ae67bdfedb23e7f0ce303ff883a463668e10df1dcf2e57cea8cf1fc7c836f5d13a73c1ecd8d66284fc0c5", + 's07i3p02.png': "0f67816f188ce11f15d0f345d2695f1920a322d84be3f332d542c5d20d10a9cef2cebf1e8ae778b25677beeda229c06221eb3450b851ea0e8cd102e5cad14e66", + 's07n3p02.png': "0af60c935e2da2a8a3173edf614085ad0df095c882a140437bdf93ecd5494cfeb5c068dc7ea4d49e181c67d26dd9cf8c969bb2d95f2fbce212ddf5817dd7f642", + 's08i3p02.png': "3204dd0879b6b1284f197a0437e674c4280adad180d4a1d37dff1555be1924d6234dad5d02266aa575d782c53fa52fd4580d452482ba939c732c09709075306f", + 's08n3p02.png': "135039621ce2079465ef49c1b7e5a5aeab299993d2bac864d822ff0669843df1e733a729ca5226ec05c94ddfebdcb2f4b78699fb3adb741db993d9757b4ca71c", + 's09i3p02.png': "dc4d8ed06a81fcd604f949f43e7d5f482c3ad9efd113d220b709de38ca0ca1a340f4cce7e929ca6323390b4ea7c4399cf2f1f2e34c1ef1b38c8296b51d7e8f4b", + 's09n3p02.png': "96b8204f7008899993d8d277ff42c8eb7c85e7cd522cae2e9cf28b8e10e3904c7ec2e3227372f397f6c5a381875dd747a42409471a29ad5049240417ec1441f3", + 's32i3p04.png': "a4de495c70f54eeccd115492a0dfe1cbbcf7f308343eacf6a57515d7a248b356a2639f5e88246d630c8713724cfd7ab1d5d388c677f026a1c5c9b4270a837c67", + 's32n3p04.png': "757009a2fdf19d1187d85930eeb4e75c6831387348fd8e618d1ef971c27d2682b2464b4160a743a6c6d712553fd7fe1f0e5d78226e348f270093495297ec83bf", + 's33i3p04.png': "19259eaf76a42fec949a21c4c55715247a027c2b59ba4bd0f45fdea1f3406cb475c33213b662fd64089b697a6cd25700364f76923dc2531fa0b39546242b3356", + 's33n3p04.png': "3f42b5f3896c1a47fe5dff9921d56234b4326dcffb70a7c6b1f0e7193a6f8f898438635f3b1cfef92c59d7ab827e2a3f40671d97c8547c8e7063a0076c97fba9", + 's34i3p04.png': "db0f7fe138fd732eae4a5c1b8d013167eb3be4afa86141b3e6ad096eabec67644e3108189311b3ce2c78f891d1ea7f5dfa1c30be59509edf123696064eecd00c", + 's34n3p04.png': "e35d9338637ae4c7a1c74a0e73a5c4ba405c2b0d36efd92512b7572d418c25e7d06361be2a8d16323c2708ff3921da24b76be5a41bd3cccf09c27ad472078261", + 's35i3p04.png': "9f0602e1160b81c2e88fc30daf295ff29c4f6faa5a3391c69d895621e7ab76e2071ced6f5a798e294f69621bb32272070ff4ceeeb1436b1cf03640417961a9d1", + 's35n3p04.png': "f12e6a7364073b7a010db0cf952989f5f5da20c592b0256efabb5e82e96d928dce7695c2a0af0749ddfa2739e3ca985463eb0f4a7fb6b8ab0638d65079fc6265", + 's36i3p04.png': "a2d0374bba6fb665deda98b1bd0ef767243b7a974b9f00c78d54bdbfed2c00decf6faef95816ef711efdc359247142be08ce2b86f7860b7384948ba394b4dd21", + 's36n3p04.png': "e200a6869dba1fc9e2eeeff6e686e02313c366a990bfb5582f66dba486251392a465db03806092543008e1130624f1d16cde61613d0ae2a9589941f69a1608db", + 's37i3p04.png': "555f1f7ec2d8ddb58ced7162a4dec315780be13b7cb9ed6d8978046c7fbcb91b4ecf6fcc5c99bd5b8b8d348e4cd202d882a31dec1ecd25156af8968c4a06fbff", + 's37n3p04.png': "d28b3f442b481214774d543924ab07b75fee5916262e6404992962184ea0059ca34fb32d14076642ecd83a3063966ea80676e3165759632782ffd844702cb74e", + 's38i3p04.png': "d4fe0179effbf28f7d28c0192de614512870367cec14ca04d86eb9fff9d6864b1074a68144023244e46cb69ad601e81632c36eb2ed307a32a3c37b7683f882d5", + 's38n3p04.png': "19d23ca8b9b44d26bc9173a69d1d8332e4e7b2ce6e6422195e71dfd0701158023253b130a831a035b2fce0a1556004e8de74e893caeea3d12410909e6e649831", + 's39i3p04.png': "f0c783232707d288b086398565918079e510677742bb5dab09b4520687a06809ae328d24508ebd8f5c26cf85105300ba98e55c29e3773579929839ed9ddbf7fa", + 's39n3p04.png': "39763c31a17edf2751442cf45ae3cc315623ed5b04141bbced2ee8d233485b2d1a2cc0678d60a1c84e3d89a7513634bfd9c82b58754cdfe95885f7a54ff18e2c", + 's40i3p04.png': "d33b29e6669accf1325e8212c555a6cb1d8413c445140aecb62eb6b40912743ef7c6298ad4bc2e232bc448ddaba37f79fa4d25e0cc8e3d53a5ebc93f8f984555", + 's40n3p04.png': "463eb60a2f0884fc368ea0650c5b7b6469c1429e5f006a6537e4aac721094bbd57baa6bcfe4025939089e27b344d7077ff09618e9ce3a5bd40ee3fe5a0e33ce0", + 'tbbn0g04.png': "24fb55d9fed351946869552edb15bf20a2e7a050094ca4b51ebd135958f8b77401e0e9b18618de0cbd18f835e16df24dc57ef094d3b456f0eb9f73c31748ce2a", + 'tbbn2c16.png': "ee98ae3a1cb4a851830ee3e8f4112d08bd74c2a05b778ce51102780ba9d7170c5200c9cc4e1a5c0833618ebe137595eb8fc0591b8798bb6e4e94c234f8cbba3b", + 'tbbn3p08.png': "ed325815b8525d3a5fa37c968b7c85946add7c08abd1c66d86e0264951cfa5a377078b910a78cc664ec2c43d352093c2609b8d01c9572f4ba922d98264e55c04", + 'tbgn2c16.png': "e6da4190d4cea04b0002d598d68c0536448899dd8fca4931b44bc8ea6d47e6946c55ebc5d2049936db2f7f52caef124648d25a360e838571f5f7cb2795eb3657", + 'tbgn3p08.png': "865358d285a8d8682a16b050d9c1b7492042e1a217b0b5753ad28ae1a698dd77150f8cd6510f345157e1efa1411b4e4d5938a81024c150c9d5e76e423105d9c8", + 'tbrn2c08.png': "cc3cd3b8b6cc9920914e0184bdedcf9e59975db3f99c39b93ea1202cf6e11689eb966fc40ae2caca8ef224a8aac03723142b58b701ca2a02fd73a62e379f7a6b", + 'tbwn0g16.png': "fa8359bf8cdac3ab9ac54cd4f1e96b1469ff3e3494102be179b5c7394a4e37953ef54f5e1ccffe771b4f028225ec164b410b0d08f82224ed93a654433feb14fe", + 'tbwn3p08.png': "a0e10707fe9df085596a724561ba6c80662f87095ba479b2345c985f4beb2933f7767fe3d07b30002591259dc14012f4b26fa9b503cc75fd644def7c27c9f2e6", + 'tbyn3p08.png': "9920b40b016c4a05a094c28a989d06e108e2ebd5d72c2a6ddca359b68ad95238b0b6dca0f60b78c56b551a2e44f924d5a443071bda6f6e31669b0a78c09b18af", + 'tm3n3p02.png': "e9910a8ccb78a10980c55c6ca7115757b48075e42c14a83aece92a7f85eee538d5e1d9301efc443a867628993ceff1c13aebcc574641ad85eec48ae05df9997c", + 'tp0n0g08.png': "57fbf8a07060565c23918420952d1feb506ce5b1bdb25f22d41ee1de4ce9655c6dde04b9643d2491b59efba229b7e0cacd6799010a89005802c28a300a4e578e", + 'tp0n2c08.png': "eed3f6c18bd81d96870a47c737574746293c71c517b6a5ba88afdad5ef89fa30d975717f8a8f84ec16cb98390353934cbdd6e4b099e86f0ca3fe8d50fa07bb11", + 'tp0n3p08.png': "9650347d0f4a1a6596828f48766949947148952c93c4899541324a48289ea558c5d7bd1a076ac0e32f71d197e478ffc418e424c52b3fcacd2f94eaae98a566c3", + 'tp1n3p08.png': "fbd0f987d98d96f85679ec237c05925032e8d7783551b3f3cffb1b7db6ebdb3afb61214460386b2a3a011826bbda8340757ba22727325eea9fe518c136969f24", + 'xc1n0g08.png': "6aa3e16f11e82378fa7d3767a785840960f2c152338374e3f64f94eb0799e1f29637a1a66435b6dcdbffb9208efbff65a70e17e80f1e05b3038a837dcf79312c", + 'xc9n2c08.png': "7e0d4285bc67345e092656f04b235b4fc273c7892e26dea339dc2bb8df6348d8477f7e6573fe4f40ede1c94b49a60d46ef47ff4f7327fb12cd6de3737e8b5e4c", + 'xcrn0g04.png': "f971b781eb229ada882bb333bbcce956740f9761581d68ba95b49d998b89499bdd4598c4155e72240d6a5b74c04d7081fe284f78fd3f85f2550a3d2171f6efbb", + 'xcsn0g01.png': "9e1d234c3775920546ab9f9eeabdb75457bd823d48ed677fdfe61a95c02c65730182ba7e84209bfbf59de5d1bea533ca57c90ff6a49ea6d5749636f949bdc16a", + 'xd0n2c08.png': "6458d4b8f10a01ff86207496fbf38b25c35857cc537e9e11085fd5fa7befe956e707e6c1c61bdd76a393a816fdc916e2003fcecf5b33dfeb53c4bb071110eaf0", + 'xd3n2c08.png': "fdf261941945d1231ff3b8e91db063ac5a52e8e56f1fc80c5c51149ed255a4eebb4351194d76ae156bdff1eaf5180184c2aa2dd27e4129498279360e00d7db4e", + 'xd9n2c08.png': "b7a3a47c0863bde4ae8aa5ef5fe5d43f8e29abb413dc002553b63d6a4c1fb79eaee66011f1f45887c218aef3fcd63fda847a1b1bc1975252c3b4fcad553c7b70", + 'xdtn0g01.png': "788ed6f9e7ed64e5f4dfa8b62dd88f6f37f7abd1c8e3abf4db2d1c46f0344e7ba638f6721e69ebffa19cf6536f7685f485e9eb548a3cae55131963da05950acd", + 'xhdn0g08.png': "1f06044e4607902e7bd8ba291bf2b9bbb855a881b4a7dde6a21e9f5f9b74f0f43f55a8aa31fa6d1c225d0dc891744943c1544aa17836b3ecf900f041b1cca23c", + 'xlfn0g04.png': "bf3467f8aa9d35f7ae17cd59c3b2d5f6bf110b71afdb3a501ee88306647d669e8419e78b93b995651193a9ff99c849f82530a0bd410cec445feffe36c8df8dd1", + 'xs1n0g01.png': "d1bae1e471886f1661354f23b3564a34c9e1b076bd158ba129388aeb1f287d39ba647269b955bf3cc81f2b962ac6a3eb1a887a8c85243574e84778e48096cf88", + 'xs2n0g01.png': "abd509aa6253d8367980e4f6f4d96bb4b3732287601a7bd3dfb0fa4c70dc3908937c16e6459961421409c74f9952b4693ed1d352dc002bd120a57ba5407cac1f", + 'xs4n0g01.png': "015a1bdc1f878435c3de87feed442e467b3c96f3db3134d1e23653b6759a3a3d68f05325d15992ecce18ec5c00cab7e430c1965ccf36b434d8c9f9a4e8194bfe", + 'xs7n0g01.png': "03dab5037599a58f25dcbd1be556ff60dba08ed3e0e92e5791c53e98041b0ef174a9ebdf75fd997696521ca96a19ca1bb9a5eff929f1a7c0dc1a7d3598d07d04", + 'z00n2c08.png': "9a65f94ebb3614b65e093534a7763f55687144fc9a82e2680e7cad9863d72947a0de3dfe57468e11b578234b8460485e8ee895c681c391d140d84b15c7a40f41", + 'z03n2c08.png': "847fd249d190ccd8ec54afc910afaecf007db7ea5753c18eec8de654b159f4cdf99b70bdd55ec276ce5340fd2ede5290468fe4c6029b5e4c0825ca881a75bde3", + '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", + '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", - 'emblem-1024.jpg': "d7b7e3ffaa5cda04c667e3742752091d78e02aa2d3c7a63406af679ce810a0a86666b10fcab12cc7ead2fadf2f6c2e1237bc94f892a62a4c218e18a20f96dbe4", + 'emblem-1024.jpg': "d7b7e3ffaa5cda04c667e3742752091d78e02aa2d3c7a63406af679ce810a0a86666b10fcab12cc7ead2fadf2f6c2e1237bc94f892a62a4c218e18a20f96dbe4", + 'emblem-1024-progressive.jpg': "7a6f4b112bd7189320c58dcddb9129968bcf268798c1e0c4f2243c10b3e3d9a6962c9f142d9fd65f8fb31e9a1e899008cae22b3ffde713250d315499b412e160", - 'unicode.xml': "e0cdc94f07fdbb15eea811ed2ae6dcf494a83d197dafe6580c740270feb0d8f5f7146d4a7d4c2d2ea25f8bd9678bc986123484b39399819a6b7262687959d1ae", + 'unicode.xml': "e0cdc94f07fdbb15eea811ed2ae6dcf494a83d197dafe6580c740270feb0d8f5f7146d4a7d4c2d2ea25f8bd9678bc986123484b39399819a6b7262687959d1ae", } def try_download_file(url, out_file): diff --git a/tests/core/image/test_core_image.odin b/tests/core/image/test_core_image.odin index ca737973d..29b654f84 100644 --- a/tests/core/image/test_core_image.odin +++ b/tests/core/image/test_core_image.odin @@ -5,7 +5,7 @@ List of contributors: Jeroen van Rijn: Initial implementation. - A test suite for PNG, TGA, NetPBM, QOI and BMP. + A test suite for PNG, TGA, NetPBM, QOI, BMP, and JPEG. */ #+feature dynamic-literals package test_core_image @@ -2366,7 +2366,13 @@ run_bmp_suite :: proc(t: ^testing.T, suite: []Test) { Basic_JPG_Tests := []Test{ { "emblem-1024", { - {Default, nil, {1024, 1024, 3, 8}, 0x_46a29e0f}, + {Default, nil, {1024, 1024, 3, 8}, 0x_46a29e0f}, + // {Alpha_Add, nil, {1024, 1024, 4, 8}, 0x_46a29e0f}, + }, + }, + { + "emblem-1024-progressive", { + {Default, .Unsupported_Frame_Type, {1024, 1024, 3, 8}, 0x_46a29e0f}, }, }, } From 64e96527765281c80b0e93b052148f62eaa01bda Mon Sep 17 00:00:00 2001 From: FourteenBrush <74827262+FourteenBrush@users.noreply.github.com> Date: Tue, 9 Sep 2025 14:04:36 +0200 Subject: [PATCH 021/162] Add `RtlNtStatusToDosError` Maps kernel NTSTATUS to win32 system error --- core/sys/windows/kernel32.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index 99064df60..ff27cf795 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -555,6 +555,7 @@ foreign kernel32 { GetHandleInformation :: proc(hObject: HANDLE, lpdwFlags: ^DWORD) -> BOOL --- RtlCaptureStackBackTrace :: proc(FramesToSkip: ULONG, FramesToCapture: ULONG, BackTrace: [^]PVOID, BackTraceHash: PULONG) -> USHORT --- + RtlNtStatusToDosError :: proc(status: NTSTATUS) -> ULONG --- GetSystemPowerStatus :: proc(lpSystemPowerStatus: ^SYSTEM_POWER_STATUS) -> BOOL --- } From fb1dd3052d92a8e8b8de6aca9c74c482cfac30bf Mon Sep 17 00:00:00 2001 From: FourteenBrush <74827262+FourteenBrush@users.noreply.github.com> Date: Tue, 9 Sep 2025 14:11:38 +0200 Subject: [PATCH 022/162] Add `RtlNtStatusToDosError` `ERROR_MR_MID_NOT_FOUND` error --- core/sys/windows/winerror.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/sys/windows/winerror.odin b/core/sys/windows/winerror.odin index fee04fdc4..23467761d 100644 --- a/core/sys/windows/winerror.odin +++ b/core/sys/windows/winerror.odin @@ -222,6 +222,7 @@ ERROR_LOCK_FAILED : DWORD : 167 ERROR_ALREADY_EXISTS : DWORD : 183 ERROR_NO_DATA : DWORD : 232 ERROR_ENVVAR_NOT_FOUND : DWORD : 203 +ERROR_MR_MID_NOT_FOUND : DWORD : 317 ERROR_OPERATION_ABORTED : DWORD : 995 ERROR_IO_PENDING : DWORD : 997 ERROR_NO_UNICODE_TRANSLATION : DWORD : 1113 From 737c87a726b857934672684ffa2ee0b713e44d52 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 9 Sep 2025 14:12:48 +0200 Subject: [PATCH 023/162] Optionally save BMP --- tests/core/image/test_core_image.odin | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/core/image/test_core_image.odin b/tests/core/image/test_core_image.odin index 29b654f84..82ff2704a 100644 --- a/tests/core/image/test_core_image.odin +++ b/tests/core/image/test_core_image.odin @@ -2389,6 +2389,7 @@ run_jpg_suite :: proc(t: ^testing.T, suite: []Test) { for test in file.tests { img, err := jpeg.load(test_file, test.options) + defer jpeg.destroy(img) 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) @@ -2402,13 +2403,13 @@ run_jpg_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 to check load - test_bmp := strings.concatenate({TEST_SUITE_PATH_JPG, "/", file.file, ".bmp"}, context.temp_allocator) - - save_err := bmp.save(test_bmp, img) - testing.expectf(t, save_err == nil, "expected saving to BMP in memory not to raise error, got %v", save_err) + // Optionally save to BMP file to check file loaded properly during development + when false { + test_bmp := strings.concatenate({TEST_SUITE_PATH_JPG, "/", file.file, ".bmp"}, context.temp_allocator) + save_err := bmp.save(test_bmp, img) + testing.expectf(t, save_err == nil, "expected saving to BMP not to raise error, got %v", save_err) + } } - bmp.destroy(img) } } return From 7b3ca701e0266cf2ffd4a8ee4b332ae569740fd1 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Tue, 9 Sep 2025 14:51:16 +0200 Subject: [PATCH 024/162] Implement .alpha_add_if_missing for JPEG --- core/image/jpeg/jpeg.odin | 98 ++++++++++++++++++++++----- tests/core/image/test_core_image.odin | 26 +++---- 2 files changed, 95 insertions(+), 29 deletions(-) diff --git a/core/image/jpeg/jpeg.odin b/core/image/jpeg/jpeg.odin index 58c6dff50..391b4316c 100644 --- a/core/image/jpeg/jpeg.odin +++ b/core/image/jpeg/jpeg.odin @@ -927,9 +927,9 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a cbcr_pixel_column := k / luma_h_sampling_factor + 4 * h cbcr_pixel := cbcr_pixel_row * BLOCK_SIZE + cbcr_pixel_column - r := cast(i16)math.clamp(cast(f32)y_blk[.Y][i] + 1.402 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255) - g := cast(i16)math.clamp(cast(f32)y_blk[.Y][i] - 0.344 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] - 0.714 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255) - b := cast(i16)math.clamp(cast(f32)y_blk[.Y][i] + 1.772 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] + 128, 0, 255) + r := cast(i16)clamp(cast(f32)y_blk[.Y][i] + 1.402 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255) + g := cast(i16)clamp(cast(f32)y_blk[.Y][i] - 0.344 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] - 0.714 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255) + b := cast(i16)clamp(cast(f32)y_blk[.Y][i] + 1.772 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] + 128, 0, 255) y_blk[.Y][i] = r y_blk[.Cb][i] = g @@ -941,28 +941,94 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } } + if .alpha_add_if_missing in options { + img.channels += 1 + } + if resize(&img.pixels.buf, img.width * img.height * img.channels) != nil { return img, .Unable_To_Allocate_Or_Resize } - out := mem.slice_data_cast([]image.RGB_Pixel, img.pixels.buf[:]) - for y in 0.. Date: Tue, 9 Sep 2025 17:13:21 +0200 Subject: [PATCH 025/162] Expand grayscale JPEGs to RGB(A) And handle grayscale jpeg example file in test suite. --- core/image/jpeg/jpeg.odin | 34 +++++++++++++++++++-------- tests/core/.gitignore | 2 ++ tests/core/download_assets.py | 1 + tests/core/image/test_core_image.odin | 8 +++++++ 4 files changed, 35 insertions(+), 10 deletions(-) diff --git a/core/image/jpeg/jpeg.odin b/core/image/jpeg/jpeg.odin index 391b4316c..a2ad7e61e 100644 --- a/core/image/jpeg/jpeg.odin +++ b/core/image/jpeg/jpeg.odin @@ -190,6 +190,10 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a options -= {.return_header} } + if .do_not_expand_channels in options || .do_not_expand_grayscale in options { + return img, .Unsupported_Option + } + first := compress.read_u8(ctx) or_return soi := cast(image.JPEG_Marker)compress.read_u8(ctx) or_return if first != 0xFF && soi != .SOI { @@ -941,16 +945,25 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } } + orig_channels := img.channels + + // We automatically expand grayscale images to RGB + if img.channels == 1 { + img.channels += 2 + } + if .alpha_add_if_missing in options { - img.channels += 1 + img.channels += 1 + orig_channels += 1 } if resize(&img.pixels.buf, img.width * img.height * img.channels) != nil { return img, .Unable_To_Allocate_Or_Resize } - switch img.channels { - case 1: + switch orig_channels { + case 1: // Grayscale JPEG expanded to RGB + out := mem.slice_data_cast([]image.RGB_Pixel, img.pixels.buf[:]) out_idx := 0 for y in 0.. Date: Tue, 9 Sep 2025 18:34:19 +0200 Subject: [PATCH 026/162] Small updates to JPEG loader - Remove some unnecessary nesting - Add frame type (SOF0, et al) to metadata if `.return_metadata` is used --- core/image/common.odin | 28 +- core/image/jpeg/jpeg.odin | 1508 +++++++++++++++++++------------------ 2 files changed, 774 insertions(+), 762 deletions(-) diff --git a/core/image/common.odin b/core/image/common.odin index 0e5668e50..4014e2ae6 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -642,22 +642,23 @@ JFXX_Extension_Code :: enum u8 { } JPEG_Marker :: enum u8 { - SOF0 = 0xC0, - SOF1 = 0xC1, - SOF2 = 0xC2, - SOF3 = 0xC3, + SOF0 = 0xC0, // Baseline sequential DCT + SOF1 = 0xC1, // Extended sequential DCT + SOF2 = 0xC2, // Progressive DCT + SOF3 = 0xC3, // Lossless (sequential) + SOF5 = 0xC5, // Differential sequential DCT + SOF6 = 0xC6, // Differential progressive DCT + SOF7 = 0xC7, // Differential lossless (sequential) + SOF9 = 0xC9, // Extended sequential DCT, Arithmetic coding + SOF10 = 0xCA, // Progressive DCT, Arithmetic coding + SOF11 = 0xCB, // Lossless (sequential), Arithmetic coding + SOF13 = 0xCD, // Differential sequential DCT, Arithmetic coding + SOF14 = 0xCE, // Differential progressive DCT, Arithmetic coding + SOF15 = 0xCF, // Differential lossless (sequential), Arithmetic coding + DHT = 0xC4, - SOF5 = 0xC5, - SOF6 = 0xC6, - SOF7 = 0xC7, JPG = 0xC8, - SOF9 = 0xC9, - SOF10 = 0xCA, - SOF11 = 0xCB, DAC = 0xCC, - SOF13 = 0xCD, - SOF14 = 0xCE, - SOF15 = 0xCF, RST0 = 0xD0, RST1 = 0xD1, RST2 = 0xD2, @@ -713,6 +714,7 @@ JPEG_Info :: struct { jfxx_app0: Maybe(JFXX_APP0), comments: [dynamic]string, exif: [dynamic]Exif, + frame_type: JPEG_Marker, } // Function to help with image buffer calculations diff --git a/core/image/jpeg/jpeg.odin b/core/image/jpeg/jpeg.odin index a2ad7e61e..818bd13d5 100644 --- a/core/image/jpeg/jpeg.odin +++ b/core/image/jpeg/jpeg.odin @@ -219,841 +219,851 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a defer delete(blocks) loop: for { + // Loop until we find 0xFF. first = compress.read_u8(ctx) or_return - if first == 0xFF { - marker := cast(image.JPEG_Marker)compress.read_u8(ctx) or_return - if expect_EOI && marker != .EOI { - return img, .Extra_Data_After_SOS - } - #partial switch marker { - case cast(image.JPEG_Marker)0xFF: - // If we encounter multiple FF bytes then just skip them - continue - case .SOI: - return img, .Duplicate_SOI_Marker - case .APP0: - ident := make([dynamic]byte, 0, 16, context.temp_allocator) or_return - length := cast(int)((compress.read_data(ctx, u16be) or_return) - 2) - for { - b := compress.read_u8(ctx) or_return - if b == 0x00 { - break - } - append(&ident, b) or_return - } - if slice.equal(ident[:], image.JFIF_Magic[:]) { - if length != 14 { - // Malformed APP0. Skip it - compress.read_slice(ctx, length - len(ident) - 1) or_return - continue - } + (first == 0xFF) or_continue - version := compress.read_data(ctx, u16be) or_return - units := cast(image.JFIF_Unit)(compress.read_u8(ctx) or_return) - x_density := compress.read_data(ctx, u16be) or_return - y_density := compress.read_data(ctx, u16be) or_return + marker := cast(image.JPEG_Marker)compress.read_u8(ctx) or_return + if expect_EOI && marker != .EOI { + return img, .Extra_Data_After_SOS + } + #partial switch marker { + case cast(image.JPEG_Marker)0xFF: + // If we encounter multiple FF bytes then just skip them + continue + case .SOI: + return img, .Duplicate_SOI_Marker + case .APP0: + ident := make([dynamic]byte, 0, 16, context.temp_allocator) or_return + length := cast(int)((compress.read_data(ctx, u16be) or_return) - 2) + for { + b := compress.read_u8(ctx) or_return + if b == 0x00 { + break + } + append(&ident, b) or_return + } + if slice.equal(ident[:], image.JFIF_Magic[:]) { + if length != 14 { + // Malformed APP0. Skip it + compress.read_slice(ctx, length - len(ident) - 1) or_return + continue + } + + version := compress.read_data(ctx, u16be) or_return + units := cast(image.JFIF_Unit)(compress.read_u8(ctx) or_return) + x_density := compress.read_data(ctx, u16be) or_return + y_density := compress.read_data(ctx, u16be) or_return + x_thumbnail := cast(int)compress.read_u8(ctx) or_return + y_thumbnail := cast(int)compress.read_u8(ctx) or_return + thumbnail: []image.RGB_Pixel + + if x_thumbnail * y_thumbnail != 0 { + greyscale_thumbnail := false + thumbnail_size := x_thumbnail * y_thumbnail * 3 + // According to the JFIF spec, the thumbnail should always be made of RGB pixels. + // But some jpegs encode single-channel thumbnails. + if thumbnail_size != length - 14 && thumbnail_size / 3 == length - 14 { + thumbnail_size = x_thumbnail * y_thumbnail + greyscale_thumbnail = true + } else { + return img, .Invalid_Thumbnail_Size + } + thumb_pixels := slice.reinterpret([]image.RGB_Pixel, compress.read_slice_from_memory(ctx, x_thumbnail * y_thumbnail) or_return) + + if .return_metadata in options { + thumbnail = make([]image.RGB_Pixel, x_thumbnail * y_thumbnail) or_return + copy(thumbnail, thumb_pixels) + + info: ^image.JPEG_Info + if img.metadata == nil { + info = new(image.JPEG_Info) or_return + } else { + info = img.metadata.(^image.JPEG_Info) + } + info.jfif_app0 = image.JFIF_APP0{ + version, + x_density, + y_density, + units, + cast(u8)x_thumbnail, + cast(u8)y_thumbnail, + greyscale_thumbnail, + thumbnail, + } + img.metadata = info + } + } + } else if slice.equal(ident[:], image.JFXX_Magic[:]) { + extension_code := cast(image.JFXX_Extension_Code)compress.read_u8(ctx) or_return + thumbnail: []byte + + switch extension_code { + // We return the JPEG-compressed bytes for this type of thumbnail. + // It's up to the user if they want to decode it by checking the extension code + // and calling image.load() on the thumbnail. + // Not sure where to document that though, maybe it's better if the thumbnail is always raw pixel data. + case .Thumbnail_JPEG: + // +1 for the NUL byte + thumbnail_len := length - (size_of(image.JFXX_Magic) + 1 + size_of(image.JFXX_Extension_Code)) + thumbnail_jpeg := compress.read_slice(ctx, thumbnail_len) or_return + + if .return_metadata in options { + thumbnail = make([]byte, thumbnail_len) or_return + copy(thumbnail, thumbnail_jpeg) + + info: ^image.JPEG_Info + if img.metadata == nil { + info = new(image.JPEG_Info) or_return + } else { + info = img.metadata.(^image.JPEG_Info) + } + info.jfxx_app0 = image.JFXX_APP0{ + extension_code, + 0, + 0, + thumbnail, + } + img.metadata = info + } + case .Thumbnail_3_Byte_RGB: x_thumbnail := cast(int)compress.read_u8(ctx) or_return y_thumbnail := cast(int)compress.read_u8(ctx) or_return - thumbnail: []image.RGB_Pixel + pixels := compress.read_slice(ctx, x_thumbnail * y_thumbnail * 3) or_return - if x_thumbnail * y_thumbnail != 0 { - greyscale_thumbnail := false - thumbnail_size := x_thumbnail * y_thumbnail * 3 - // According to the JFIF spec, the thumbnail should always be made of RGB pixels. - // But some jpegs encode single-channel thumbnails. - if thumbnail_size != length - 14 && thumbnail_size / 3 == length - 14 { - thumbnail_size = x_thumbnail * y_thumbnail - greyscale_thumbnail = true + if .return_metadata in options { + thumbnail = make([]byte, x_thumbnail * y_thumbnail * 3) or_return + copy(thumbnail, pixels) + + info: ^image.JPEG_Info + if img.metadata == nil { + info = new(image.JPEG_Info) or_return } else { - return img, .Invalid_Thumbnail_Size + info = img.metadata.(^image.JPEG_Info) } - thumb_pixels := slice.reinterpret([]image.RGB_Pixel, compress.read_slice_from_memory(ctx, x_thumbnail * y_thumbnail) or_return) - - if .return_metadata in options { - thumbnail = make([]image.RGB_Pixel, x_thumbnail * y_thumbnail) or_return - copy(thumbnail, thumb_pixels) - - info: ^image.JPEG_Info - if img.metadata == nil { - info = new(image.JPEG_Info) or_return - } else { - info = img.metadata.(^image.JPEG_Info) - } - info.jfif_app0 = image.JFIF_APP0{ - version, - x_density, - y_density, - units, - cast(u8)x_thumbnail, - cast(u8)y_thumbnail, - greyscale_thumbnail, - thumbnail, - } - img.metadata = info + info.jfxx_app0 = image.JFXX_APP0{ + extension_code, + cast(u8)x_thumbnail, + cast(u8)y_thumbnail, + thumbnail, } + img.metadata = info } - } else if slice.equal(ident[:], image.JFXX_Magic[:]) { - extension_code := cast(image.JFXX_Extension_Code)compress.read_u8(ctx) or_return - thumbnail: []byte + case .Thumbnail_1_Byte_Palette: // NOTE(illusionman1212): NOT TESTED. Couldn't find a jpeg to test this with. + x_thumbnail := cast(int)compress.read_u8(ctx) or_return + y_thumbnail := cast(int)compress.read_u8(ctx) or_return + palette := slice.reinterpret([]image.RGB_Pixel, compress.read_slice(ctx, THUMBNAIL_PALETTE_SIZE / 3) or_return) + old_pixels := compress.read_slice(ctx, x_thumbnail * y_thumbnail) or_return - switch extension_code { - // We return the JPEG-compressed bytes for this type of thumbnail. - // It's up to the user if they want to decode it by checking the extension code - // and calling image.load() on the thumbnail. - // Not sure where to document that though, maybe it's better if the thumbnail is always raw pixel data. - case .Thumbnail_JPEG: - // +1 for the NUL byte - thumbnail_len := length - (size_of(image.JFXX_Magic) + 1 + size_of(image.JFXX_Extension_Code)) - thumbnail_jpeg := compress.read_slice(ctx, thumbnail_len) or_return - - if .return_metadata in options { - thumbnail = make([]byte, thumbnail_len) or_return - copy(thumbnail, thumbnail_jpeg) - - info: ^image.JPEG_Info - if img.metadata == nil { - info = new(image.JPEG_Info) or_return - } else { - info = img.metadata.(^image.JPEG_Info) - } - info.jfxx_app0 = image.JFXX_APP0{ - extension_code, - 0, - 0, - thumbnail, - } - img.metadata = info + if .return_metadata in options { + pixels := make([]byte, x_thumbnail * y_thumbnail * 3) or_return + for i in 0.. 0 && len(info.exif[len(info.exif) - 1].data) == SEGMENT_MAX_SIZE - len(ident) - 2 { - exif.byte_order = info.exif[len(info.exif) - 1].byte_order + info: ^image.JPEG_Info + if img.metadata == nil { + info = new(image.JPEG_Info) or_return } else { - compress.read_slice(ctx, length - len(ident) - 2) or_return - continue + info = img.metadata.(^image.JPEG_Info) } + info.jfxx_app0 = image.JFXX_APP0{ + extension_code, + cast(u8)x_thumbnail, + cast(u8)y_thumbnail, + pixels, + } + img.metadata = info } + case: + return img, .Invalid_JFXX_Extension_Code + } + } else { + // - 1 for the NUL byte + compress.read_slice(ctx, length - len(ident) - 1) or_return + continue + } + case .APP1: // Metadata + length := cast(int)((compress.read_data(ctx, u16be) or_return) - 2) + if .return_metadata not_in options { + compress.read_slice(ctx, length) or_return + continue + } + info: ^image.JPEG_Info + if img.metadata == nil { + info = new(image.JPEG_Info) or_return + } else { + info = img.metadata.(^image.JPEG_Info) + } - // - 2 for the NUL byte and padding byte - data := compress.read_slice(ctx, length - len(ident) - 2) or_return - exif.data = make([]byte, len(data)) or_return - copy(exif.data, data) + ident := make([dynamic]byte, 0, 16, context.temp_allocator) or_return + for { + b := compress.read_u8(ctx) or_return + if b == 0x00 { + break + } + append(&ident, b) or_return + } - append(&info.exif, exif) or_return - img.metadata = info + if slice.equal(ident[:], image.Exif_Magic[:]) { + // Padding byte according to section 4.7.2.2 in Exif spec 3.0 + compress.read_u8(ctx) or_return + + exif: image.Exif + peek := compress.peek_data(ctx, [4]byte) or_return + if peek[0] == 'M' && peek[1] == 'M' { + exif.byte_order = .big_endian + if peek[2] != 0 || peek[3] != 42 { + // - 2 for the NUL byte and padding byte + compress.read_slice(ctx, length - len(ident) - 2) or_return + continue + } + } else if peek[0] == 'I' && peek[1] == 'I' { + exif.byte_order = .little_endian + if peek[2] != 42 || peek[3] != 0 { + compress.read_slice(ctx, length - len(ident) - 2) or_return + continue + } } else { - // - 1 for the NUL byte - compress.read_slice(ctx, length - len(ident) - 1) or_return - continue - } - case .COM: - length := (compress.read_data(ctx, u16be) or_return) - 2 - comment := string(compress.read_slice(ctx, cast(int)length) or_return) - if .return_metadata in options { - if info, ok := img.metadata.(^image.JPEG_Info); ok { - append(&info.comments, strings.clone(comment)) or_return - } - } - case .DQT: - length := cast(int)(compress.read_data(ctx, u16be) or_return) - 2 + // If we can't determine the endianness then this Exif data is likely a continuation of the previous + // APP1 Exif data - for length > 0 { - precision_and_index := compress.read_u8(ctx) or_return - precision := precision_and_index >> 4 - index := precision_and_index & 0xF - - if precision != 0 && precision != 1 { - return img, .Invalid_Quantization_Table_Precision - } - - if index < 0 || index > 3 { - return img, .Invalid_Quantization_Table_Index - } - - // When precision is 0, we read 64 u8s. - // when it's 1, we read 64 u16s. - table_bytes := 64 - if precision == 1 { - table_bytes = 128 - table := compress.read_slice(ctx, table_bytes) or_return - for v, i in slice.reinterpret([]u16be, table) { - quantization[index][i] = v - } + // We only treat it as such if a previous Exif entry exists and its data length is the max + if len(info.exif) > 0 && len(info.exif[len(info.exif) - 1].data) == SEGMENT_MAX_SIZE - len(ident) - 2 { + exif.byte_order = info.exif[len(info.exif) - 1].byte_order } else { - table := compress.read_slice(ctx, table_bytes) or_return - for v, i in table { - quantization[index][i] = cast(u16be)v - } - } - - length -= table_bytes + 1 - } - case .DHT: - length := (compress.read_data(ctx, u16be) or_return) - 2 - - for length > 0 { - type_index := compress.read_u8(ctx) or_return - type := cast(Coefficient)((type_index >> 4) & 0xF) - index := type_index & 0xF - - if type != .DC && type != .AC { - return img, .Invalid_Huffman_Coefficient_Type - } - - if index < 0 || index > 3 { - return img, .Invalid_Huffman_Table_Index - } - - lengths := compress.read_slice(ctx, HUFFMAN_MAX_BITS) or_return - num_symbols: u8 = 0 - for length, i in lengths { - num_symbols += length - huffman[type][index].offsets[i + 1] = num_symbols - } - - if num_symbols > HUFFMAN_MAX_SYMBOLS { - return img, .Huffman_Symbols_Exceeds_Max - } - - symbols := compress.read_slice(ctx, cast(int)num_symbols) or_return - copy(huffman[type][index].symbols[:], symbols) - - length -= cast(u16be)(1 + HUFFMAN_MAX_BITS + num_symbols) - - code: u32 = 0 - for i in 0.. 0 { + precision_and_index := compress.read_u8(ctx) or_return + precision := precision_and_index >> 4 + index := precision_and_index & 0xF + + if precision != 0 && precision != 1 { + return img, .Invalid_Quantization_Table_Precision } - // Length - compress.read_data(ctx, u16be) or_return - precision := compress.read_u8(ctx) or_return - height := compress.read_data(ctx, u16be) or_return - width := compress.read_data(ctx, u16be) or_return - components := compress.read_u8(ctx) or_return - img.width = cast(int)width - img.height = cast(int)height - img.depth = cast(int)precision - img.channels = cast(int)components - - // TODO: 12-bit precision is valid too but we don't support it. - if precision == 12 { - return img, .Unsupported_12_Bit_Depth - } - if precision != 8 { - return img, .Invalid_Frame_Bit_Depth_Combo + if index < 0 || index > 3 { + return img, .Invalid_Quantization_Table_Index } - // TODO: spec allows for the height to be 0 on the condition that a DNL marker MUST exist to define - // how many lines in the frame we have. - // ISO/IEC 10918-1: 1993. - // Section B.2.5 - if img.width == 0 || img.height == 0 { - return img, .Invalid_Image_Dimensions - } - - if u128(img.width) * u128(img.height) > image.MAX_DIMENSIONS { - return img, .Image_Dimensions_Too_Large - } - - // TODO: Some JPEGs use CMYK as the color model which means there will be 4 components - if components != 1 && components != 3 { - return img, .Invalid_Number_Of_Channels - } - - mcu_width = (img.width + 7) / BLOCK_SIZE - mcu_height = (img.height + 7) / BLOCK_SIZE - block_width = mcu_width - block_height = mcu_height - - for _ in 0.. .Cr { - return img, .Image_Does_Not_Adhere_to_Spec + length -= table_bytes + 1 + } + case .DHT: + length := (compress.read_data(ctx, u16be) or_return) - 2 + + for length > 0 { + type_index := compress.read_u8(ctx) or_return + type := cast(Coefficient)((type_index >> 4) & 0xF) + index := type_index & 0xF + + if type != .DC && type != .AC { + return img, .Invalid_Huffman_Coefficient_Type + } + + if index < 0 || index > 3 { + return img, .Invalid_Huffman_Table_Index + } + + lengths := compress.read_slice(ctx, HUFFMAN_MAX_BITS) or_return + num_symbols: u8 = 0 + for length, i in lengths { + num_symbols += length + huffman[type][index].offsets[i + 1] = num_symbols + } + + if num_symbols > HUFFMAN_MAX_SYMBOLS { + return img, .Huffman_Symbols_Exceeds_Max + } + + symbols := compress.read_slice(ctx, cast(int)num_symbols) or_return + copy(huffman[type][index].symbols[:], symbols) + + length -= cast(u16be)(1 + HUFFMAN_MAX_BITS + num_symbols) + + code: u32 = 0 + for i in 0..> 4 - vertical_sampling := h_v_factors & 0xF + // Length + compress.read_data(ctx, u16be) or_return + precision := compress.read_u8(ctx) or_return + height := compress.read_data(ctx, u16be) or_return + width := compress.read_data(ctx, u16be) or_return + components := compress.read_u8(ctx) or_return + img.width = cast(int)width + img.height = cast(int)height + img.depth = cast(int)precision + img.channels = cast(int)components - // TODO: spec says the range for the sampling factors is 1-4 - // We only support 1,2 for now. - if horizontal_sampling < 1 || horizontal_sampling > 2 { + // TODO: 12-bit precision is valid too but we don't support it. + if precision == 12 { + return img, .Unsupported_12_Bit_Depth + } + if precision != 8 { + return img, .Invalid_Frame_Bit_Depth_Combo + } + + // TODO: spec allows for the height to be 0 on the condition that a DNL marker MUST exist to define + // how many lines in the frame we have. + // ISO/IEC 10918-1: 1993. + // Section B.2.5 + if img.width == 0 || img.height == 0 { + return img, .Invalid_Image_Dimensions + } + + if u128(img.width) * u128(img.height) > image.MAX_DIMENSIONS { + return img, .Image_Dimensions_Too_Large + } + + // TODO: Some JPEGs use CMYK as the color model which means there will be 4 components + if components != 1 && components != 3 { + return img, .Invalid_Number_Of_Channels + } + + if img.metadata != nil { + info := img.metadata.(^image.JPEG_Info) + info.frame_type = marker + } + + mcu_width = (img.width + 7) / BLOCK_SIZE + mcu_height = (img.height + 7) / BLOCK_SIZE + block_width = mcu_width + block_height = mcu_height + + for _ in 0.. .Cr { + return img, .Image_Does_Not_Adhere_to_Spec + } + + h_v_factors := compress.read_u8(ctx) or_return + horizontal_sampling := h_v_factors >> 4 + vertical_sampling := h_v_factors & 0xF + + // TODO: spec says the range for the sampling factors is 1-4 + // We only support 1,2 for now. + if horizontal_sampling < 1 || horizontal_sampling > 2 { + return img, .Invalid_Sampling_Factor + } + if vertical_sampling < 1 || vertical_sampling > 2 { + return img, .Invalid_Sampling_Factor + } + + if id == .Y { + if horizontal_sampling == 2 && mcu_width % 2 == 1 { + block_width += 1 + } + if vertical_sampling == 2 && mcu_height % 2 == 1 { + block_height += 1 + } + } else { + if horizontal_sampling != 1 && vertical_sampling != 1 { return img, .Invalid_Sampling_Factor } - if vertical_sampling < 1 || vertical_sampling > 2 { - return img, .Invalid_Sampling_Factor - } - - if id == .Y { - if horizontal_sampling == 2 && mcu_width % 2 == 1 { - block_width += 1 - } - if vertical_sampling == 2 && mcu_height % 2 == 1 { - block_height += 1 - } - } else { - if horizontal_sampling != 1 && vertical_sampling != 1 { - return img, .Invalid_Sampling_Factor - } - } - - quantization_table_idx := compress.read_u8(ctx) or_return - - if quantization_table_idx < 0 || quantization_table_idx > 3 { - return img, .Invalid_Quantization_Table_Index - } - - color_components[id].quantization_table_idx = quantization_table_idx - color_components[id].v_sampling_factor = cast(int)vertical_sampling - color_components[id].h_sampling_factor = cast(int)horizontal_sampling - } - case .SOF2: // Progressive DCT - fallthrough - case .SOF3: // Lossless (sequential) - fallthrough - case .SOF5: // Differential sequential DCT - fallthrough - case .SOF6: // Differential progressive DCT - fallthrough - case .SOF7: // Differential lossless (sequential) - fallthrough - case .SOF9: // Extended sequential DCT, Arithmetic coding - fallthrough - case .SOF10: // Progressive DCT, Arithmetic coding - fallthrough - case .SOF11: // Lossless (sequential), Arithmetic coding - fallthrough - case .SOF13: // Differential sequential DCT, Arithmetic coding - fallthrough - case .SOF14: // Differential progressive DCT, Arithmetic coding - fallthrough - case .SOF15: // Differential lossless (sequential), Arithmetic coding - return img, .Unsupported_Frame_Type - case .SOS: - if img.channels == 0 && img.depth == 0 && img.width == 0 && img.height == 0 { - return img, .Encountered_SOS_Before_SOF } - if .do_not_decompress_image in options { - return img, nil + quantization_table_idx := compress.read_u8(ctx) or_return + + if quantization_table_idx < 0 || quantization_table_idx > 3 { + return img, .Invalid_Quantization_Table_Index } - // Length - compress.read_data(ctx, u16be) or_return - num_components := compress.read_u8(ctx) or_return - if num_components != 1 && num_components != 3 { - return img, .Invalid_Number_Of_Channels + color_components[id].quantization_table_idx = quantization_table_idx + color_components[id].v_sampling_factor = cast(int)vertical_sampling + color_components[id].h_sampling_factor = cast(int)horizontal_sampling + } + case .SOF2: // Progressive DCT + fallthrough + case .SOF3: // Lossless (sequential) + fallthrough + case .SOF5: // Differential sequential DCT + fallthrough + case .SOF6: // Differential progressive DCT + fallthrough + case .SOF7: // Differential lossless (sequential) + fallthrough + case .SOF9: // Extended sequential DCT, Arithmetic coding + fallthrough + case .SOF10: // Progressive DCT, Arithmetic coding + fallthrough + case .SOF11: // Lossless (sequential), Arithmetic coding + fallthrough + case .SOF13: // Differential sequential DCT, Arithmetic coding + fallthrough + case .SOF14: // Differential progressive DCT, Arithmetic coding + fallthrough + case .SOF15: // Differential lossless (sequential), Arithmetic coding + if img.metadata != nil { + info := img.metadata.(^image.JPEG_Info) + info.frame_type = marker + } + return img, .Unsupported_Frame_Type + case .SOS: + if img.channels == 0 && img.depth == 0 && img.width == 0 && img.height == 0 { + return img, .Encountered_SOS_Before_SOF + } + + if .do_not_decompress_image in options { + return img, nil + } + + // Length + compress.read_data(ctx, u16be) or_return + num_components := compress.read_u8(ctx) or_return + if num_components != 1 && num_components != 3 { + return img, .Invalid_Number_Of_Channels + } + + for _ in 0.. .Cr { + return img, .Image_Does_Not_Adhere_to_Spec } - for _ in 0.. .Cr { - return img, .Image_Does_Not_Adhere_to_Spec - } + // high 4 is DC, low 4 is AC + coefficient_indices := compress.read_u8(ctx) or_return + dc_table_idx := coefficient_indices >> 4 + ac_table_idx := coefficient_indices & 0xF - // high 4 is DC, low 4 is AC - coefficient_indices := compress.read_u8(ctx) or_return - dc_table_idx := coefficient_indices >> 4 - ac_table_idx := coefficient_indices & 0xF - - if (dc_table_idx < 0 || dc_table_idx > 3) || (ac_table_idx < 0 || ac_table_idx > 3) { - return img, .Invalid_Huffman_Table_Index - } - - color_components[component_id].dc_table_idx = dc_table_idx - color_components[component_id].ac_table_idx = ac_table_idx + if (dc_table_idx < 0 || dc_table_idx > 3) || (ac_table_idx < 0 || ac_table_idx > 3) { + return img, .Invalid_Huffman_Table_Index } - // TODO: These aren't used for sequential DCT, only progressive and lossless. - Ss := compress.read_u8(ctx) or_return - _ = Ss - Se := compress.read_u8(ctx) or_return - _ = Se - Ah_Al := compress.read_u8(ctx) or_return - _ = Ah_Al - blocks = make([]Block, block_height * block_width) or_return + color_components[component_id].dc_table_idx = dc_table_idx + color_components[component_id].ac_table_idx = ac_table_idx + } + // TODO: These aren't used for sequential DCT, only progressive and lossless. + Ss := compress.read_u8(ctx) or_return + _ = Ss + Se := compress.read_u8(ctx) or_return + _ = Se + Ah_Al := compress.read_u8(ctx) or_return + _ = Ah_Al - previous_dc: [Component]i16 + blocks = make([]Block, block_height * block_width) or_return - luma_v_sampling_factor := color_components[.Y].v_sampling_factor - luma_h_sampling_factor := color_components[.Y].h_sampling_factor + previous_dc: [Component]i16 - restart_interval *= luma_v_sampling_factor * luma_h_sampling_factor - #no_bounds_check for y := 0; y < mcu_height; y += luma_v_sampling_factor { - for x := 0; x < mcu_width; x += luma_h_sampling_factor { - blk := y * block_width + x + luma_v_sampling_factor := color_components[.Y].v_sampling_factor + luma_h_sampling_factor := color_components[.Y].h_sampling_factor - if restart_interval != 0 && blk % restart_interval == 0 { - previous_dc[.Y] = 0 - previous_dc[.Cb] = 0 - previous_dc[.Cr] = 0 - byte_align(ctx) - } - for c in 1..=img.channels { - c := cast(Component)c - for v in 0.. 11 { + length := get_symbol(ctx, dc_table) + + if length > 11 { + return img, .Corrupt + } + + dc_coeff := cast(i16)read_bits_msb(ctx, length) + + if length != 0 && dc_coeff < (1 << (length - 1)) { + dc_coeff -= (1 << length) - 1 + } + mcu[0] = (dc_coeff + previous_dc[c]) * cast(i16)quantization_table[0] + previous_dc[c] = dc_coeff + previous_dc[c] + + for i := 1; i < COEFFICIENT_COUNT; i += 1 { + // High nibble is amount of 0s to skip. + // Low nibble is length of coeff. + symbol := get_symbol(ctx, ac_table) + + // Special symbol used to indicate + // that the rest of the MCU is filled with 0s + if symbol == 0x00 { + continue h_loop + } + + amnt_zeros := int(symbol >> 4) + ac_coeff_len := symbol & 0xF + ac_coeff: i16 = 0 + + if i + amnt_zeros >= COEFFICIENT_COUNT || ac_coeff_len > 10 { return img, .Corrupt } - dc_coeff := cast(i16)read_bits_msb(ctx, length) + i += amnt_zeros - if length != 0 && dc_coeff < (1 << (length - 1)) { - dc_coeff -= (1 << length) - 1 + ac_coeff = cast(i16)read_bits_msb(ctx, ac_coeff_len) + if ac_coeff < (1 << (ac_coeff_len - 1)) { + ac_coeff -= (1 << ac_coeff_len) - 1 } - mcu[0] = (dc_coeff + previous_dc[c]) * cast(i16)quantization_table[0] - previous_dc[c] = dc_coeff + previous_dc[c] - for i := 1; i < COEFFICIENT_COUNT; i += 1 { - // High nibble is amount of 0s to skip. - // Low nibble is length of coeff. - symbol := get_symbol(ctx, ac_table) - - // Special symbol used to indicate - // that the rest of the MCU is filled with 0s - if symbol == 0x00 { - continue h_loop - } - - amnt_zeros := int(symbol >> 4) - ac_coeff_len := symbol & 0xF - ac_coeff: i16 = 0 - - if i + amnt_zeros >= COEFFICIENT_COUNT || ac_coeff_len > 10 { - return img, .Corrupt - } - - i += amnt_zeros - - ac_coeff = cast(i16)read_bits_msb(ctx, ac_coeff_len) - if ac_coeff < (1 << (ac_coeff_len - 1)) { - ac_coeff -= (1 << ac_coeff_len) - 1 - } - - mcu[zigzag[i]] = ac_coeff * cast(i16)quantization_table[i] - } + mcu[zigzag[i]] = ac_coeff * cast(i16)quantization_table[i] } } } + } - for c in 1..=img.channels { - c := cast(Component)c + for c in 1..=img.channels { + c := cast(Component)c - for v in 0..= 0; v -= 1 { - for h := luma_h_sampling_factor - 1; h >= 0; h -= 1 { - y_blk := &blocks[(y + v) * block_width + (x + h)] + // Convert the YCbCr pixel data to RGB + cbcr_blk := &blocks[y * block_width + x] + for v := luma_v_sampling_factor - 1; v >= 0; v -= 1 { + for h := luma_h_sampling_factor - 1; h >= 0; h -= 1 { + y_blk := &blocks[(y + v) * block_width + (x + h)] - for j := BLOCK_SIZE - 1; j >= 0; j -= 1 { - for k := BLOCK_SIZE - 1; k >= 0; k -= 1 { - i := j * BLOCK_SIZE + k - cbcr_pixel_row := j / luma_v_sampling_factor + 4 * v - cbcr_pixel_column := k / luma_h_sampling_factor + 4 * h - cbcr_pixel := cbcr_pixel_row * BLOCK_SIZE + cbcr_pixel_column + for j := BLOCK_SIZE - 1; j >= 0; j -= 1 { + for k := BLOCK_SIZE - 1; k >= 0; k -= 1 { + i := j * BLOCK_SIZE + k + cbcr_pixel_row := j / luma_v_sampling_factor + 4 * v + cbcr_pixel_column := k / luma_h_sampling_factor + 4 * h + cbcr_pixel := cbcr_pixel_row * BLOCK_SIZE + cbcr_pixel_column - r := cast(i16)clamp(cast(f32)y_blk[.Y][i] + 1.402 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255) - g := cast(i16)clamp(cast(f32)y_blk[.Y][i] - 0.344 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] - 0.714 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255) - b := cast(i16)clamp(cast(f32)y_blk[.Y][i] + 1.772 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] + 128, 0, 255) + r := cast(i16)clamp(cast(f32)y_blk[.Y][i] + 1.402 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255) + g := cast(i16)clamp(cast(f32)y_blk[.Y][i] - 0.344 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] - 0.714 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255) + b := cast(i16)clamp(cast(f32)y_blk[.Y][i] + 1.772 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] + 128, 0, 255) - y_blk[.Y][i] = r - y_blk[.Cb][i] = g - y_blk[.Cr][i] = b - } + y_blk[.Y][i] = r + y_blk[.Cb][i] = g + y_blk[.Cr][i] = b } } } } } - - orig_channels := img.channels - - // We automatically expand grayscale images to RGB - if img.channels == 1 { - img.channels += 2 - } - - if .alpha_add_if_missing in options { - img.channels += 1 - orig_channels += 1 - } - - if resize(&img.pixels.buf, img.width * img.height * img.channels) != nil { - return img, .Unable_To_Allocate_Or_Resize - } - - switch orig_channels { - case 1: // Grayscale JPEG expanded to RGB - out := mem.slice_data_cast([]image.RGB_Pixel, img.pixels.buf[:]) - out_idx := 0 - for y in 0.. Date: Tue, 9 Sep 2025 20:45:32 +0200 Subject: [PATCH 027/162] Add `nullptr` checks to more type helpers. --- src/types.cpp | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/types.cpp b/src/types.cpp index c465714db..6b94b1690 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1230,7 +1230,6 @@ gb_internal bool is_type_named(Type *t) { } gb_internal bool is_type_boolean(Type *t) { - // t = core_type(t); t = base_type(t); if (t == nullptr) { return false; } if (t->kind == Type_Basic) { @@ -1239,7 +1238,6 @@ gb_internal bool is_type_boolean(Type *t) { return false; } gb_internal bool is_type_integer(Type *t) { - // t = core_type(t); t = base_type(t); if (t == nullptr) { return false; } if (t->kind == Type_Basic) { @@ -1249,6 +1247,7 @@ gb_internal bool is_type_integer(Type *t) { } gb_internal bool is_type_integer_like(Type *t) { t = core_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & (BasicFlag_Integer|BasicFlag_Boolean)) != 0; } @@ -1281,7 +1280,6 @@ gb_internal bool is_type_integer_128bit(Type *t) { return false; } gb_internal bool is_type_rune(Type *t) { - // t = core_type(t); t = base_type(t); if (t == nullptr) { return false; } if (t->kind == Type_Basic) { @@ -1290,7 +1288,6 @@ gb_internal bool is_type_rune(Type *t) { return false; } gb_internal bool is_type_numeric(Type *t) { - // t = core_type(t); t = base_type(t); if (t == nullptr) { return false; } if (t->kind == Type_Basic) { @@ -1448,24 +1445,28 @@ gb_internal bool is_type_tuple(Type *t) { return t->kind == Type_Tuple; } gb_internal bool is_type_uintptr(Type *t) { + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.kind == Basic_uintptr); } return false; } gb_internal bool is_type_rawptr(Type *t) { + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return t->Basic.kind == Basic_rawptr; } return false; } gb_internal bool is_type_u8(Type *t) { + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return t->Basic.kind == Basic_u8; } return false; } gb_internal bool is_type_u16(Type *t) { + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return t->Basic.kind == Basic_u16; } @@ -1612,6 +1613,7 @@ gb_internal bool is_matrix_square(Type *t) { gb_internal bool is_type_valid_for_matrix_elems(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } if (is_type_integer(t)) { return true; } else if (is_type_float(t)) { @@ -1839,40 +1841,49 @@ gb_internal Type *base_complex_elem_type(Type *t) { gb_internal bool is_type_struct(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_Struct; } gb_internal bool is_type_union(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_Union; } gb_internal bool is_type_soa_struct(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None; } gb_internal bool is_type_raw_union(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return (t->kind == Type_Struct && t->Struct.is_raw_union); } gb_internal bool is_type_enum(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return (t->kind == Type_Enum); } gb_internal bool is_type_bit_set(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return (t->kind == Type_BitSet); } gb_internal bool is_type_bit_field(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return (t->kind == Type_BitField); } gb_internal bool is_type_map(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return t->kind == Type_Map; } gb_internal bool is_type_union_maybe_pointer(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Union && t->Union.variants.count == 1) { Type *v = t->Union.variants[0]; return is_type_internally_pointer_like(v); @@ -1883,6 +1894,7 @@ gb_internal bool is_type_union_maybe_pointer(Type *t) { gb_internal bool is_type_union_maybe_pointer_original_alignment(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Union && t->Union.variants.count == 1) { Type *v = t->Union.variants[0]; if (is_type_internally_pointer_like(v)) { @@ -1917,6 +1929,7 @@ gb_internal TypeEndianKind type_endian_kind_of(Type *t) { gb_internal bool is_type_endian_big(Type *t) { t = core_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { if (t->Basic.flags & BasicFlag_EndianBig) { return true; @@ -1933,6 +1946,7 @@ gb_internal bool is_type_endian_big(Type *t) { } gb_internal bool is_type_endian_little(Type *t) { t = core_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { if (t->Basic.flags & BasicFlag_EndianLittle) { return true; @@ -1950,6 +1964,7 @@ gb_internal bool is_type_endian_little(Type *t) { gb_internal bool is_type_endian_platform(Type *t) { t = core_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_Basic) { return (t->Basic.flags & (BasicFlag_EndianLittle|BasicFlag_EndianBig)) == 0; } else if (t->kind == Type_BitSet) { @@ -1965,6 +1980,7 @@ gb_internal bool types_have_same_internal_endian(Type *a, Type *b) { } gb_internal bool is_type_endian_specific(Type *t) { t = core_type(t); + if (t == nullptr) { return false; } if (t->kind == Type_BitSet) { t = bit_set_to_int(t); } @@ -2062,19 +2078,23 @@ gb_internal Type *integer_endian_type_to_platform_type(Type *t) { gb_internal bool is_type_any(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return (t->kind == Type_Basic && t->Basic.kind == Basic_any); } gb_internal bool is_type_typeid(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } return (t->kind == Type_Basic && t->Basic.kind == Basic_typeid); } gb_internal bool is_type_untyped_nil(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } // NOTE(bill): checking for `nil` or `---` at once is just to improve the error handling return (t->kind == Type_Basic && (t->Basic.kind == Basic_UntypedNil || t->Basic.kind == Basic_UntypedUninit)); } gb_internal bool is_type_untyped_uninit(Type *t) { t = base_type(t); + if (t == nullptr) { return false; } // NOTE(bill): checking for `nil` or `---` at once is just to improve the error handling return (t->kind == Type_Basic && t->Basic.kind == Basic_UntypedUninit); } From 413b44d05cfb592b4797492a934c7b084f9f4024 Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Wed, 10 Sep 2025 13:23:56 +0200 Subject: [PATCH 028/162] Add missing caller location param to append in strings builder --- core/strings/builder.odin | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/strings/builder.odin b/core/strings/builder.odin index b1180d5e9..285ced9ce 100644 --- a/core/strings/builder.odin +++ b/core/strings/builder.odin @@ -296,8 +296,8 @@ Inputs: Returns: - res: A cstring of the Builder's buffer */ -unsafe_to_cstring :: proc(b: ^Builder) -> (res: cstring) { - append(&b.buf, 0) +unsafe_to_cstring :: proc(b: ^Builder, loc := #caller_location) -> (res: cstring) { + append(&b.buf, 0, loc) pop(&b.buf) return cstring(raw_data(b.buf)) } @@ -311,8 +311,8 @@ Returns: - res: A cstring of the Builder's buffer upon success - err: An optional allocator error if one occured, `nil` otherwise */ -to_cstring :: proc(b: ^Builder) -> (res: cstring, err: mem.Allocator_Error) #optional_allocator_error { - n := append(&b.buf, 0) or_return +to_cstring :: proc(b: ^Builder, loc := #caller_location) -> (res: cstring, err: mem.Allocator_Error) #optional_allocator_error { + n := append(&b.buf, 0, loc) or_return if n != 1 { return nil, .Out_Of_Memory } @@ -518,9 +518,9 @@ Output: abc */ -write_string :: proc(b: ^Builder, s: string) -> (n: int) { +write_string :: proc(b: ^Builder, s: string, loc := #caller_location) -> (n: int) { n0 := len(b.buf) - append(&b.buf, s) + append(&b.buf, s, loc) n1 := len(b.buf) return n1-n0 } From 629777b988da50ab347b6309ac07821acfd1ee59 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 12:42:36 +0100 Subject: [PATCH 029/162] Multithread `check_update_dependency_tree_for_procedures` --- src/checker.cpp | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/checker.cpp b/src/checker.cpp index e5dda2aa7..c43175230 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -7025,6 +7025,7 @@ gb_internal void add_type_info_for_type_definitions(Checker *c) { } } +#if 0 gb_internal void check_walk_all_dependencies(DeclInfo *decl) { if (decl == nullptr) { return; @@ -7046,6 +7047,44 @@ gb_internal void check_update_dependency_tree_for_procedures(Checker *c) { check_walk_all_dependencies(decl); } } +#else +gb_internal void check_walk_all_dependencies(DeclInfo *decl); + +gb_internal WORKER_TASK_PROC(check_walk_all_dependencies_worker_proc) { + if (data == nullptr) { + return 0; + } + DeclInfo *decl = cast(DeclInfo *)data; + + for (DeclInfo *child = decl->next_child; child != nullptr; child = child->next_sibling) { + thread_pool_add_task(check_walk_all_dependencies_worker_proc, child); + check_walk_all_dependencies(child); + } + + add_deps_from_child_to_parent(decl); + return 0; +} + +gb_internal void check_walk_all_dependencies(DeclInfo *decl) { + if (decl != nullptr) { + thread_pool_add_task(check_walk_all_dependencies_worker_proc, decl); + } +} + +gb_internal void check_update_dependency_tree_for_procedures(Checker *c) { + mutex_lock(&c->nested_proc_lits_mutex); + for (DeclInfo *decl : c->nested_proc_lits) { + check_walk_all_dependencies(decl); + } + mutex_unlock(&c->nested_proc_lits_mutex); + for (Entity *e : c->info.entities) { + DeclInfo *decl = e->decl_info; + check_walk_all_dependencies(decl); + } + + thread_pool_wait(); +} +#endif gb_internal void check_parsed_files(Checker *c) { From 70d396c8ad7b79e3e3676cfbb732565653f0d627 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 13:26:07 +0100 Subject: [PATCH 030/162] Split type and inline cycles into separate loops --- src/checker.cpp | 41 ++++++++++++++++++++++++++--------------- src/checker.hpp | 3 +++ 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index c43175230..441c7fa6a 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2552,14 +2552,12 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { } } - gb_internal void add_dependency_to_set(Checker *c, Entity *entity) { if (entity == nullptr) { return; } CheckerInfo *info = &c->info; - auto *set = &info->minimum_dependency_set; if (entity->type != nullptr && is_type_polymorphic(entity->type)) { @@ -2569,8 +2567,11 @@ gb_internal void add_dependency_to_set(Checker *c, Entity *entity) { } } - if (ptr_set_update(set, entity)) { - return; + { + MUTEX_GUARD(&info->minimum_dependency_type_info_mutex); + if (ptr_set_update(&info->minimum_dependency_set, entity)) { + return; + } } DeclInfo *decl = decl_info_of_entity(entity); @@ -7173,25 +7174,35 @@ gb_internal void check_parsed_files(Checker *c) { } check_merge_queues_into_arrays(c); - TIME_SECTION("check for type cycles and inline cycles"); + TIME_SECTION("check for type cycles"); // NOTE(bill): Check for illegal cyclic type declarations for_array(i, c->info.definitions) { Entity *e = c->info.definitions[i]; - if (e->kind == Entity_TypeName && e->type != nullptr && is_type_typed(e->type)) { + if (e->kind != Entity_TypeName) { + continue; + } + if (e->type != nullptr && is_type_typed(e->type)) { if (e->TypeName.is_type_alias) { // Ignore for the time being } else { (void)type_align_of(e->type); } - } else if (e->kind == Entity_Procedure) { - DeclInfo *decl = e->decl_info; - ast_node(pl, ProcLit, decl->proc_lit); - if (pl->inlining == ProcInlining_inline) { - for (Entity *dep : decl->deps) { - if (dep == e) { - error(e->token, "Cannot inline recursive procedure '%.*s'", LIT(e->token.string)); - break; - } + } + } + + TIME_SECTION("check for inline cycles"); + for_array(i, c->info.definitions) { + Entity *e = c->info.definitions[i]; + if (e->kind != Entity_Procedure) { + continue; + } + DeclInfo *decl = e->decl_info; + ast_node(pl, ProcLit, decl->proc_lit); + if (pl->inlining == ProcInlining_inline) { + for (Entity *dep : decl->deps) { + if (dep == e) { + error(e->token, "Cannot inline recursive procedure '%.*s'", LIT(e->token.string)); + break; } } } diff --git a/src/checker.hpp b/src/checker.hpp index 58ac8beb5..1da46b74a 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -448,7 +448,10 @@ struct CheckerInfo { AstPackage * init_package; Scope * init_scope; Entity * entry_point; + + BlockingMutex minimum_dependency_set_mutex; PtrSet minimum_dependency_set; + BlockingMutex minimum_dependency_type_info_mutex; PtrMap min_dep_type_info_index_map; TypeSet min_dep_type_info_set; From 60684ff028fe4b0df1925d23d4ff05192f45faab Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 13:39:06 +0100 Subject: [PATCH 031/162] Multithread some of the min dep system --- src/checker.cpp | 111 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 96 insertions(+), 15 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index 441c7fa6a..26430359c 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2377,8 +2377,11 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { return; } - if (type_set_update(&c->info.min_dep_type_info_set, t)) { - return; + { + MUTEX_GUARD(&c->info.minimum_dependency_type_info_mutex); + if (type_set_update(&c->info.min_dep_type_info_set, t)) { + return; + } } // Add nested types @@ -2604,6 +2607,82 @@ gb_internal void add_dependency_to_set(Checker *c, Entity *entity) { } } +struct AddDependecyToSetWorkerData { + Checker *c; + Entity *entity; +}; + +gb_internal void add_dependency_to_set_threaded(Checker *c, Entity *entity); + +gb_internal WORKER_TASK_PROC(add_dependency_to_set_worker) { + AddDependecyToSetWorkerData *wd = cast(AddDependecyToSetWorkerData *)data; + Checker *c = wd->c; + Entity *entity = wd->entity; + if (entity == nullptr) { + return 0; + } + + CheckerInfo *info = &c->info; + + if (entity->type != nullptr && + is_type_polymorphic(entity->type)) { + DeclInfo *decl = decl_info_of_entity(entity); + if (decl != nullptr && decl->gen_proc_type == nullptr) { + return 0; + } + } + + { + MUTEX_GUARD(&info->minimum_dependency_type_info_mutex); + if (ptr_set_update(&info->minimum_dependency_set, entity)) { + return 0; + } + } + + DeclInfo *decl = decl_info_of_entity(entity); + if (decl == nullptr) { + return 0; + } + for (TypeInfoPair const tt : decl->type_info_deps) { + add_min_dep_type_info(c, tt.type); + } + + for (Entity *e : decl->deps) { + add_dependency_to_set(c, e); + if (e->kind == Entity_Procedure && e->Procedure.is_foreign) { + Entity *fl = e->Procedure.foreign_library; + if (fl != nullptr) { + GB_ASSERT_MSG(fl->kind == Entity_LibraryName && + (fl->flags&EntityFlag_Used), + "%.*s", LIT(entity->token.string)); + add_dependency_to_set_threaded(c, fl); + } + } else if (e->kind == Entity_Variable && e->Variable.is_foreign) { + Entity *fl = e->Variable.foreign_library; + if (fl != nullptr) { + GB_ASSERT_MSG(fl->kind == Entity_LibraryName && + (fl->flags&EntityFlag_Used), + "%.*s", LIT(entity->token.string)); + add_dependency_to_set_threaded(c, fl); + } + } + } + return 0; +} + + +gb_internal void add_dependency_to_set_threaded(Checker *c, Entity *entity) { + if (entity == nullptr) { + return; + } + + AddDependecyToSetWorkerData *wd = gb_alloc_item(temporary_allocator(), AddDependecyToSetWorkerData); + wd->c = c; + wd->entity = entity; + thread_pool_add_task(add_dependency_to_set_worker, wd); +} + + gb_internal void force_add_dependency_entity(Checker *c, Scope *scope, String const &name) { Entity *e = scope_lookup(scope, name); if (e == nullptr) { @@ -2657,23 +2736,23 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st Entity *e = c->info.definitions[i]; if (e->scope == builtin_pkg->scope) { if (e->type == nullptr) { - add_dependency_to_set(c, e); + add_dependency_to_set_threaded(c, e); } } else if (e->kind == Entity_Procedure && e->Procedure.is_export) { - add_dependency_to_set(c, e); + add_dependency_to_set_threaded(c, e); } else if (e->kind == Entity_Variable && e->Variable.is_export) { - add_dependency_to_set(c, e); + add_dependency_to_set_threaded(c, e); } } for (Entity *e; mpsc_dequeue(&c->info.required_foreign_imports_through_force_queue, &e); /**/) { array_add(&c->info.required_foreign_imports_through_force, e); - add_dependency_to_set(c, e); + add_dependency_to_set_threaded(c, e); } for (Entity *e; mpsc_dequeue(&c->info.required_global_variable_queue, &e); /**/) { e->flags |= EntityFlag_Used; - add_dependency_to_set(c, e); + add_dependency_to_set_threaded(c, e); } for_array(i, c->info.entities) { @@ -2681,16 +2760,16 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st switch (e->kind) { case Entity_Variable: if (e->Variable.is_export) { - add_dependency_to_set(c, e); + add_dependency_to_set_threaded(c, e); } else if (e->flags & EntityFlag_Require) { - add_dependency_to_set(c, e); + add_dependency_to_set_threaded(c, e); } break; case Entity_Procedure: if (e->Procedure.is_export) { - add_dependency_to_set(c, e); + add_dependency_to_set_threaded(c, e); } else if (e->flags & EntityFlag_Require) { - add_dependency_to_set(c, e); + add_dependency_to_set_threaded(c, e); } if (e->flags & EntityFlag_Init) { Type *t = base_type(e->type); @@ -2730,7 +2809,7 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st if (is_init) { - add_dependency_to_set(c, e); + add_dependency_to_set_threaded(c, e); array_add(&c->info.init_procedures, e); } } else if (e->flags & EntityFlag_Fini) { @@ -2765,7 +2844,7 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st } if (is_fini) { - add_dependency_to_set(c, e); + add_dependency_to_set_threaded(c, e); array_add(&c->info.fini_procedures, e); } } @@ -2782,7 +2861,7 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st Entity *e = entry.value; if (e != nullptr) { e->flags |= EntityFlag_Used; - add_dependency_to_set(c, e); + add_dependency_to_set_threaded(c, e); } } @@ -2797,7 +2876,7 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st } } else if (start != nullptr) { start->flags |= EntityFlag_Used; - add_dependency_to_set(c, start); + add_dependency_to_set_threaded(c, start); } } @@ -2905,6 +2984,8 @@ gb_internal void generate_minimum_dependency_set(Checker *c, Entity *start) { generate_minimum_dependency_set_internal(c, start); + thread_pool_wait(); + #undef FORCE_ADD_RUNTIME_ENTITIES } From 75449283c2e3fd0ebfcd5594ad0b55aa895682e8 Mon Sep 17 00:00:00 2001 From: Brad Lewis <22850972+BradLewis@users.noreply.github.com> Date: Wed, 10 Sep 2025 08:44:06 -0400 Subject: [PATCH 032/162] Allow missing trailing comma with proc groups with odin parser --- 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 18dd9aa88..dab2d5d6a 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -2485,7 +2485,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { allow_token(p, .Comma) or_break } - close := expect_token(p, .Close_Brace) + close := expect_closing_brace_of_field_list(p) if len(args) == 0 { error(p, tok.pos, "expected at least 1 argument in procedure group") From 9492dccb4a94cf266f05f0d7b47b5c1ce8daa272 Mon Sep 17 00:00:00 2001 From: Jacob Friedman Date: Wed, 10 Sep 2025 16:50:12 +0200 Subject: [PATCH 033/162] Fix incorrect json encoding for control characters < 32 --- core/io/util.odin | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/core/io/util.odin b/core/io/util.odin index 72983523a..a956a5975 100644 --- a/core/io/util.odin +++ b/core/io/util.odin @@ -189,6 +189,23 @@ write_escaped_rune :: proc(w: Writer, r: rune, quote: byte, html_safe := false, write_encoded_rune(w, r, false, &n) or_return return } + if r < 32 && for_json { + switch r { + case '\b': write_string(w, `\b`, &n) or_return + case '\f': write_string(w, `\f`, &n) or_return + case '\n': write_string(w, `\n`, &n) or_return + case '\r': write_string(w, `\r`, &n) or_return + case '\t': write_string(w, `\t`, &n) or_return + case: + write_byte(w, '\\', &n) or_return + write_byte(w, 'u', &n) or_return + write_byte(w, '0', &n) or_return + write_byte(w, '0', &n) or_return + write_byte(w, DIGITS_LOWER[r>>4 & 0xf], &n) or_return + write_byte(w, DIGITS_LOWER[r & 0xf], &n) or_return + } + return + } switch r { case '\a': write_string(w, `\a`, &n) or_return case '\b': write_string(w, `\b`, &n) or_return From 1e0902677f905e752b42e2f48dcda53141b78eee Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 17:29:11 +0100 Subject: [PATCH 034/162] Multithread min dep set by removing the set itself --- src/checker.cpp | 197 +++++++++++++++++----------------- src/checker.hpp | 7 +- src/entity.cpp | 1 + src/error.cpp | 16 +-- src/llvm_backend.cpp | 17 +-- src/llvm_backend_proc.cpp | 3 +- src/llvm_backend_stmt.cpp | 6 +- src/main.cpp | 3 +- src/name_canonicalization.cpp | 61 ++++++++++- src/name_canonicalization.hpp | 2 + src/ptr_set.cpp | 38 +++++++ src/threading.cpp | 38 +++++++ src/types.cpp | 1 + 13 files changed, 265 insertions(+), 125 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index 26430359c..b3a702cbd 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1712,7 +1712,7 @@ gb_internal void check_remove_expr_info(CheckerContext *c, Ast *e) { } gb_internal isize type_info_index(CheckerInfo *info, TypeInfoPair pair, bool error_on_failure) { - mutex_lock(&info->minimum_dependency_type_info_mutex); + rw_mutex_shared_lock(&info->minimum_dependency_type_info_mutex); isize entry_index = -1; u64 hash = pair.hash; @@ -1720,7 +1720,7 @@ gb_internal isize type_info_index(CheckerInfo *info, TypeInfoPair pair, bool err if (found_entry_index) { entry_index = *found_entry_index; } - mutex_unlock(&info->minimum_dependency_type_info_mutex); + rw_mutex_shared_unlock(&info->minimum_dependency_type_info_mutex); if (error_on_failure && entry_index < 0) { compiler_error("Type_Info for '%s' could not be found", type_to_string(pair.type)); @@ -2377,11 +2377,8 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { return; } - { - MUTEX_GUARD(&c->info.minimum_dependency_type_info_mutex); - if (type_set_update(&c->info.min_dep_type_info_set, t)) { - return; - } + if (type_set_update_with_mutex(&c->info.min_dep_type_info_set, t, &c->info.min_dep_type_info_set_mutex)) { + return; } // Add nested types @@ -2555,13 +2552,15 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { } } +gb_internal void add_dependency_to_set_threaded(Checker *c, Entity *entity); + +gb_global std::atomic global_checker_ptr; + gb_internal void add_dependency_to_set(Checker *c, Entity *entity) { if (entity == nullptr) { return; } - CheckerInfo *info = &c->info; - if (entity->type != nullptr && is_type_polymorphic(entity->type)) { DeclInfo *decl = decl_info_of_entity(entity); @@ -2570,11 +2569,8 @@ gb_internal void add_dependency_to_set(Checker *c, Entity *entity) { } } - { - MUTEX_GUARD(&info->minimum_dependency_type_info_mutex); - if (ptr_set_update(&info->minimum_dependency_set, entity)) { - return; - } + if (entity->min_dep_count.fetch_add(1, std::memory_order_relaxed) > 0) { + return; } DeclInfo *decl = decl_info_of_entity(entity); @@ -2584,46 +2580,45 @@ gb_internal void add_dependency_to_set(Checker *c, Entity *entity) { for (TypeInfoPair const tt : decl->type_info_deps) { add_min_dep_type_info(c, tt.type); } + for (Entity *e : decl->deps) { + switch (e->kind) { + case Entity_Procedure: + if (e->Procedure.is_foreign) { + Entity *fl = e->Procedure.foreign_library; + if (fl != nullptr) { + GB_ASSERT_MSG(fl->kind == Entity_LibraryName && + (fl->flags&EntityFlag_Used), + "%.*s", LIT(entity->token.string)); + add_dependency_to_set(c, fl); + } + } + break; + case Entity_Variable: + if (e->Variable.is_foreign) { + Entity *fl = e->Variable.foreign_library; + if (fl != nullptr) { + GB_ASSERT_MSG(fl->kind == Entity_LibraryName && + (fl->flags&EntityFlag_Used), + "%.*s", LIT(entity->token.string)); + add_dependency_to_set(c, fl); + } + } + break; + } + } for (Entity *e : decl->deps) { add_dependency_to_set(c, e); - if (e->kind == Entity_Procedure && e->Procedure.is_foreign) { - Entity *fl = e->Procedure.foreign_library; - if (fl != nullptr) { - GB_ASSERT_MSG(fl->kind == Entity_LibraryName && - (fl->flags&EntityFlag_Used), - "%.*s", LIT(entity->token.string)); - add_dependency_to_set(c, fl); - } - } else if (e->kind == Entity_Variable && e->Variable.is_foreign) { - Entity *fl = e->Variable.foreign_library; - if (fl != nullptr) { - GB_ASSERT_MSG(fl->kind == Entity_LibraryName && - (fl->flags&EntityFlag_Used), - "%.*s", LIT(entity->token.string)); - add_dependency_to_set(c, fl); - } - } } + } - -struct AddDependecyToSetWorkerData { - Checker *c; - Entity *entity; -}; - -gb_internal void add_dependency_to_set_threaded(Checker *c, Entity *entity); - gb_internal WORKER_TASK_PROC(add_dependency_to_set_worker) { - AddDependecyToSetWorkerData *wd = cast(AddDependecyToSetWorkerData *)data; - Checker *c = wd->c; - Entity *entity = wd->entity; + Checker *c = global_checker_ptr.load(std::memory_order_relaxed); + Entity *entity = cast(Entity *)data; if (entity == nullptr) { return 0; } - CheckerInfo *info = &c->info; - if (entity->type != nullptr && is_type_polymorphic(entity->type)) { DeclInfo *decl = decl_info_of_entity(entity); @@ -2632,11 +2627,8 @@ gb_internal WORKER_TASK_PROC(add_dependency_to_set_worker) { } } - { - MUTEX_GUARD(&info->minimum_dependency_type_info_mutex); - if (ptr_set_update(&info->minimum_dependency_set, entity)) { - return 0; - } + if (entity->min_dep_count.fetch_add(1, std::memory_order_relaxed) > 0) { + return 0; } DeclInfo *decl = decl_info_of_entity(entity); @@ -2648,25 +2640,36 @@ gb_internal WORKER_TASK_PROC(add_dependency_to_set_worker) { } for (Entity *e : decl->deps) { - add_dependency_to_set(c, e); - if (e->kind == Entity_Procedure && e->Procedure.is_foreign) { - Entity *fl = e->Procedure.foreign_library; - if (fl != nullptr) { - GB_ASSERT_MSG(fl->kind == Entity_LibraryName && - (fl->flags&EntityFlag_Used), - "%.*s", LIT(entity->token.string)); - add_dependency_to_set_threaded(c, fl); + switch (e->kind) { + case Entity_Procedure: + if (e->Procedure.is_foreign) { + Entity *fl = e->Procedure.foreign_library; + if (fl != nullptr) { + GB_ASSERT_MSG(fl->kind == Entity_LibraryName && + (fl->flags&EntityFlag_Used), + "%.*s", LIT(entity->token.string)); + add_dependency_to_set_threaded(c, fl); + } } - } else if (e->kind == Entity_Variable && e->Variable.is_foreign) { - Entity *fl = e->Variable.foreign_library; - if (fl != nullptr) { - GB_ASSERT_MSG(fl->kind == Entity_LibraryName && - (fl->flags&EntityFlag_Used), - "%.*s", LIT(entity->token.string)); - add_dependency_to_set_threaded(c, fl); + break; + case Entity_Variable: + if (e->Variable.is_foreign) { + Entity *fl = e->Variable.foreign_library; + if (fl != nullptr) { + GB_ASSERT_MSG(fl->kind == Entity_LibraryName && + (fl->flags&EntityFlag_Used), + "%.*s", LIT(entity->token.string)); + add_dependency_to_set_threaded(c, fl); + } } + break; } } + + for (Entity *e : decl->deps) { + add_dependency_to_set_threaded(c, e); + } + return 0; } @@ -2675,11 +2678,7 @@ gb_internal void add_dependency_to_set_threaded(Checker *c, Entity *entity) { if (entity == nullptr) { return; } - - AddDependecyToSetWorkerData *wd = gb_alloc_item(temporary_allocator(), AddDependecyToSetWorkerData); - wd->c = c; - wd->entity = entity; - thread_pool_add_task(add_dependency_to_set_worker, wd); + thread_pool_add_task(add_dependency_to_set_worker, entity); } @@ -2732,27 +2731,35 @@ gb_internal void collect_testing_procedures_of_package(Checker *c, AstPackage *p } gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *start) { + // auto const &add_to_set = add_dependency_to_set; + auto const &add_to_set = add_dependency_to_set_threaded; + + Scope *builtin_scope = builtin_pkg->scope; for_array(i, c->info.definitions) { Entity *e = c->info.definitions[i]; - if (e->scope == builtin_pkg->scope) { + if (e->scope == builtin_scope) { if (e->type == nullptr) { - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); + } + } else if (e->kind == Entity_Procedure) { + if (e->Procedure.is_export) { + add_to_set(c, e); + } + } else if (e->kind == Entity_Variable) { + if (e->Variable.is_export) { + add_to_set(c, e); } - } else if (e->kind == Entity_Procedure && e->Procedure.is_export) { - add_dependency_to_set_threaded(c, e); - } else if (e->kind == Entity_Variable && e->Variable.is_export) { - add_dependency_to_set_threaded(c, e); } } for (Entity *e; mpsc_dequeue(&c->info.required_foreign_imports_through_force_queue, &e); /**/) { array_add(&c->info.required_foreign_imports_through_force, e); - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); } for (Entity *e; mpsc_dequeue(&c->info.required_global_variable_queue, &e); /**/) { e->flags |= EntityFlag_Used; - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); } for_array(i, c->info.entities) { @@ -2760,16 +2767,16 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st switch (e->kind) { case Entity_Variable: if (e->Variable.is_export) { - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); } else if (e->flags & EntityFlag_Require) { - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); } break; case Entity_Procedure: if (e->Procedure.is_export) { - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); } else if (e->flags & EntityFlag_Require) { - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); } if (e->flags & EntityFlag_Init) { Type *t = base_type(e->type); @@ -2809,7 +2816,7 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st if (is_init) { - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); array_add(&c->info.init_procedures, e); } } else if (e->flags & EntityFlag_Fini) { @@ -2844,7 +2851,7 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st } if (is_fini) { - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); array_add(&c->info.fini_procedures, e); } } @@ -2861,7 +2868,7 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st Entity *e = entry.value; if (e != nullptr) { e->flags |= EntityFlag_Used; - add_dependency_to_set_threaded(c, e); + add_to_set(c, e); } } @@ -2876,16 +2883,11 @@ gb_internal void generate_minimum_dependency_set_internal(Checker *c, Entity *st } } else if (start != nullptr) { start->flags |= EntityFlag_Used; - add_dependency_to_set_threaded(c, start); + add_to_set(c, start); } } gb_internal void generate_minimum_dependency_set(Checker *c, Entity *start) { - isize entity_count = c->info.entities.count; - isize min_dep_set_cap = next_pow2_isize(entity_count*4); // empirically determined factor - - ptr_set_init(&c->info.minimum_dependency_set, min_dep_set_cap); - #define FORCE_ADD_RUNTIME_ENTITIES(condition, ...) do { \ if (condition) { \ String entities[] = {__VA_ARGS__}; \ @@ -6267,8 +6269,10 @@ gb_internal void check_unchecked_bodies(Checker *c) { // use the `procs_to_check` array global_procedure_body_in_worker_queue = false; - for (Entity *e : c->info.minimum_dependency_set) { - check_procedure_later_from_entity(c, e, "check_unchecked_bodies"); + for (Entity *e : c->info.entities) { + if (e->min_dep_count.load(std::memory_order_relaxed) > 0) { + check_procedure_later_from_entity(c, e, "check_unchecked_bodies"); + } } if (!global_procedure_body_in_worker_queue) { @@ -7042,6 +7046,7 @@ gb_internal void check_merge_queues_into_arrays(Checker *c) { } check_add_entities_from_queues(c); check_add_definitions_from_queues(c); + thread_pool_wait(); } gb_internal GB_COMPARE_PROC(init_procedures_cmp) { @@ -7100,7 +7105,7 @@ gb_internal void add_type_info_for_type_definitions(Checker *c) { Entity *e = c->info.definitions[i]; if (e->kind == Entity_TypeName && e->type != nullptr && is_type_typed(e->type)) { i64 align = type_align_of(e->type); - if (align > 0 && ptr_set_exists(&c->info.minimum_dependency_set, e)) { + if (align > 0 && e->min_dep_count.load(std::memory_order_relaxed) > 0) { add_type_info_type(&c->builtin_ctx, e->type); } } @@ -7170,6 +7175,8 @@ gb_internal void check_update_dependency_tree_for_procedures(Checker *c) { gb_internal void check_parsed_files(Checker *c) { + global_checker_ptr.store(c, std::memory_order_relaxed); + TIME_SECTION("map full filepaths to scope"); add_type_info_type(&c->builtin_ctx, t_invalid); @@ -7312,11 +7319,9 @@ gb_internal void check_parsed_files(Checker *c) { check_unchecked_bodies(c); TIME_SECTION("check #soa types"); - check_merge_queues_into_arrays(c); - thread_pool_wait(); - TIME_SECTION("update minimum dependency set"); + TIME_SECTION("update minimum dependency set again"); generate_minimum_dependency_set_internal(c, c->info.entry_point); // NOTE(laytan): has to be ran after generate_minimum_dependency_set, diff --git a/src/checker.hpp b/src/checker.hpp index 1da46b74a..8b4d61ee2 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -449,11 +449,10 @@ struct CheckerInfo { Scope * init_scope; Entity * entry_point; - BlockingMutex minimum_dependency_set_mutex; - PtrSet minimum_dependency_set; - - BlockingMutex minimum_dependency_type_info_mutex; + RwMutex minimum_dependency_type_info_mutex; PtrMap min_dep_type_info_index_map; + + RWSpinLock min_dep_type_info_set_mutex; TypeSet min_dep_type_info_set; Array type_info_types_hash_map; // 2 * type_info_types.count diff --git a/src/entity.cpp b/src/entity.cpp index 6c0aa6ace..5ca3fa916 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -164,6 +164,7 @@ struct Entity { u64 id; std::atomic flags; std::atomic state; + std::atomic min_dep_count; Token token; Scope * scope; Type * type; diff --git a/src/error.cpp b/src/error.cpp index 006d5ae8d..10bf1caf5 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -86,7 +86,7 @@ gb_internal char *token_pos_to_string(TokenPos const &pos); gb_internal bool set_file_path_string(i32 index, String const &path) { bool ok = false; GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.path_mutex); + // mutex_lock(&global_error_collector.path_mutex); mutex_lock(&global_files_mutex); if (index >= global_file_path_strings.count) { @@ -99,14 +99,14 @@ gb_internal bool set_file_path_string(i32 index, String const &path) { } mutex_unlock(&global_files_mutex); - mutex_unlock(&global_error_collector.path_mutex); + // mutex_unlock(&global_error_collector.path_mutex); return ok; } gb_internal bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { bool ok = false; GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.path_mutex); + // mutex_lock(&global_error_collector.path_mutex); mutex_lock(&global_files_mutex); if (index >= global_files.count) { @@ -118,13 +118,13 @@ gb_internal bool thread_safe_set_ast_file_from_id(i32 index, AstFile *file) { ok = true; } mutex_unlock(&global_files_mutex); - mutex_unlock(&global_error_collector.path_mutex); + // mutex_unlock(&global_error_collector.path_mutex); return ok; } gb_internal String get_file_path_string(i32 index) { GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.path_mutex); + // mutex_lock(&global_error_collector.path_mutex); mutex_lock(&global_files_mutex); String path = {}; @@ -133,13 +133,13 @@ gb_internal String get_file_path_string(i32 index) { } mutex_unlock(&global_files_mutex); - mutex_unlock(&global_error_collector.path_mutex); + // mutex_unlock(&global_error_collector.path_mutex); return path; } gb_internal AstFile *thread_safe_get_ast_file_from_id(i32 index) { GB_ASSERT(index >= 0); - mutex_lock(&global_error_collector.path_mutex); + // mutex_lock(&global_error_collector.path_mutex); mutex_lock(&global_files_mutex); AstFile *file = nullptr; @@ -148,7 +148,7 @@ gb_internal AstFile *thread_safe_get_ast_file_from_id(i32 index) { } mutex_unlock(&global_files_mutex); - mutex_unlock(&global_error_collector.path_mutex); + // mutex_unlock(&global_error_collector.path_mutex); return file; } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index ff17e9c10..11b979774 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2097,8 +2097,6 @@ gb_internal GB_COMPARE_PROC(llvm_global_entity_cmp) { } gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, CheckerInfo *info, bool do_threading) { - auto *min_dep_set = &info->minimum_dependency_set; - for (Entity *e : info->entities) { String name = e->token.string; Scope * scope = e->scope; @@ -2135,11 +2133,16 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker } } - if (!polymorphic_struct && !ptr_set_exists(min_dep_set, e)) { + if (!polymorphic_struct && e->min_dep_count.load(std::memory_order_relaxed) == 0) { // NOTE(bill): Nothing depends upon it so doesn't need to be built continue; } + // if (!polymorphic_struct && !ptr_set_exists(min_dep_set, e)) { + // // NOTE(bill): Nothing depends upon it so doesn't need to be built + // continue; + // } + lbModule *m = &gen->default_module; if (USE_SEPARATE_MODULES) { m = lb_module_of_entity(gen, e); @@ -2845,8 +2848,6 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { lbModule *default_module = &gen->default_module; CheckerInfo *info = gen->info; - auto *min_dep_set = &info->minimum_dependency_set; - switch (build_context.metrics.arch) { case TargetArch_amd64: case TargetArch_i386: @@ -3184,10 +3185,14 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { continue; } - if (!ptr_set_exists(min_dep_set, e)) { + if (e->min_dep_count.load(std::memory_order_relaxed) == 0) { continue; } + // if (!ptr_set_exists(min_dep_set, e)) { + // continue; + // } + DeclInfo *decl = decl_info_of_entity(e); if (decl == nullptr) { continue; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index f2e6662c8..06829efab 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -798,9 +798,8 @@ gb_internal void lb_end_procedure_body(lbProcedure *p) { gb_internal void lb_build_nested_proc(lbProcedure *p, AstProcLit *pd, Entity *e) { GB_ASSERT(pd->body != nullptr); lbModule *m = p->module; - auto *min_dep_set = &m->info->minimum_dependency_set; - if (ptr_set_exists(min_dep_set, e) == false) { + if (e->min_dep_count.load(std::memory_order_relaxed) == 0) { // NOTE(bill): Nothing depends upon it so doesn't need to be built return; } diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 5481ca447..590920b59 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -3,8 +3,6 @@ gb_internal void lb_build_constant_value_decl(lbProcedure *p, AstValueDecl *vd) return; } - auto *min_dep_set = &p->module->info->minimum_dependency_set; - for (Ast *ident : vd->names) { GB_ASSERT(ident->kind == Ast_Ident); Entity *e = entity_of_node(ident); @@ -21,7 +19,7 @@ gb_internal void lb_build_constant_value_decl(lbProcedure *p, AstValueDecl *vd) } } - if (!polymorphic_struct && !ptr_set_exists(min_dep_set, e)) { + if (!polymorphic_struct && e->min_dep_count.load(std::memory_order_relaxed) == 0) { continue; } @@ -56,7 +54,7 @@ gb_internal void lb_build_constant_value_decl(lbProcedure *p, AstValueDecl *vd) if (gpd) { rw_mutex_shared_lock(&gpd->mutex); for (Entity *e : gpd->procs) { - if (!ptr_set_exists(min_dep_set, e)) { + if (e->min_dep_count.load(std::memory_order_relaxed) == 0) { continue; } DeclInfo *d = decl_info_of_entity(e); diff --git a/src/main.cpp b/src/main.cpp index db4dee080..184b1eaac 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3050,7 +3050,8 @@ gb_internal void print_show_unused(Checker *c) { if (e->token.string == "_") { continue; } - if (ptr_set_exists(&info->minimum_dependency_set, e)) { + + if (e->min_dep_count.load(std::memory_order_relaxed) > 0) { continue; } array_add(&unused, e); diff --git a/src/name_canonicalization.cpp b/src/name_canonicalization.cpp index 6a4538e26..8bacfabc6 100644 --- a/src/name_canonicalization.cpp +++ b/src/name_canonicalization.cpp @@ -57,10 +57,13 @@ gb_internal isize type_set__find(TypeSet *s, TypeInfoPair pair) { usize mask = s->capacity-1; usize hash_index = cast(usize)hash & mask; for (usize i = 0; i < s->capacity; i++) { - Type *key = s->keys[hash_index].type; - if (are_types_identical_unique_tuples(key, pair.type)) { + auto *e = &s->keys[hash_index]; + u64 hash = e->hash; + Type *key = e->type; + if (hash == pair.hash && + are_types_identical_unique_tuples(key, pair.type)) { return hash_index; - } else if (key == 0) { + } else if (key == nullptr) { return -1; } hash_index = (hash_index+1)&mask; @@ -164,6 +167,48 @@ gb_internal bool type_set_update(TypeSet *s, Type *ptr) { // returns true if it return type_set_update(s, pair); } +gb_internal bool type_set_update_with_mutex(TypeSet *s, TypeInfoPair pair, RWSpinLock *m) { // returns true if it previously existsed + rwlock_acquire_upgrade(m); + if (type_set_exists(s, pair)) { + rwlock_release_upgrade(m); + return true; + } + + rwlock_release_upgrade_and_acquire_write(m); + defer (rwlock_release_write(m)); + + if (s->keys == nullptr) { + type_set_init(s); + } else if (type_set__full(s)) { + type_set_grow(s); + } + GB_ASSERT(s->count < s->capacity); + GB_ASSERT(s->capacity >= 0); + + usize mask = s->capacity-1; + usize hash = cast(usize)pair.hash; + usize hash_index = (cast(usize)hash) & mask; + GB_ASSERT(hash_index < s->capacity); + for (usize i = 0; i < s->capacity; i++) { + TypeInfoPair *key = &s->keys[hash_index]; + GB_ASSERT(!are_types_identical_unique_tuples(key->type, pair.type)); + if (key->hash == TYPE_SET_TOMBSTONE || key->hash == 0) { + *key = pair; + s->count++; + return false; + } + hash_index = (hash_index+1)&mask; + } + + GB_PANIC("ptr set out of memory"); + return false; +} + +gb_internal bool type_set_update_with_mutex(TypeSet *s, Type *ptr, RWSpinLock *m) { // returns true if it previously existsed + TypeInfoPair pair = {ptr, type_hash_canonical_type(ptr)}; + return type_set_update_with_mutex(s, pair, m); +} + gb_internal Type *type_set_add(TypeSet *s, Type *ptr) { type_set_update(s, ptr); @@ -328,12 +373,20 @@ gb_internal u64 type_hash_canonical_type(Type *type) { if (type == nullptr) { return 0; } + u64 prev_hash = type->canonical_hash.load(std::memory_order_relaxed); + if (prev_hash != 0) { + return prev_hash; + } + u64 hash = fnv64a(nullptr, 0); TypeWriter w = {}; type_writer_make_hasher(&w, &hash); write_type_to_canonical_string(&w, type); + hash = hash ? hash : 1; - return hash ? hash : 1; + type->canonical_hash.store(hash, std::memory_order_relaxed); + + return hash; } gb_internal String type_to_canonical_string(gbAllocator allocator, Type *type) { diff --git a/src/name_canonicalization.hpp b/src/name_canonicalization.hpp index 304aff42e..00b450fbe 100644 --- a/src/name_canonicalization.hpp +++ b/src/name_canonicalization.hpp @@ -102,6 +102,8 @@ gb_internal Type *type_set_add (TypeSet *s, Type *ptr); gb_internal Type *type_set_add (TypeSet *s, TypeInfoPair pair); gb_internal bool type_set_update (TypeSet *s, Type *ptr); // returns true if it previously existed gb_internal bool type_set_update (TypeSet *s, TypeInfoPair pair); // returns true if it previously existed +gb_internal bool type_set_update_with_mutex(TypeSet *s, TypeInfoPair pair, RWSpinLock *m); +gb_internal bool type_set_update_with_mutex(TypeSet *s, Type *ptr, RWSpinLock *m); gb_internal bool type_set_exists (TypeSet *s, Type *ptr); gb_internal void type_set_remove (TypeSet *s, Type *ptr); gb_internal void type_set_clear (TypeSet *s); diff --git a/src/ptr_set.cpp b/src/ptr_set.cpp index 5097e2bb6..06c1e4a58 100644 --- a/src/ptr_set.cpp +++ b/src/ptr_set.cpp @@ -134,6 +134,44 @@ gb_internal bool ptr_set_update(PtrSet *s, T ptr) { // returns true if it pre return false; } +template +gb_internal bool ptr_set_update_with_mutex(PtrSet *s, T ptr, RWSpinLock *m) { // returns true if it previously existsed + rwlock_acquire_upgrade(m); + if (ptr_set_exists(s, ptr)) { + rwlock_release_upgrade(m); + return true; + } + + rwlock_release_upgrade_and_acquire_write(m); + defer (rwlock_release_write(m)); + + if (s->keys == nullptr) { + ptr_set_init(s); + } else if (ptr_set__full(s)) { + ptr_set_grow(s); + } + GB_ASSERT(s->count < s->capacity); + GB_ASSERT(s->capacity >= 0); + + usize mask = s->capacity-1; + u32 hash = ptr_map_hash_key(ptr); + usize hash_index = (cast(usize)hash) & mask; + GB_ASSERT(hash_index < s->capacity); + for (usize i = 0; i < s->capacity; i++) { + T *key = &s->keys[hash_index]; + GB_ASSERT(*key != ptr); + if (*key == (T)PtrSet::TOMBSTONE || *key == 0) { + *key = ptr; + s->count++; + return false; + } + hash_index = (hash_index+1)&mask; + } + + GB_PANIC("ptr set out of memory"); + return false; +} + template gb_internal T ptr_set_add(PtrSet *s, T ptr) { ptr_set_update(s, ptr); diff --git a/src/threading.cpp b/src/threading.cpp index a0d1c4049..f1d9264e3 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -448,6 +448,44 @@ gb_internal void semaphore_wait(Semaphore *s) { } #endif +static const int RWLOCK_WRITER = 1; +static const int RWLOCK_UPGRADED = 2; +static const int RWLOCK_READER = 4; +struct RWSpinLock { + Futex bits; +}; + +void rwlock_release_write(RWSpinLock *l) { + l->bits.fetch_and(~(RWLOCK_WRITER | RWLOCK_UPGRADED), std::memory_order_release); + futex_signal(&l->bits); +} + +bool rwlock_try_acquire_upgrade(RWSpinLock *l) { + int value = l->bits.fetch_or(RWLOCK_UPGRADED, std::memory_order_acquire); + return (value & (RWLOCK_UPGRADED | RWLOCK_WRITER)) == 0; +} + +void rwlock_acquire_upgrade(RWSpinLock *l) { + while (!rwlock_try_acquire_upgrade(l)) { + futex_wait(&l->bits, RWLOCK_UPGRADED); + } +} +void rwlock_release_upgrade(RWSpinLock *l) { + l->bits.fetch_add(-RWLOCK_UPGRADED, std::memory_order_acq_rel); +} + +bool rwlock_try_release_upgrade_and_acquire_write(RWSpinLock *l) { + int expect = RWLOCK_UPGRADED; + return l->bits.compare_exchange_strong(expect, RWLOCK_WRITER, std::memory_order_acq_rel); +} + +void rwlock_release_upgrade_and_acquire_write(RWSpinLock *l) { + while (!rwlock_try_release_upgrade_and_acquire_write(l)) { + futex_wait(&l->bits, RWLOCK_WRITER); + } +} + + struct Parker { Futex state; }; diff --git a/src/types.cpp b/src/types.cpp index 6b94b1690..44f9394c7 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -334,6 +334,7 @@ struct Type { // NOTE(bill): These need to be at the end to not affect the unionized data std::atomic cached_size; std::atomic cached_align; + std::atomic canonical_hash; std::atomic flags; // TypeFlag bool failure; }; From af37ba76c1b50c6f4fbe7045446ca645ed604d3e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 18:02:02 +0100 Subject: [PATCH 035/162] Use arena in `calculate_global_init_order` --- src/checker.cpp | 136 +++++++++++++++++++++++++----------------- src/common_memory.cpp | 7 +++ 2 files changed, 88 insertions(+), 55 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index b3a702cbd..0b54a3bbb 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -3006,22 +3006,37 @@ gb_internal bool is_entity_a_dependency(Entity *e) { return false; } -gb_internal Array generate_entity_dependency_graph(CheckerInfo *info, gbAllocator allocator) { - PtrMap M = {}; - map_init(&M, info->entities.count); - defer (map_destroy(&M)); +gb_internal Array generate_entity_dependency_graph(CheckerInfo *info, Arena *arena) { + PtrMap M_procs = {}; + PtrMap M_vars = {}; + PtrMap M_other = {}; + + map_init(&M_procs, info->entities.count); + defer (map_destroy(&M_procs)); + + map_init(&M_vars, info->entities.count); + defer (map_destroy(&M_vars)); + + map_init(&M_other, info->entities.count); + defer (map_destroy(&M_other)); + for_array(i, info->entities) { Entity *e = info->entities[i]; - if (is_entity_a_dependency(e)) { - EntityGraphNode *n = gb_alloc_item(allocator, EntityGraphNode); - n->entity = e; - map_set(&M, e, n); + if (!is_entity_a_dependency(e)) { + continue; + } + EntityGraphNode *n = arena_alloc_item(arena); + n->entity = e; + switch (e->kind) { + case Entity_Procedure: map_set(&M_procs, e, n); break; + case Entity_Variable: map_set(&M_vars, e, n); break; + default: map_set(&M_other, e, n); break; } } TIME_SECTION("generate_entity_dependency_graph: Calculate edges for graph M - Part 1"); // Calculate edges for graph M - for (auto const &entry : M) { + for (auto const &entry : M_procs) { EntityGraphNode *n = entry.value; Entity *e = n->entity; @@ -3033,56 +3048,70 @@ gb_internal Array generate_entity_dependency_graph(CheckerInf continue; } GB_ASSERT(dep != nullptr); - if (is_entity_a_dependency(dep)) { - EntityGraphNode *m = map_must_get(&M, dep); - entity_graph_node_set_add(&n->succ, m); - entity_graph_node_set_add(&m->pred, n); + if (!is_entity_a_dependency(dep)) { + continue; } + EntityGraphNode *m = nullptr; + + switch (dep->kind) { + case Entity_Procedure: m = map_must_get(&M_procs, dep); break; + case Entity_Variable: m = map_must_get(&M_vars, dep); break; + default: m = map_must_get(&M_other, dep); break; + } + entity_graph_node_set_add(&n->succ, m); + entity_graph_node_set_add(&m->pred, n); } } - TIME_SECTION("generate_entity_dependency_graph: Calculate edges for graph M - Part 2"); - auto G = array_make(allocator, 0, M.count); + TIME_SECTION("generate_entity_dependency_graph: Calculate edges for graph M - Part 2a (init)"); - for (auto const &m_entry : M) { - auto *e = m_entry.key; + auto G = array_make(arena_allocator(arena), 0, M_procs.count + M_vars.count + M_other.count); + + TIME_SECTION("generate_entity_dependency_graph: Calculate edges for graph M - Part 2b (procs)"); + + for (auto const &m_entry : M_procs) { EntityGraphNode *n = m_entry.value; - if (e->kind == Entity_Procedure) { - // Connect each pred 'p' of 'n' with each succ 's' and from - // the procedure node - for (EntityGraphNode *p : n->pred) { - // Ignore self-cycles - if (p != n) { - // Each succ 's' of 'n' becomes a succ of 'p', and - // each pred 'p' of 'n' becomes a pred of 's' - for (EntityGraphNode *s : n->succ) { - // Ignore self-cycles - if (s != n) { - if (p->entity->kind == Entity_Procedure && - s->entity->kind == Entity_Procedure) { - // NOTE(bill, 2020-11-15): Only care about variable initialization ordering - // TODO(bill): This is probably wrong!!!! - continue; - } - // IMPORTANT NOTE/TODO(bill, 2020-11-15): These three calls take the majority of the - // the time to process - entity_graph_node_set_add(&p->succ, s); - entity_graph_node_set_add(&s->pred, p); - // Remove edge to 'n' - entity_graph_node_set_remove(&s->pred, n); - } - } - - // Remove edge to 'n' - entity_graph_node_set_remove(&p->succ, n); - } + // Connect each pred 'p' of 'n' with each succ 's' and from + // the procedure node + for (EntityGraphNode *p : n->pred) { + // Ignore self-cycles + if (p == n) { + continue; } - } else if (e->kind == Entity_Variable) { - array_add(&G, n); + // Each succ 's' of 'n' becomes a succ of 'p', and + // each pred 'p' of 'n' becomes a pred of 's' + for (EntityGraphNode *s : n->succ) { + // Ignore self-cycles + if (s == n) { + continue; + } + if (p->entity->kind == Entity_Procedure && + s->entity->kind == Entity_Procedure) { + // NOTE(bill, 2020-11-15): Only care about variable initialization ordering + // TODO(bill): This is probably wrong!!!! + continue; + } + // IMPORTANT NOTE/TODO(bill, 2020-11-15): These three calls take the majority of the + // the time to process + entity_graph_node_set_add(&p->succ, s); + entity_graph_node_set_add(&s->pred, p); + // Remove edge to 'n' + entity_graph_node_set_remove(&s->pred, n); + } + + // Remove edge to 'n' + entity_graph_node_set_remove(&p->succ, n); } } + TIME_SECTION("generate_entity_dependency_graph: Calculate edges for graph M - Part 2c (vars)"); + + for (auto const &m_entry : M_vars) { + EntityGraphNode *n = m_entry.value; + array_add(&G, n); + } + TIME_SECTION("generate_entity_dependency_graph: Dependency Count Checker"); for_array(i, G) { EntityGraphNode *n = G[i]; @@ -6012,13 +6041,10 @@ gb_internal void calculate_global_init_order(Checker *c) { CheckerInfo *info = &c->info; TIME_SECTION("calculate_global_init_order: generate entity dependency graph"); - Array dep_graph = generate_entity_dependency_graph(info, heap_allocator()); - defer ({ - for_array(i, dep_graph) { - entity_graph_node_destroy(dep_graph[i], heap_allocator()); - } - array_free(&dep_graph); - }); + Arena *arena = get_arena(ThreadArena_Temporary); + ArenaTempGuard arena_guard(arena); + + Array dep_graph = generate_entity_dependency_graph(info, arena); TIME_SECTION("calculate_global_init_order: priority queue create"); // NOTE(bill): Priority queue diff --git a/src/common_memory.cpp b/src/common_memory.cpp index 47b2796a9..9e0222bc4 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -113,6 +113,13 @@ gb_internal void *arena_alloc(Arena *arena, isize min_size, isize alignment) { return ptr; } + +template +gb_internal T *arena_alloc_item(Arena *arena) { + return cast(T *)arena_alloc(arena, gb_size_of(T), gb_align_of(T)); +} + + gb_internal void arena_free_all(Arena *arena) { while (arena->curr_block != nullptr) { MemoryBlock *free_block = arena->curr_block; From 21b1173076cec12f97c5779556509ef1b908c644 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 18:20:20 +0100 Subject: [PATCH 036/162] Minor clean up of permanent/temporary arena usage --- src/build_settings.cpp | 10 +++++----- src/bundle_command.cpp | 2 +- src/check_builtin.cpp | 26 ++++++++++++------------- src/check_expr.cpp | 15 +++++++------- src/check_stmt.cpp | 2 +- src/check_type.cpp | 24 +++++++++++------------ src/checker.cpp | 6 +++--- src/common_memory.cpp | 44 +++++++++++++++++++++++++++++++++++++++++- src/gb/gb.h | 24 ++++++++++++----------- 9 files changed, 98 insertions(+), 55 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 4bee0ad4e..0081fabee 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1126,7 +1126,7 @@ gb_internal String internal_odin_root_dir(void) { mutex_lock(&string_buffer_mutex); defer (mutex_unlock(&string_buffer_mutex)); - text = gb_alloc_array(permanent_allocator(), wchar_t, len+1); + text = permanent_alloc_array(len+1); GetModuleFileNameW(nullptr, text, cast(int)len); path = string16_to_string(heap_allocator(), make_string16(cast(u16 *)text, len)); @@ -1181,7 +1181,7 @@ gb_internal String internal_odin_root_dir(void) { mutex_lock(&string_buffer_mutex); defer (mutex_unlock(&string_buffer_mutex)); - text = gb_alloc_array(permanent_allocator(), u8, len + 1); + text = permanent_alloc_array(len + 1); gb_memmove(text, &path_buf[0], len); path = path_to_fullpath(heap_allocator(), make_string(text, len), nullptr); @@ -1233,7 +1233,7 @@ gb_internal String internal_odin_root_dir(void) { mutex_lock(&string_buffer_mutex); defer (mutex_unlock(&string_buffer_mutex)); - text = gb_alloc_array(permanent_allocator(), u8, len + 1); + text = permanent_alloc_array(len + 1); gb_memmove(text, &path_buf[0], len); path = path_to_fullpath(heap_allocator(), make_string(text, len), nullptr); @@ -1394,7 +1394,7 @@ gb_internal String internal_odin_root_dir(void) { mutex_lock(&string_buffer_mutex); defer (mutex_unlock(&string_buffer_mutex)); - text = gb_alloc_array(permanent_allocator(), u8, len + 1); + text = permanent_alloc_array(len + 1); gb_memmove(text, &path_buf[0], len); @@ -1429,7 +1429,7 @@ gb_internal String path_to_fullpath(gbAllocator a, String s, bool *ok_) { len = GetFullPathNameW(cast(wchar_t *)&string16[0], 0, nullptr, nullptr); if (len != 0) { - wchar_t *text = gb_alloc_array(permanent_allocator(), wchar_t, len+1); + wchar_t *text = permanent_alloc_array(len+1); GetFullPathNameW(cast(wchar_t *)&string16[0], len, text, nullptr); mutex_unlock(&fullpath_mutex); diff --git a/src/bundle_command.cpp b/src/bundle_command.cpp index cd0cd589f..7abd48104 100644 --- a/src/bundle_command.cpp +++ b/src/bundle_command.cpp @@ -83,7 +83,7 @@ i32 bundle_android(String original_init_directory) { return 1; } - int *dir_numbers = gb_alloc_array(temporary_allocator(), int, possible_valid_dirs.count); + int *dir_numbers = temporary_alloc_array(possible_valid_dirs.count); char buf[1024] = {}; for_array(i, possible_valid_dirs) { diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index a08382c9a..7e1567750 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -354,7 +354,7 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan } isize const arg_offset = 1; - auto param_types = slice_make(permanent_allocator(), ce->args.count-arg_offset); + auto param_types = permanent_slice_make(ce->args.count-arg_offset); param_types[0] = t_objc_id; param_types[1] = sel_type; @@ -462,7 +462,7 @@ gb_internal bool check_builtin_objc_procedure(CheckerContext *c, Operand *operan { // NOTE(harold): The last argument specified in the call is the handler proc, // any other arguments before it are capture by-copy arguments. - auto param_operands = slice_make(permanent_allocator(), ce->args.count); + auto param_operands = permanent_slice_make(ce->args.count); isize capture_arg_count = ce->args.count - 1; @@ -3554,7 +3554,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } case BuiltinProc_compress_values: { - Operand *ops = gb_alloc_array(temporary_allocator(), Operand, ce->args.count); + Operand *ops = temporary_alloc_array(ce->args.count); isize value_count = 0; @@ -3685,9 +3685,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As operand->mode = Addressing_Value; } else { Type *st = alloc_type_struct_complete(); - st->Struct.fields = slice_make(permanent_allocator(), value_count); - st->Struct.tags = gb_alloc_array(permanent_allocator(), String, value_count); - st->Struct.offsets = gb_alloc_array(permanent_allocator(), i64, value_count); + st->Struct.fields = permanent_slice_make(value_count); + st->Struct.tags = permanent_alloc_array(value_count); + st->Struct.offsets = permanent_alloc_array(value_count); Scope *scope = create_scope(c->info, nullptr); @@ -4387,7 +4387,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As elem = alloc_type_struct(); elem->Struct.scope = s; elem->Struct.fields = slice_from_array(fields); - elem->Struct.tags = gb_alloc_array(permanent_allocator(), String, fields.count); + elem->Struct.tags = permanent_alloc_array(fields.count); elem->Struct.node = dummy_node_struct; type_set_offsets(elem); wait_signal_set(&elem->Struct.fields_wait_signal); @@ -4419,7 +4419,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As gb_string_free(s); return false; } - auto types = slice_make(permanent_allocator(), t->Struct.fields.count-1); + auto types = permanent_slice_make(t->Struct.fields.count-1); for_array(i, types) { Entity *f = t->Struct.fields[i]; GB_ASSERT(f->type->kind == Type_MultiPointer); @@ -4755,8 +4755,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As if (is_type_array(elem)) { Type *old_array = base_type(elem); soa_struct = alloc_type_struct(); - soa_struct->Struct.fields = slice_make(heap_allocator(), cast(isize)old_array->Array.count); - soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, cast(isize)old_array->Array.count); + soa_struct->Struct.fields = permanent_slice_make(cast(isize)old_array->Array.count); + soa_struct->Struct.tags = permanent_alloc_array(cast(isize)old_array->Array.count); soa_struct->Struct.node = operand->expr; soa_struct->Struct.soa_kind = StructSoa_Fixed; soa_struct->Struct.soa_elem = elem; @@ -4788,8 +4788,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As Type *old_struct = base_type(elem); soa_struct = alloc_type_struct(); - soa_struct->Struct.fields = slice_make(heap_allocator(), old_struct->Struct.fields.count); - soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, old_struct->Struct.fields.count); + soa_struct->Struct.fields = permanent_slice_make(old_struct->Struct.fields.count); + soa_struct->Struct.tags = permanent_alloc_array(old_struct->Struct.fields.count); soa_struct->Struct.node = operand->expr; soa_struct->Struct.soa_kind = StructSoa_Fixed; soa_struct->Struct.soa_elem = elem; @@ -6181,7 +6181,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } Type *new_type = alloc_type_union(); - auto variants = slice_make(permanent_allocator(), bt->Union.variants.count); + auto variants = permanent_slice_make(bt->Union.variants.count); for_array(i, bt->Union.variants) { variants[i] = alloc_type_pointer(bt->Union.variants[i]); } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index e005b0bd0..84f1c6f0a 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -4639,7 +4639,6 @@ gb_internal ExactValue convert_exact_value_for_type(ExactValue v, Type *type) { } gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *target_type) { - // GB_ASSERT_NOT_NULL(target_type); if (target_type == nullptr || operand->mode == Addressing_Invalid || operand->mode == Addressing_Type || is_type_typed(operand->type) || @@ -4810,7 +4809,7 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar TEMPORARY_ALLOCATOR_GUARD(); isize count = t->Union.variants.count; - ValidIndexAndScore *valids = gb_alloc_array(temporary_allocator(), ValidIndexAndScore, count); + ValidIndexAndScore *valids = temporary_alloc_array(count); isize valid_count = 0; isize first_success_index = -1; for_array(i, t->Union.variants) { @@ -6257,7 +6256,7 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A } GB_ASSERT(ce->split_args); - auto visited = slice_make(temporary_allocator(), pt->param_count); + auto visited = temporary_slice_make(pt->param_count); auto ordered_operands = array_make(temporary_allocator(), pt->param_count); defer ({ for (Operand const &o : ordered_operands) { @@ -7254,7 +7253,7 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, } // Try to reduce the list further for `$T: typeid` like parameters - bool *possibly_ignore = gb_alloc_array(temporary_allocator(), bool, procs.count); + bool *possibly_ignore = temporary_alloc_array(procs.count); isize possibly_ignore_set = 0; if (true) { @@ -7342,7 +7341,7 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, } isize max_spaces = gb_max(max_name_length, max_type_length); - char *spaces = gb_alloc_array(temporary_allocator(), char, max_spaces+1); + char *spaces = temporary_alloc_array(max_spaces+1); for (isize i = 0; i < max_spaces; i++) { spaces[i] = ' '; } @@ -7706,7 +7705,7 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O } else { TEMPORARY_ALLOCATOR_GUARD(); - bool *visited = gb_alloc_array(temporary_allocator(), bool, param_count); + bool *visited = temporary_alloc_array(param_count); // LEAK(bill) ordered_operands = array_make(permanent_allocator(), param_count); @@ -8881,7 +8880,7 @@ gb_internal void add_constant_switch_case(CheckerContext *ctx, SeenMap *seen, Op isize count = multi_map_count(seen, key); if (count) { TEMPORARY_ALLOCATOR_GUARD(); - TypeAndToken *taps = gb_alloc_array(temporary_allocator(), TypeAndToken, count); + TypeAndToken *taps = temporary_alloc_array(count); multi_map_get_all(seen, key, taps); for (isize i = 0; i < count; i++) { @@ -10927,7 +10926,7 @@ gb_internal ExprKind check_selector_call_expr(CheckerContext *c, Operand *o, Ast } } - auto modified_args = slice_make(heap_allocator(), ce->args.count+1); + auto modified_args = permanent_slice_make(ce->args.count+1); modified_args[0] = first_arg; slice_copy(&modified_args, ce->args, 1); ce->args = modified_args; diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index ae88ff333..e03d4a7ae 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -1963,7 +1963,7 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) } auto rhs = slice_from_array(vals); - auto lhs = slice_make(temporary_allocator(), rhs.count); + auto lhs = temporary_slice_make(rhs.count); slice_copy(&lhs, rs->vals); isize addressable_index = cast(isize)is_map; diff --git a/src/check_type.cpp b/src/check_type.cpp index a104d6fc0..4c995588f 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1106,7 +1106,7 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, GB_ASSERT(fields.count <= bf->fields.count); - auto bit_offsets = slice_make(permanent_allocator(), fields.count); + auto bit_offsets = permanent_slice_make(fields.count); i64 curr_offset = 0; for_array(i, bit_sizes) { bit_offsets[i] = curr_offset; @@ -2707,7 +2707,7 @@ gb_internal Type *get_map_cell_type(Type *type) { // Padding exists Type *s = alloc_type_struct(); Scope *scope = create_scope(nullptr, nullptr); - s->Struct.fields = slice_make(permanent_allocator(), 2); + s->Struct.fields = permanent_slice_make(2); s->Struct.fields[0] = alloc_entity_field(scope, make_token_ident("v"), alloc_type_array(type, len), false, 0, EntityState_Resolved); s->Struct.fields[1] = alloc_entity_field(scope, make_token_ident("_"), alloc_type_array(t_u8, padding), false, 1, EntityState_Resolved); s->Struct.scope = scope; @@ -2732,7 +2732,7 @@ gb_internal void init_map_internal_debug_types(Type *type) { Type *metadata_type = alloc_type_struct(); Scope *metadata_scope = create_scope(nullptr, nullptr); - metadata_type->Struct.fields = slice_make(permanent_allocator(), 5); + metadata_type->Struct.fields = permanent_slice_make(5); metadata_type->Struct.fields[0] = alloc_entity_field(metadata_scope, make_token_ident("key"), key, false, 0, EntityState_Resolved); metadata_type->Struct.fields[1] = alloc_entity_field(metadata_scope, make_token_ident("value"), value, false, 1, EntityState_Resolved); metadata_type->Struct.fields[2] = alloc_entity_field(metadata_scope, make_token_ident("hash"), t_uintptr, false, 2, EntityState_Resolved); @@ -2750,7 +2750,7 @@ gb_internal void init_map_internal_debug_types(Type *type) { Scope *scope = create_scope(nullptr, nullptr); Type *debug_type = alloc_type_struct(); - debug_type->Struct.fields = slice_make(permanent_allocator(), 3); + debug_type->Struct.fields = permanent_slice_make(3); debug_type->Struct.fields[0] = alloc_entity_field(scope, make_token_ident("data"), metadata_type, false, 0, EntityState_Resolved); debug_type->Struct.fields[1] = alloc_entity_field(scope, make_token_ident("len"), t_int, false, 1, EntityState_Resolved); debug_type->Struct.fields[2] = alloc_entity_field(scope, make_token_ident("allocator"), t_allocator, false, 2, EntityState_Resolved); @@ -2983,13 +2983,13 @@ gb_internal bool complete_soa_type(Checker *checker, Type *t, bool wait_to_finis if (wait_to_finish) { wait_signal_until_available(&old_struct->Struct.fields_wait_signal); } else { - GB_ASSERT(old_struct->Struct.fields_wait_signal.futex.load()); + GB_ASSERT(old_struct->Struct.fields_wait_signal.futex.load() != 0); } field_count = old_struct->Struct.fields.count; - t->Struct.fields = slice_make(permanent_allocator(), field_count+extra_field_count); - t->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count); + t->Struct.fields = permanent_slice_make(field_count+extra_field_count); + t->Struct.tags = permanent_alloc_array(field_count+extra_field_count); auto const &add_entity = [](Scope *scope, Entity *entity) { @@ -3107,7 +3107,7 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e if (is_polymorphic) { field_count = 0; - soa_struct->Struct.fields = slice_make(permanent_allocator(), field_count+extra_field_count); + soa_struct->Struct.fields = permanent_slice_make(field_count+extra_field_count); soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count); soa_struct->Struct.soa_count = 0; @@ -3117,7 +3117,7 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e Type *old_array = base_type(elem); field_count = cast(isize)old_array->Array.count; - soa_struct->Struct.fields = slice_make(permanent_allocator(), field_count+extra_field_count); + soa_struct->Struct.fields = permanent_slice_make(field_count+extra_field_count); soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count); string_map_init(&scope->elements, 8); @@ -3159,8 +3159,8 @@ gb_internal Type *make_soa_struct_internal(CheckerContext *ctx, Ast *array_typ_e if (old_struct->Struct.fields_wait_signal.futex.load()) { field_count = old_struct->Struct.fields.count; - soa_struct->Struct.fields = slice_make(permanent_allocator(), field_count+extra_field_count); - soa_struct->Struct.tags = gb_alloc_array(permanent_allocator(), String, field_count+extra_field_count); + soa_struct->Struct.fields = permanent_slice_make(field_count+extra_field_count); + soa_struct->Struct.tags = permanent_alloc_array(field_count+extra_field_count); for_array(i, old_struct->Struct.fields) { Entity *old_field = old_struct->Struct.fields[i]; @@ -3355,7 +3355,7 @@ gb_internal void check_array_type_internal(CheckerContext *ctx, Ast *e, Type **t } } gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_type) { - GB_ASSERT_NOT_NULL(type); + GB_ASSERT(type != nullptr); if (e == nullptr) { *type = t_invalid; return true; diff --git a/src/checker.cpp b/src/checker.cpp index 0b54a3bbb..04e46d0e6 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -6177,7 +6177,7 @@ gb_internal bool check_proc_info(Checker *c, ProcInfo *pi, UntypedExprInfoMap *u switch (pi->decl->proc_checked_state.load()) { case ProcCheckedState_InProgress: if (e) { - GB_ASSERT(global_procedure_body_in_worker_queue.load()); + GB_ASSERT(global_procedure_body_in_worker_queue.load() != 0); } return false; case ProcCheckedState_Checked: @@ -6431,7 +6431,7 @@ gb_internal WORKER_TASK_PROC(check_proc_info_worker_proc) { gb_internal void check_init_worker_data(Checker *c) { u32 thread_count = cast(u32)global_thread_pool.threads.count; - check_procedure_bodies_worker_data = gb_alloc_array(permanent_allocator(), CheckProcedureBodyWorkerData, thread_count); + check_procedure_bodies_worker_data = permanent_alloc_array(thread_count); for (isize i = 0; i < thread_count; i++) { check_procedure_bodies_worker_data[i].c = c; @@ -6493,7 +6493,7 @@ gb_internal Type *tuple_to_pointers(Type *ot) { Type *t = alloc_type_tuple(); - t->Tuple.variables = slice_make(heap_allocator(), ot->Tuple.variables.count); + t->Tuple.variables = permanent_slice_make(ot->Tuple.variables.count); Scope *scope = nullptr; for_array(i, t->Tuple.variables) { diff --git a/src/common_memory.cpp b/src/common_memory.cpp index 9e0222bc4..0b95e1242 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -353,7 +353,7 @@ gb_internal gbAllocator arena_allocator(Arena *arena) { gb_internal GB_ALLOCATOR_PROC(arena_allocator_proc) { void *ptr = nullptr; Arena *arena = cast(Arena *)allocator_data; - GB_ASSERT_NOT_NULL(arena); + GB_ASSERT(arena != nullptr); switch (type) { case gbAllocation_Alloc: @@ -401,6 +401,48 @@ gb_internal Arena *get_arena(ThreadArenaKind kind) { } +template +gb_internal T *permanent_alloc_item() { + Arena *arena = get_arena(ThreadArena_Permanent); + return arena_alloc_item(arena); +} + +template +gb_internal T *permanent_alloc_array(isize count) { + Arena *arena = get_arena(ThreadArena_Permanent); + return cast(T *)arena_alloc(arena, gb_size_of(T)*count, gb_align_of(T)); +} + +template +gb_internal Slice permanent_slice_make(isize count) { + Arena *arena = get_arena(ThreadArena_Permanent); + T *data = cast(T *)arena_alloc(arena, gb_size_of(T)*count, gb_align_of(T)); + return {data, count}; +} + +template +gb_internal T *temporary_alloc_item() { + Arena *arena = get_arena(ThreadArena_Temporary); + return arena_alloc_item(arena); +} + +template +gb_internal T *temporary_alloc_array(isize count) { + Arena *arena = get_arena(ThreadArena_Temporary); + return cast(T *)arena_alloc(arena, gb_size_of(T)*count, gb_align_of(T)); +} + +template +gb_internal Slice temporary_slice_make(isize count) { + Arena *arena = get_arena(ThreadArena_Temporary); + T *data = cast(T *)arena_alloc(arena, gb_size_of(T)*count, gb_align_of(T)); + return {data, count}; +} + + + + + gb_internal GB_ALLOCATOR_PROC(thread_arena_allocator_proc) { void *ptr = nullptr; diff --git a/src/gb/gb.h b/src/gb/gb.h index ffc40b8ca..c52f63cec 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -714,13 +714,15 @@ extern "C++" { } while (0) #endif + +#if defined(DISABLE_ASSERT) +#define GB_ASSERT(cond) gb_unused(cond) +#endif + #ifndef GB_ASSERT #define GB_ASSERT(cond) GB_ASSERT_MSG(cond, NULL) #endif -#ifndef GB_ASSERT_NOT_NULL -#define GB_ASSERT_NOT_NULL(ptr) GB_ASSERT_MSG((ptr) != NULL, #ptr " must not be NULL") -#endif // NOTE(bill): Things that shouldn't happen with a message! #ifndef GB_PANIC @@ -3719,7 +3721,7 @@ gb_inline i32 gb_strcmp(char const *s1, char const *s2) { } gb_inline char *gb_strcpy(char *dest, char const *source) { - GB_ASSERT_NOT_NULL(dest); + GB_ASSERT(dest != NULL); if (source) { char *str = dest; while (*source) *str++ = *source++; @@ -3729,7 +3731,7 @@ gb_inline char *gb_strcpy(char *dest, char const *source) { gb_inline char *gb_strncpy(char *dest, char const *source, isize len) { - GB_ASSERT_NOT_NULL(dest); + GB_ASSERT(dest != NULL); if (source) { char *str = dest; while (len > 0 && *source) { @@ -3746,7 +3748,7 @@ gb_inline char *gb_strncpy(char *dest, char const *source, isize len) { gb_inline isize gb_strlcpy(char *dest, char const *source, isize len) { isize result = 0; - GB_ASSERT_NOT_NULL(dest); + GB_ASSERT(dest != NULL); if (source) { char const *source_start = source; char *str = dest; @@ -5636,7 +5638,7 @@ gbFileContents gb_file_read_contents(gbAllocator a, b32 zero_terminate, char con void gb_file_free_contents(gbFileContents *fc) { if (fc == NULL || fc->size == 0) return; - GB_ASSERT_NOT_NULL(fc->data); + GB_ASSERT(fc->data != NULL); gb_free(fc->allocator, fc->data); fc->data = NULL; fc->size = 0; @@ -5648,7 +5650,7 @@ void gb_file_free_contents(gbFileContents *fc) { gb_inline b32 gb_path_is_absolute(char const *path) { b32 result = false; - GB_ASSERT_NOT_NULL(path); + GB_ASSERT(path != NULL); #if defined(GB_SYSTEM_WINDOWS) result == (gb_strlen(path) > 2) && gb_char_is_alpha(path[0]) && @@ -5663,7 +5665,7 @@ gb_inline b32 gb_path_is_relative(char const *path) { return !gb_path_is_absolut gb_inline b32 gb_path_is_root(char const *path) { b32 result = false; - GB_ASSERT_NOT_NULL(path); + GB_ASSERT(path != NULL); #if defined(GB_SYSTEM_WINDOWS) result = gb_path_is_absolute(path) && (gb_strlen(path) == 3); #else @@ -5674,14 +5676,14 @@ gb_inline b32 gb_path_is_root(char const *path) { gb_inline char const *gb_path_base_name(char const *path) { char const *ls; - GB_ASSERT_NOT_NULL(path); + GB_ASSERT(path != NULL); ls = gb_char_last_occurence(path, '/'); return (ls == NULL) ? path : ls+1; } gb_inline char const *gb_path_extension(char const *path) { char const *ld; - GB_ASSERT_NOT_NULL(path); + GB_ASSERT(path != NULL); ld = gb_char_last_occurence(path, '.'); return (ld == NULL) ? NULL : ld+1; } From a36a8722dc823c6fe143f7935e79467c6569bc00 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 19:30:32 +0100 Subject: [PATCH 037/162] Minimize more thread contention --- src/build_settings.cpp | 2 +- src/check_decl.cpp | 4 +-- src/check_expr.cpp | 16 +++++------- src/check_stmt.cpp | 5 ++-- src/check_type.cpp | 9 ++++--- src/checker.cpp | 58 ++++++++++++++++++++++-------------------- src/checker.hpp | 15 ++++++----- src/entity.cpp | 2 +- src/error.cpp | 11 ++++++++ src/threading.cpp | 10 ++++---- 10 files changed, 74 insertions(+), 58 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 0081fabee..0c88f3d13 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1163,7 +1163,7 @@ gb_internal String internal_odin_root_dir(void) { return global_module_path; } - auto path_buf = array_make(heap_allocator(), 300); + auto path_buf = array_make(temporary_allocator(), 300); defer (array_free(&path_buf)); len = 0; diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 7dd9db105..49731ad60 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1981,9 +1981,9 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de ast_node(bs, BlockStmt, body); + TEMPORARY_ALLOCATOR_GUARD(); Array using_entities = {}; - using_entities.allocator = heap_allocator(); - defer (array_free(&using_entities)); + using_entities.allocator = temporary_allocator(); { if (type->Proc.param_count > 0) { diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 84f1c6f0a..bdbccb4f8 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7516,11 +7516,10 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op return check_call_arguments_proc_group(c, operand, call); } - auto positional_operands = array_make(heap_allocator(), 0, positional_args.count); - auto named_operands = array_make(heap_allocator(), 0, 0); + TEMPORARY_ALLOCATOR_GUARD(); - defer (array_free(&positional_operands)); - defer (array_free(&named_operands)); + auto positional_operands = array_make(temporary_allocator(), 0, positional_args.count); + auto named_operands = array_make(temporary_allocator(), 0, 0); if (positional_args.count > 0) { Entity **lhs = nullptr; @@ -7623,11 +7622,10 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O { // NOTE(bill, 2019-10-26): Allow a cycle in the parameters but not in the fields themselves auto prev_type_path = c->type_path; - c->type_path = new_checker_type_path(); - defer ({ - destroy_checker_type_path(c->type_path); - c->type_path = prev_type_path; - }); + TEMPORARY_ALLOCATOR_GUARD(); + + c->type_path = new_checker_type_path(temporary_allocator()); + defer (c->type_path = prev_type_path); if (is_call_expr_field_value(ce)) { named_fields = true; diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index e03d4a7ae..ba9c08fed 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -2567,8 +2567,9 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { result_count = proc_type->Proc.results->Tuple.variables.count; } - auto operands = array_make(heap_allocator(), 0, 2*rs->results.count); - defer (array_free(&operands)); + TEMPORARY_ALLOCATOR_GUARD(); + + auto operands = array_make(temporary_allocator(), 0, 2*rs->results.count); check_unpack_arguments(ctx, result_entities, result_count, &operands, rs->results, UnpackFlag_AllowOk); diff --git a/src/check_type.cpp b/src/check_type.cpp index 4c995588f..e99909d6b 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -3512,8 +3512,9 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T case_ast_node(pt, PointerType, e); CheckerContext c = *ctx; - c.type_path = new_checker_type_path(); - defer (destroy_checker_type_path(c.type_path)); + + TEMPORARY_ALLOCATOR_GUARD(); + c.type_path = new_checker_type_path(temporary_allocator()); Type *elem = t_invalid; Operand o = {}; @@ -3747,8 +3748,8 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T gb_internal Type *check_type(CheckerContext *ctx, Ast *e) { CheckerContext c = *ctx; - c.type_path = new_checker_type_path(); - defer (destroy_checker_type_path(c.type_path)); + TEMPORARY_ALLOCATOR_GUARD(); + c.type_path = new_checker_type_path(temporary_allocator()); return check_type_expr(&c, e, nullptr); } diff --git a/src/checker.cpp b/src/checker.cpp index 04e46d0e6..c1d6302ad 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1441,7 +1441,8 @@ gb_internal void init_checker_info(CheckerInfo *i) { map_init(&i->objc_method_implementations); string_map_init(&i->load_file_cache); - array_init(&i->all_procedures, heap_allocator()); + array_init(&i->all_procedures, a); + mpsc_init(&i->all_procedures_queue, a); mpsc_init(&i->entity_queue, a); // 1<<20); mpsc_init(&i->definition_queue, a); //); // 1<<20); @@ -1475,6 +1476,10 @@ gb_internal void destroy_checker_info(CheckerInfo *i) { array_free(&i->required_foreign_imports_through_force); array_free(&i->defineables); + array_free(&i->all_procedures); + + mpsc_destroy(&i->all_procedures_queue); + mpsc_destroy(&i->entity_queue); mpsc_destroy(&i->definition_queue); mpsc_destroy(&i->required_global_variable_queue); @@ -1502,12 +1507,12 @@ gb_internal CheckerContext make_checker_context(Checker *c) { ctx.scope = builtin_pkg->scope; ctx.pkg = builtin_pkg; - ctx.type_path = new_checker_type_path(); + ctx.type_path = new_checker_type_path(heap_allocator()); ctx.type_level = 0; return ctx; } gb_internal void destroy_checker_context(CheckerContext *ctx) { - destroy_checker_type_path(ctx->type_path); + destroy_checker_type_path(ctx->type_path, heap_allocator()); } gb_internal bool add_curr_ast_file(CheckerContext *ctx, AstFile *file) { @@ -1758,7 +1763,7 @@ gb_internal void add_untyped(CheckerContext *c, Ast *expr, AddressingMode mode, check_set_expr_info(c, expr, mode, type, value); } -gb_internal void add_type_and_value(CheckerContext *ctx, Ast *expr, AddressingMode mode, Type *type, ExactValue const &value) { +gb_internal void add_type_and_value(CheckerContext *ctx, Ast *expr, AddressingMode mode, Type *type, ExactValue const &value, bool use_mutex) { if (expr == nullptr) { return; } @@ -1776,7 +1781,7 @@ gb_internal void add_type_and_value(CheckerContext *ctx, Ast *expr, AddressingMo mutex = &ctx->pkg->type_and_value_mutex; } - mutex_lock(mutex); + if (use_mutex) mutex_lock(mutex); Ast *prev_expr = nullptr; while (prev_expr != expr) { prev_expr = expr; @@ -1801,7 +1806,7 @@ gb_internal void add_type_and_value(CheckerContext *ctx, Ast *expr, AddressingMo break; }; } - mutex_unlock(mutex); + if (use_mutex) mutex_unlock(mutex); } gb_internal void add_entity_definition(CheckerInfo *i, Ast *identifier, Entity *entity) { @@ -2345,11 +2350,9 @@ gb_internal void check_procedure_later(Checker *c, ProcInfo *info) { } if (DEBUG_CHECK_ALL_PROCEDURES) { - MUTEX_GUARD_BLOCK(&c->info.all_procedures_mutex) { - GB_ASSERT(info != nullptr); - GB_ASSERT(info->decl != nullptr); - array_add(&c->info.all_procedures, info); - } + GB_ASSERT(info != nullptr); + GB_ASSERT(info->decl != nullptr); + mpsc_enqueue(&c->info.all_procedures_queue, info); } } @@ -3195,19 +3198,17 @@ gb_internal Type *find_type_in_pkg(CheckerInfo *info, String const &pkg, String return e->type; } -gb_internal CheckerTypePath *new_checker_type_path() { - gbAllocator a = heap_allocator(); - auto *tp = gb_alloc_item(a, CheckerTypePath); - array_init(tp, a, 0, 16); +gb_internal CheckerTypePath *new_checker_type_path(gbAllocator allocator) { + auto *tp = gb_alloc_item(allocator, CheckerTypePath); + array_init(tp, allocator, 0, 16); return tp; } -gb_internal void destroy_checker_type_path(CheckerTypePath *tp) { +gb_internal void destroy_checker_type_path(CheckerTypePath *tp, gbAllocator allocator) { array_free(tp); - gb_free(heap_allocator(), tp); + gb_free(allocator, tp); } - gb_internal void check_type_path_push(CheckerContext *c, Entity *e) { GB_ASSERT(c->type_path != nullptr); GB_ASSERT(e != nullptr); @@ -5283,9 +5284,10 @@ gb_internal void check_add_import_decl(CheckerContext *ctx, Ast *decl) { GB_ASSERT(scope->flags&ScopeFlag_Pkg); - if (ptr_set_update(&parent_scope->imported, scope)) { - // error(token, "Multiple import of the same file within this scope"); - } + ptr_set_add(&parent_scope->imported, scope); + // if (ptr_set_update(&parent_scope->imported, scope)) { + // // error(token, "Multiple import of the same file within this scope"); + // } String import_name = path_to_entity_name(id->import_name.string, id->fullpath, false); if (is_blank_ident(import_name)) { @@ -6041,10 +6043,10 @@ gb_internal void calculate_global_init_order(Checker *c) { CheckerInfo *info = &c->info; TIME_SECTION("calculate_global_init_order: generate entity dependency graph"); - Arena *arena = get_arena(ThreadArena_Temporary); - ArenaTempGuard arena_guard(arena); + Arena *temporary_arena = get_arena(ThreadArena_Temporary); + ArenaTempGuard temporary_arena_guard(temporary_arena); - Array dep_graph = generate_entity_dependency_graph(info, arena); + Array dep_graph = generate_entity_dependency_graph(info, temporary_arena); TIME_SECTION("calculate_global_init_order: priority queue create"); // NOTE(bill): Priority queue @@ -6321,8 +6323,9 @@ gb_internal void check_safety_all_procedures_for_unchecked(Checker *c) { defer (map_destroy(&untyped)); - for_array(i, c->info.all_procedures) { - ProcInfo *pi = c->info.all_procedures[i]; + array_reserve(&c->info.all_procedures, c->info.all_procedures_queue.count.load()); + + for (ProcInfo *pi = nullptr; mpsc_dequeue(&c->info.all_procedures_queue, &pi); /**/) { GB_ASSERT(pi != nullptr); GB_ASSERT(pi->decl != nullptr); Entity *e = pi->decl->entity; @@ -6337,6 +6340,8 @@ gb_internal void check_safety_all_procedures_for_unchecked(Checker *c) { consume_proc_info(c, pi, &untyped); } } + + array_add(&c->info.all_procedures, pi); } } @@ -7412,7 +7417,6 @@ gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("add untyped expression values"); - // Add untyped expression values for (UntypedExprInfo u = {}; mpsc_dequeue(&c->global_untyped_queue, &u); /**/) { GB_ASSERT(u.expr != nullptr && u.info != nullptr); if (is_type_typed(u.info->type)) { diff --git a/src/checker.hpp b/src/checker.hpp index 8b4d61ee2..5a40b10a0 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -309,11 +309,12 @@ struct EntityGraphNode; typedef PtrSet EntityGraphNodeSet; struct EntityGraphNode { - Entity * entity; // Procedure, Variable, Constant + Entity *entity; // Procedure, Variable, Constant + EntityGraphNodeSet pred; EntityGraphNodeSet succ; - isize index; // Index in array/queue - isize dep_count; + isize index; // Index in array/queue + isize dep_count; }; @@ -516,7 +517,7 @@ struct CheckerInfo { BlockingMutex load_file_mutex; StringMap load_file_cache; - BlockingMutex all_procedures_mutex; + MPSCQueue all_procedures_queue; Array all_procedures; BlockingMutex instrumentation_mutex; @@ -629,7 +630,7 @@ gb_internal void scope_lookup_parent (Scope *s, String const &name, Scope **s gb_internal Entity *scope_insert (Scope *s, Entity *entity); -gb_internal void add_type_and_value (CheckerContext *c, Ast *expression, AddressingMode mode, Type *type, ExactValue const &value); +gb_internal void add_type_and_value (CheckerContext *c, Ast *expression, AddressingMode mode, Type *type, ExactValue const &value, bool use_mutex=true); gb_internal ExprInfo *check_get_expr_info (CheckerContext *c, Ast *expr); gb_internal void add_untyped (CheckerContext *c, Ast *expression, AddressingMode mode, Type *basic_type, ExactValue const &value); gb_internal void add_entity_use (CheckerContext *c, Ast *identifier, Entity *entity); @@ -650,8 +651,8 @@ gb_internal void check_collect_entities(CheckerContext *c, Slice const &n gb_internal void check_collect_entities_from_when_stmt(CheckerContext *c, AstWhenStmt *ws); gb_internal void check_delayed_file_import_entity(CheckerContext *c, Ast *decl); -gb_internal CheckerTypePath *new_checker_type_path(); -gb_internal void destroy_checker_type_path(CheckerTypePath *tp); +gb_internal CheckerTypePath *new_checker_type_path(gbAllocator allocator); +gb_internal void destroy_checker_type_path(CheckerTypePath *tp, gbAllocator allocator); gb_internal void check_type_path_push(CheckerContext *c, Entity *e); gb_internal Entity *check_type_path_pop (CheckerContext *c); diff --git a/src/entity.cpp b/src/entity.cpp index 5ca3fa916..e07e882f3 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -348,7 +348,7 @@ gb_internal Entity *alloc_entity(EntityKind kind, Scope *scope, Token token, Typ entity->type = type; entity->id = 1 + global_entity_id.fetch_add(1); if (token.pos.file_id) { - entity->file = thread_safe_get_ast_file_from_id(token.pos.file_id); + entity->file = thread_unsafe_get_ast_file_from_id(token.pos.file_id); } return entity; } diff --git a/src/error.cpp b/src/error.cpp index 10bf1caf5..53bc01654 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -153,6 +153,17 @@ gb_internal AstFile *thread_safe_get_ast_file_from_id(i32 index) { } +// use AFTER PARSER +gb_internal AstFile *thread_unsafe_get_ast_file_from_id(i32 index) { + GB_ASSERT(index >= 0); + AstFile *file = nullptr; + if (index < global_files.count) { + file = global_files[index]; + } + return file; +} + + // NOTE: defined in build_settings.cpp gb_internal bool global_warnings_as_errors(void); diff --git a/src/threading.cpp b/src/threading.cpp index f1d9264e3..a35176ce6 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -448,9 +448,9 @@ gb_internal void semaphore_wait(Semaphore *s) { } #endif -static const int RWLOCK_WRITER = 1; -static const int RWLOCK_UPGRADED = 2; -static const int RWLOCK_READER = 4; +static const int RWLOCK_WRITER = 1<<0; +static const int RWLOCK_UPGRADED = 1<<1; +static const int RWLOCK_READER = 1<<2; struct RWSpinLock { Futex bits; }; @@ -467,7 +467,7 @@ bool rwlock_try_acquire_upgrade(RWSpinLock *l) { void rwlock_acquire_upgrade(RWSpinLock *l) { while (!rwlock_try_acquire_upgrade(l)) { - futex_wait(&l->bits, RWLOCK_UPGRADED); + futex_wait(&l->bits, RWLOCK_UPGRADED | RWLOCK_WRITER); } } void rwlock_release_upgrade(RWSpinLock *l) { @@ -481,7 +481,7 @@ bool rwlock_try_release_upgrade_and_acquire_write(RWSpinLock *l) { void rwlock_release_upgrade_and_acquire_write(RWSpinLock *l) { while (!rwlock_try_release_upgrade_and_acquire_write(l)) { - futex_wait(&l->bits, RWLOCK_WRITER); + futex_wait(&l->bits, RWLOCK_UPGRADED); } } From 01258d4817bb8d04ab287d847c93d36952f11f22 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 19:38:30 +0100 Subject: [PATCH 038/162] Multithread "check all scope usages" --- src/build_settings.cpp | 2 +- src/checker.cpp | 43 ++++++++++++++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 0c88f3d13..077660f10 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1163,8 +1163,8 @@ gb_internal String internal_odin_root_dir(void) { return global_module_path; } + TEMPORARY_ALLOCATOR_GUARD(); auto path_buf = array_make(temporary_allocator(), 300); - defer (array_free(&path_buf)); len = 0; for (;;) { diff --git a/src/checker.cpp b/src/checker.cpp index c1d6302ad..1fca3dfe5 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -7204,6 +7204,36 @@ gb_internal void check_update_dependency_tree_for_procedures(Checker *c) { } #endif +gb_internal WORKER_TASK_PROC(check_scope_usage_file_worker) { + Checker *c = global_checker_ptr.load(std::memory_order_relaxed); + AstFile *f = cast(AstFile *)data; + u64 vet_flags = ast_file_vet_flags(f); + check_scope_usage(c, f->scope, vet_flags); + return 0; +} + +gb_internal WORKER_TASK_PROC(check_scope_usage_pkg_worker) { + Checker *c = global_checker_ptr.load(std::memory_order_relaxed); + AstPackage *pkg = cast(AstPackage *)data; + check_scope_usage_internal(c, pkg->scope, 0, true); + return 0; +} + + + +gb_internal void check_all_scope_usages(Checker *c) { + for (auto const &entry : c->info.files) { + AstFile *f = entry.value; + thread_pool_add_task(check_scope_usage_file_worker, f); + } + for (auto const &entry : c->info.packages) { + AstPackage *pkg = entry.value; + thread_pool_add_task(check_scope_usage_pkg_worker, pkg); + } + + thread_pool_wait(); +} + gb_internal void check_parsed_files(Checker *c) { global_checker_ptr.store(c, std::memory_order_relaxed); @@ -7271,16 +7301,9 @@ gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("add entities from procedure bodies"); check_merge_queues_into_arrays(c); - TIME_SECTION("check scope usage"); - for (auto const &entry : c->info.files) { - AstFile *f = entry.value; - u64 vet_flags = ast_file_vet_flags(f); - check_scope_usage(c, f->scope, vet_flags); - } - for (auto const &entry : c->info.packages) { - AstPackage *pkg = entry.value; - check_scope_usage_internal(c, pkg->scope, 0, true); - } + TIME_SECTION("check all scope usages"); + check_all_scope_usages(c); + TIME_SECTION("add basic type information"); // Add "Basic" type information From 1c648126c73d01e6bce368bd8a55dbb89f0f2369 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 19:47:29 +0100 Subject: [PATCH 039/162] Move more from `heap_allocator()` to `temporary_allocator()` --- src/check_expr.cpp | 25 +++++++++---------- src/checker.cpp | 61 ++++++++++++++++++++++------------------------ 2 files changed, 40 insertions(+), 46 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index bdbccb4f8..dad9e8348 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6986,10 +6986,10 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, isize lhs_count = -1; i32 variadic_index = -1; - auto positional_operands = array_make(heap_allocator(), 0, 0); - auto named_operands = array_make(heap_allocator(), 0, 0); - defer (array_free(&positional_operands)); - defer (array_free(&named_operands)); + TEMPORARY_ALLOCATOR_GUARD(); + + auto positional_operands = array_make(temporary_allocator(), 0, 0); + auto named_operands = array_make(temporary_allocator(), 0, 0); if (procs.count == 1) { Entity *e = procs[0]; @@ -7030,7 +7030,7 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, if (proc_arg_count >= 0) { lhs_count = proc_arg_count; if (lhs_count > 0) { - lhs = gb_alloc_array(heap_allocator(), Entity *, lhs_count); + lhs = gb_alloc_array(temporary_allocator(), Entity *, lhs_count); for (isize param_index = 0; param_index < lhs_count; param_index++) { Entity *e = nullptr; for (Entity *p : procs) { @@ -7116,13 +7116,9 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, array_add(&named_operands, o); } - gb_free(heap_allocator(), lhs); + auto valids = array_make(temporary_allocator(), 0, procs.count); - auto valids = array_make(heap_allocator(), 0, procs.count); - defer (array_free(&valids)); - - auto proc_entities = array_make(heap_allocator(), 0, procs.count*2 + 1); - defer (array_free(&proc_entities)); + auto proc_entities = array_make(temporary_allocator(), 0, procs.count*2 + 1); for (Entity *proc : procs) { array_add(&proc_entities, proc); } @@ -7606,6 +7602,8 @@ gb_internal isize lookup_polymorphic_record_parameter(Type *t, String parameter_ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, Operand *operand, Ast *call) { + TEMPORARY_ALLOCATOR_GUARD(); + ast_node(ce, CallExpr, call); Type *original_type = operand->type; @@ -7614,7 +7612,6 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O bool show_error = true; Array operands = {}; - defer (array_free(&operands)); CallArgumentError err = CallArgumentError_None; @@ -7629,7 +7626,7 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O if (is_call_expr_field_value(ce)) { named_fields = true; - operands = array_make(heap_allocator(), ce->args.count); + operands = array_make(temporary_allocator(), ce->args.count); for_array(i, ce->args) { Ast *arg = ce->args[i]; ast_node(fv, FieldValue, arg); @@ -7661,7 +7658,7 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O } } else { - operands = array_make(heap_allocator(), 0, 2*ce->args.count); + operands = array_make(temporary_allocator(), 0, 2*ce->args.count); Entity **lhs = nullptr; isize lhs_count = -1; diff --git a/src/checker.cpp b/src/checker.cpp index 1fca3dfe5..87dd775ca 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -722,9 +722,10 @@ gb_internal bool check_vet_unused(Checker *c, Entity *e, VettedEntity *ve) { gb_internal void check_scope_usage_internal(Checker *c, Scope *scope, u64 vet_flags, bool per_entity) { u64 original_vet_flags = vet_flags; + + TEMPORARY_ALLOCATOR_GUARD(); Array vetted_entities = {}; - array_init(&vetted_entities, heap_allocator()); - defer (array_free(&vetted_entities)); + array_init(&vetted_entities, temporary_allocator()); rw_mutex_shared_lock(&scope->mutex); for (auto const &entry : scope->elements) { @@ -5123,14 +5124,14 @@ gb_internal void add_import_dependency_node(Checker *c, Ast *decl, PtrMap generate_import_dependency_graph(Checker *c) { +gb_internal Array generate_import_dependency_graph(Checker *c, gbAllocator allocator) { PtrMap M = {}; map_init(&M, 2*c->parser->packages.count); defer (map_destroy(&M)); for_array(i, c->parser->packages) { AstPackage *pkg = c->parser->packages[i]; - ImportGraphNode *n = import_graph_node_create(heap_allocator(), pkg); + ImportGraphNode *n = import_graph_node_create(allocator, pkg); map_set(&M, pkg, n); } @@ -5147,7 +5148,7 @@ gb_internal Array generate_import_dependency_graph(Checker *c } Array G = {}; - array_init(&G, heap_allocator(), 0, M.count); + array_init(&G, allocator, 0, M.count); isize i = 0; for (auto const &entry : M) { @@ -5166,7 +5167,7 @@ struct ImportPathItem { Ast * decl; }; -gb_internal Array find_import_path(Checker *c, AstPackage *start, AstPackage *end, PtrSet *visited) { +gb_internal Array find_import_path(Checker *c, AstPackage *start, AstPackage *end, PtrSet *visited, gbAllocator allocator) { Array empty_path = {}; if (ptr_set_update(visited, start)) { @@ -5200,11 +5201,11 @@ gb_internal Array find_import_path(Checker *c, AstPackage *start ImportPathItem item = {pkg, decl}; if (pkg == end) { - auto path = array_make(heap_allocator()); + auto path = array_make(allocator); array_add(&path, item); return path; } - auto next_path = find_import_path(c, pkg, end, visited); + auto next_path = find_import_path(c, pkg, end, visited, allocator); if (next_path.count > 0) { array_add(&next_path, item); return next_path; @@ -5809,14 +5810,9 @@ gb_internal void check_export_entities(Checker *c) { } gb_internal void check_import_entities(Checker *c) { - Array dep_graph = generate_import_dependency_graph(c); - defer ({ - for_array(i, dep_graph) { - import_graph_node_destroy(dep_graph[i], heap_allocator()); - } - array_free(&dep_graph); - }); + TEMPORARY_ALLOCATOR_GUARD(); + Array dep_graph = generate_import_dependency_graph(c, temporary_allocator()); TIME_SECTION("check_import_entities - sort packages"); // NOTE(bill): Priority queue @@ -5826,8 +5822,7 @@ gb_internal void check_import_entities(Checker *c) { defer (ptr_set_destroy(&emitted)); Array package_order = {}; - array_init(&package_order, heap_allocator(), 0, c->parser->packages.count); - defer (array_free(&package_order)); + array_init(&package_order, temporary_allocator(), 0, c->parser->packages.count); while (pq.queue.count > 0) { ImportGraphNode *n = priority_queue_pop(&pq); @@ -5835,11 +5830,12 @@ gb_internal void check_import_entities(Checker *c) { AstPackage *pkg = n->pkg; if (n->dep_count > 0) { + TEMPORARY_ALLOCATOR_GUARD(); + PtrSet visited = {}; defer (ptr_set_destroy(&visited)); - auto path = find_import_path(c, pkg, pkg, &visited); - defer (array_free(&path)); + auto path = find_import_path(c, pkg, pkg, &visited, temporary_allocator()); if (path.count > 1) { ImportPathItem item = path[path.count-1]; @@ -5957,9 +5953,9 @@ gb_internal void check_import_entities(Checker *c) { } -gb_internal Array find_entity_path(Entity *start, Entity *end, PtrSet *visited = nullptr); +gb_internal Array find_entity_path(Entity *start, Entity *end, gbAllocator allocator, PtrSet *visited = nullptr); -gb_internal bool find_entity_path_tuple(Type *tuple, Entity *end, PtrSet *visited, Array *path_) { +gb_internal bool find_entity_path_tuple(Type *tuple, Entity *end, gbAllocator allocator, PtrSet *visited, Array *path_) { GB_ASSERT(path_ != nullptr); if (tuple == nullptr) { return false; @@ -5973,12 +5969,12 @@ gb_internal bool find_entity_path_tuple(Type *tuple, Entity *end, PtrSetdeps) { if (dep == end) { - auto path = array_make(heap_allocator()); + auto path = array_make(allocator); array_add(&path, dep); *path_ = path; return true; } - auto next_path = find_entity_path(dep, end, visited); + auto next_path = find_entity_path(dep, end, allocator, visited); if (next_path.count > 0) { array_add(&next_path, dep); *path_ = next_path; @@ -5990,7 +5986,7 @@ gb_internal bool find_entity_path_tuple(Type *tuple, Entity *end, PtrSet find_entity_path(Entity *start, Entity *end, PtrSet *visited) { +gb_internal Array find_entity_path(Entity *start, Entity *end, gbAllocator allocator, PtrSet *visited) { PtrSet visited_ = {}; bool made_visited = false; if (visited == nullptr) { @@ -6014,20 +6010,20 @@ gb_internal Array find_entity_path(Entity *start, Entity *end, PtrSet< GB_ASSERT(t->kind == Type_Proc); Array path = {}; - if (find_entity_path_tuple(t->Proc.params, end, visited, &path)) { + if (find_entity_path_tuple(t->Proc.params, end, allocator, visited, &path)) { return path; } - if (find_entity_path_tuple(t->Proc.results, end, visited, &path)) { + if (find_entity_path_tuple(t->Proc.results, end, allocator, visited, &path)) { return path; } } else { for (Entity *dep : decl->deps) { if (dep == end) { - auto path = array_make(heap_allocator()); + auto path = array_make(allocator); array_add(&path, dep); return path; } - auto next_path = find_entity_path(dep, end, visited); + auto next_path = find_entity_path(dep, end, allocator, visited); if (next_path.count > 0) { array_add(&next_path, dep); return next_path; @@ -6061,8 +6057,8 @@ gb_internal void calculate_global_init_order(Checker *c) { Entity *e = n->entity; if (n->dep_count > 0) { - auto path = find_entity_path(e, e); - defer (array_free(&path)); + TEMPORARY_ALLOCATOR_GUARD(); + auto path = find_entity_path(e, e, temporary_allocator()); if (path.count > 0) { Entity *e = path[0]; @@ -7450,9 +7446,10 @@ gb_internal void check_parsed_files(Checker *c) { TIME_SECTION("initialize and check for collisions in type info array"); { + TEMPORARY_ALLOCATOR_GUARD(); + Array type_info_types; // sorted after filled - array_init(&type_info_types, heap_allocator()); - defer (array_free(&type_info_types)); + array_init(&type_info_types, temporary_allocator()); for (auto const &tt : c->info.min_dep_type_info_set) { array_add(&type_info_types, tt); From 3c9538c708bd5833a83d19a266e84503c72b4628 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 10 Sep 2025 21:02:24 +0200 Subject: [PATCH 040/162] Change the way math/big constants are initialized --- core/math/big/common.odin | 13 +------------ core/math/big/helpers.odin | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/core/math/big/common.odin b/core/math/big/common.odin index 5428b00ee..22655293f 100644 --- a/core/math/big/common.odin +++ b/core/math/big/common.odin @@ -14,23 +14,12 @@ import "base:intrinsics" This allows to benchmark and/or setting optimized values for a certain CPU without recompiling. */ -/* - ========================== TUNABLES ========================== - - `initialize_constants` returns `#config(MUL_KARATSUBA_CUTOFF, _DEFAULT_MUL_KARATSUBA_CUTOFF)` - and we initialize this cutoff that way so that the procedure is used and called, - because it handles initializing the constants ONE, ZERO, MINUS_ONE, NAN and INF. - - `initialize_constants` also replaces the other `_DEFAULT_*` cutoffs with custom compile-time values if so `#config`ured. - -*/ - /* There is a bug with DLL globals. They don't get set. To allow tests to run we add `-define:MATH_BIG_EXE=false` to hardcode the cutoffs for now. */ when #config(MATH_BIG_EXE, true) { - MUL_KARATSUBA_CUTOFF := initialize_constants() + MUL_KARATSUBA_CUTOFF := _DEFAULT_MUL_KARATSUBA_CUTOFF SQR_KARATSUBA_CUTOFF := _DEFAULT_SQR_KARATSUBA_CUTOFF MUL_TOOM_CUTOFF := _DEFAULT_MUL_TOOM_CUTOFF SQR_TOOM_CUTOFF := _DEFAULT_SQR_TOOM_CUTOFF diff --git a/core/math/big/helpers.odin b/core/math/big/helpers.odin index 569f0b810..9ee35c45a 100644 --- a/core/math/big/helpers.odin +++ b/core/math/big/helpers.odin @@ -778,13 +778,14 @@ int_from_bytes_little_python :: proc(a: ^Int, buf: []u8, signed := false, alloca */ INT_ONE, INT_ZERO, INT_MINUS_ONE, INT_INF, INT_MINUS_INF, INT_NAN := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{} -@(init, private) -_init_constants :: proc "contextless" () { - initialize_constants() -} +@(private) +constant_allocator: runtime.Allocator -initialize_constants :: proc "contextless" () -> (res: int) { +@(init, private) +initialize_constants :: proc "contextless" () { context = runtime.default_context() + constant_allocator = context.allocator + internal_int_set_from_integer( INT_ZERO, 0); INT_ZERO.flags = {.Immutable} internal_int_set_from_integer( INT_ONE, 1); INT_ONE.flags = {.Immutable} internal_int_set_from_integer(INT_MINUS_ONE, -1); INT_MINUS_ONE.flags = {.Immutable} @@ -796,15 +797,17 @@ initialize_constants :: proc "contextless" () -> (res: int) { internal_int_set_from_integer( INT_NAN, 1); INT_NAN.flags = {.Immutable, .NaN} internal_int_set_from_integer( INT_INF, 1); INT_INF.flags = {.Immutable, .Inf} internal_int_set_from_integer(INT_MINUS_INF, -1); INT_MINUS_INF.flags = {.Immutable, .Inf} - - return _DEFAULT_MUL_KARATSUBA_CUTOFF } /* Destroy constants. Optional for an EXE, as this would be called at the very end of a process. */ -destroy_constants :: proc() { +@(fini, private) +destroy_constants :: proc "contextless" () { + context = runtime.default_context() + context.allocator = constant_allocator + internal_destroy(INT_ONE, INT_ZERO, INT_MINUS_ONE, INT_INF, INT_MINUS_INF, INT_NAN) } From 228ddd69034d7711a6c21b129eaac1b05d71b4ff Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 20:02:39 +0100 Subject: [PATCH 041/162] Inline some ptr set iterators --- src/checker.cpp | 17 +++++++++++++---- src/ptr_set.cpp | 5 ++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index 87dd775ca..dd4c0085a 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2996,7 +2996,7 @@ gb_internal void generate_minimum_dependency_set(Checker *c, Entity *start) { #undef FORCE_ADD_RUNTIME_ENTITIES } -gb_internal bool is_entity_a_dependency(Entity *e) { +gb_internal gb_inline bool is_entity_a_dependency(Entity *e) { if (e == nullptr) return false; switch (e->kind) { case Entity_Procedure: @@ -3047,7 +3047,9 @@ gb_internal Array generate_entity_dependency_graph(CheckerInf DeclInfo *decl = decl_info_of_entity(e); GB_ASSERT(decl != nullptr); - for (Entity *dep : decl->deps) { + FOR_PTR_SET(i_dep, decl->deps) { + Entity *dep = decl->deps.keys[i_dep]; + if (dep->flags & EntityFlag_Field) { continue; } @@ -3078,14 +3080,21 @@ gb_internal Array generate_entity_dependency_graph(CheckerInf // Connect each pred 'p' of 'n' with each succ 's' and from // the procedure node - for (EntityGraphNode *p : n->pred) { + FOR_PTR_SET(i_p, n->pred) { + EntityGraphNode *p = n->pred.keys[i_p]; + // Ignore self-cycles if (p == n) { continue; } // Each succ 's' of 'n' becomes a succ of 'p', and // each pred 'p' of 'n' becomes a pred of 's' - for (EntityGraphNode *s : n->succ) { + FOR_PTR_SET(i_s, n->succ) { + EntityGraphNode *s = n->succ.keys[i_s]; + if (s == nullptr || s == cast(EntityGraphNode *)PtrSet::TOMBSTONE) { + // NOTE(bill): This is inlined to improve development build performance + continue; + } // Ignore self-cycles if (s == n) { continue; diff --git a/src/ptr_set.cpp b/src/ptr_set.cpp index 06c1e4a58..512a157d0 100644 --- a/src/ptr_set.cpp +++ b/src/ptr_set.cpp @@ -239,4 +239,7 @@ gb_internal PtrSetIterator begin(PtrSet &set) noexcept { template gb_internal PtrSetIterator end(PtrSet &set) noexcept { return PtrSetIterator{&set, set.capacity}; -} \ No newline at end of file +} + + +#define FOR_PTR_SET(index_, set_) for (usize index_ = 0; index_ < (set_).capacity; index_++) if ((set_).keys[index_] != nullptr && (set_).keys[index_] != cast(void *)~(uintptr)(0ull)) \ No newline at end of file From bc36ea41706f9773b031e161cbb6ece6da3d4250 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 20:11:36 +0100 Subject: [PATCH 042/162] Use macro instead of a C++ iterator - for speed C++ iterators are bad. --- src/check_decl.cpp | 2 +- src/check_expr.cpp | 2 +- src/checker.cpp | 36 ++++++++++++++---------------------- src/ptr_set.cpp | 9 +++++---- 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 49731ad60..ff888b74e 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1915,7 +1915,7 @@ gb_internal void add_deps_from_child_to_parent(DeclInfo *decl) { rw_mutex_shared_lock(&decl->deps_mutex); rw_mutex_lock(&decl->parent->deps_mutex); - for (Entity *e : decl->deps) { + FOR_PTR_SET(e, decl->deps) { ptr_set_add(&decl->parent->deps, e); } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index dad9e8348..012c50270 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6028,7 +6028,7 @@ gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize } rw_mutex_shared_lock(&decl->deps_mutex); rw_mutex_lock(&c->decl->deps_mutex); - for (Entity *dep : decl->deps) { + FOR_PTR_SET(dep, decl->deps) { ptr_set_add(&c->decl->deps, dep); } rw_mutex_unlock(&c->decl->deps_mutex); diff --git a/src/checker.cpp b/src/checker.cpp index dd4c0085a..934e6282b 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2584,7 +2584,7 @@ gb_internal void add_dependency_to_set(Checker *c, Entity *entity) { for (TypeInfoPair const tt : decl->type_info_deps) { add_min_dep_type_info(c, tt.type); } - for (Entity *e : decl->deps) { + FOR_PTR_SET(e, decl->deps) { switch (e->kind) { case Entity_Procedure: if (e->Procedure.is_foreign) { @@ -2611,7 +2611,7 @@ gb_internal void add_dependency_to_set(Checker *c, Entity *entity) { } } - for (Entity *e : decl->deps) { + FOR_PTR_SET(e, decl->deps) { add_dependency_to_set(c, e); } @@ -2643,7 +2643,7 @@ gb_internal WORKER_TASK_PROC(add_dependency_to_set_worker) { add_min_dep_type_info(c, tt.type); } - for (Entity *e : decl->deps) { + FOR_PTR_SET(e, decl->deps) { switch (e->kind) { case Entity_Procedure: if (e->Procedure.is_foreign) { @@ -2670,7 +2670,7 @@ gb_internal WORKER_TASK_PROC(add_dependency_to_set_worker) { } } - for (Entity *e : decl->deps) { + FOR_PTR_SET(e, decl->deps) { add_dependency_to_set_threaded(c, e); } @@ -3047,9 +3047,7 @@ gb_internal Array generate_entity_dependency_graph(CheckerInf DeclInfo *decl = decl_info_of_entity(e); GB_ASSERT(decl != nullptr); - FOR_PTR_SET(i_dep, decl->deps) { - Entity *dep = decl->deps.keys[i_dep]; - + FOR_PTR_SET(dep, decl->deps) { if (dep->flags & EntityFlag_Field) { continue; } @@ -3080,21 +3078,14 @@ gb_internal Array generate_entity_dependency_graph(CheckerInf // Connect each pred 'p' of 'n' with each succ 's' and from // the procedure node - FOR_PTR_SET(i_p, n->pred) { - EntityGraphNode *p = n->pred.keys[i_p]; - + FOR_PTR_SET(p, n->pred) { // Ignore self-cycles if (p == n) { continue; } // Each succ 's' of 'n' becomes a succ of 'p', and // each pred 'p' of 'n' becomes a pred of 's' - FOR_PTR_SET(i_s, n->succ) { - EntityGraphNode *s = n->succ.keys[i_s]; - if (s == nullptr || s == cast(EntityGraphNode *)PtrSet::TOMBSTONE) { - // NOTE(bill): This is inlined to improve development build performance - continue; - } + FOR_PTR_SET(s, n->succ) { // Ignore self-cycles if (s == n) { continue; @@ -5859,7 +5850,7 @@ gb_internal void check_import_entities(Checker *c) { } } - for (ImportGraphNode *p : n->pred) { + FOR_PTR_SET(p, n->pred) { p->dep_count = gb_max(p->dep_count-1, 0); priority_queue_fix(&pq, p->index); } @@ -5976,7 +5967,8 @@ gb_internal bool find_entity_path_tuple(Type *tuple, Entity *end, gbAllocator al if (var_decl == nullptr) { continue; } - for (Entity *dep : var_decl->deps) { + + FOR_PTR_SET(dep, var_decl->deps) { if (dep == end) { auto path = array_make(allocator); array_add(&path, dep); @@ -6026,7 +6018,7 @@ gb_internal Array find_entity_path(Entity *start, Entity *end, gbAlloc return path; } } else { - for (Entity *dep : decl->deps) { + FOR_PTR_SET(dep, decl->deps) { if (dep == end) { auto path = array_make(allocator); array_add(&path, dep); @@ -6080,7 +6072,7 @@ gb_internal void calculate_global_init_order(Checker *c) { } } - for (EntityGraphNode *p : n->pred) { + FOR_PTR_SET(p, n->pred) { p->dep_count -= 1; p->dep_count = gb_max(p->dep_count, 0); priority_queue_fix(&pq, p->index); @@ -6273,7 +6265,7 @@ gb_internal bool check_proc_info(Checker *c, ProcInfo *pi, UntypedExprInfoMap *u add_untyped_expressions(&c->info, ctx.untyped); rw_mutex_shared_lock(&ctx.decl->deps_mutex); - for (Entity *dep : ctx.decl->deps) { + FOR_PTR_SET(dep, ctx.decl->deps) { if (dep && dep->kind == Entity_Procedure && (dep->flags & EntityFlag_ProcBodyChecked) == 0) { check_procedure_later_from_entity(c, dep, NULL); @@ -7346,7 +7338,7 @@ gb_internal void check_parsed_files(Checker *c) { DeclInfo *decl = e->decl_info; ast_node(pl, ProcLit, decl->proc_lit); if (pl->inlining == ProcInlining_inline) { - for (Entity *dep : decl->deps) { + FOR_PTR_SET(dep, decl->deps) { if (dep == e) { error(e->token, "Cannot inline recursive procedure '%.*s'", LIT(e->token.string)); break; diff --git a/src/ptr_set.cpp b/src/ptr_set.cpp index 512a157d0..5b1d2cc19 100644 --- a/src/ptr_set.cpp +++ b/src/ptr_set.cpp @@ -16,6 +16,8 @@ template gb_internal bool ptr_set_exists (PtrSet *s, T ptr); template gb_internal void ptr_set_remove (PtrSet *s, T ptr); template gb_internal void ptr_set_clear (PtrSet *s); +#define FOR_PTR_SET(element, set_) for (auto *it = &(set_).keys[0], element = it ? *it : nullptr; (set_).keys != nullptr && it < &(set_).keys[(set_).capacity]; it++) if (element = *it, (*it != nullptr && *it != cast(void *)~(uintptr)(0ull))) + gb_internal gbAllocator ptr_set_allocator(void) { return heap_allocator(); } @@ -83,7 +85,7 @@ gb_internal gb_inline void ptr_set_grow(PtrSet *old_set) { PtrSet new_set = {}; ptr_set_init(&new_set, gb_max(old_set->capacity<<1, 16)); - for (T ptr : *old_set) { + FOR_PTR_SET(ptr, *old_set) { bool was_new = ptr_set_update(&new_set, ptr); GB_ASSERT(!was_new); } @@ -195,7 +197,7 @@ gb_internal gb_inline void ptr_set_clear(PtrSet *s) { gb_zero_size(s->keys, s->capacity*gb_size_of(T)); } -template +/*template struct PtrSetIterator { PtrSet *set; usize index; @@ -239,7 +241,6 @@ gb_internal PtrSetIterator begin(PtrSet &set) noexcept { template gb_internal PtrSetIterator end(PtrSet &set) noexcept { return PtrSetIterator{&set, set.capacity}; -} +}*/ -#define FOR_PTR_SET(index_, set_) for (usize index_ = 0; index_ < (set_).capacity; index_++) if ((set_).keys[index_] != nullptr && (set_).keys[index_] != cast(void *)~(uintptr)(0ull)) \ No newline at end of file From d3602ca634061bda4561a05c37f4fe3a828ad1e6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 20:37:39 +0100 Subject: [PATCH 043/162] Removal of some old checks --- src/checker.cpp | 83 ++++++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index 934e6282b..e451a38e1 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2997,7 +2997,6 @@ gb_internal void generate_minimum_dependency_set(Checker *c, Entity *start) { } gb_internal gb_inline bool is_entity_a_dependency(Entity *e) { - if (e == nullptr) return false; switch (e->kind) { case Entity_Procedure: return true; @@ -3026,7 +3025,7 @@ gb_internal Array generate_entity_dependency_graph(CheckerInf for_array(i, info->entities) { Entity *e = info->entities[i]; - if (!is_entity_a_dependency(e)) { + if (e == nullptr || !is_entity_a_dependency(e)) { continue; } EntityGraphNode *n = arena_alloc_item(arena); @@ -3048,10 +3047,10 @@ gb_internal Array generate_entity_dependency_graph(CheckerInf GB_ASSERT(decl != nullptr); FOR_PTR_SET(dep, decl->deps) { + GB_ASSERT(dep != nullptr); if (dep->flags & EntityFlag_Field) { continue; } - GB_ASSERT(dep != nullptr); if (!is_entity_a_dependency(dep)) { continue; } @@ -7129,13 +7128,18 @@ gb_internal void check_sort_init_and_fini_procedures(Checker *c) { } gb_internal void add_type_info_for_type_definitions(Checker *c) { - for_array(i, c->info.definitions) { - Entity *e = c->info.definitions[i]; + for (Entity *e : c->info.definitions) { if (e->kind == Entity_TypeName && e->type != nullptr && is_type_typed(e->type)) { + #if 0 i64 align = type_align_of(e->type); if (align > 0 && e->min_dep_count.load(std::memory_order_relaxed) > 0) { add_type_info_type(&c->builtin_ctx, e->type); } + #else + if (e->min_dep_count.load(std::memory_order_relaxed) > 0) { + add_type_info_type(&c->builtin_ctx, e->type); + } + #endif } } } @@ -7232,6 +7236,43 @@ gb_internal void check_all_scope_usages(Checker *c) { } +gb_internal void check_for_type_cycles(Checker *c) { + // NOTE(bill): Check for illegal cyclic type declarations + for_array(i, c->info.definitions) { + Entity *e = c->info.definitions[i]; + if (e->kind != Entity_TypeName) { + continue; + } + if (e->type != nullptr && is_type_typed(e->type)) { + if (e->TypeName.is_type_alias) { + // Ignore for the time being + } else { + (void)type_align_of(e->type); + } + } + } +} + +gb_internal void check_for_inline_cycles(Checker *c) { + for_array(i, c->info.definitions) { + Entity *e = c->info.definitions[i]; + if (e->kind != Entity_Procedure) { + continue; + } + DeclInfo *decl = e->decl_info; + ast_node(pl, ProcLit, decl->proc_lit); + if (pl->inlining == ProcInlining_inline) { + FOR_PTR_SET(dep, decl->deps) { + if (dep == e) { + error(e->token, "Cannot inline recursive procedure '%.*s'", LIT(e->token.string)); + break; + } + } + } + } +} + + gb_internal void check_parsed_files(Checker *c) { global_checker_ptr.store(c, std::memory_order_relaxed); @@ -7314,38 +7355,10 @@ gb_internal void check_parsed_files(Checker *c) { check_merge_queues_into_arrays(c); TIME_SECTION("check for type cycles"); - // NOTE(bill): Check for illegal cyclic type declarations - for_array(i, c->info.definitions) { - Entity *e = c->info.definitions[i]; - if (e->kind != Entity_TypeName) { - continue; - } - if (e->type != nullptr && is_type_typed(e->type)) { - if (e->TypeName.is_type_alias) { - // Ignore for the time being - } else { - (void)type_align_of(e->type); - } - } - } + check_for_type_cycles(c); TIME_SECTION("check for inline cycles"); - for_array(i, c->info.definitions) { - Entity *e = c->info.definitions[i]; - if (e->kind != Entity_Procedure) { - continue; - } - DeclInfo *decl = e->decl_info; - ast_node(pl, ProcLit, decl->proc_lit); - if (pl->inlining == ProcInlining_inline) { - FOR_PTR_SET(dep, decl->deps) { - if (dep == e) { - error(e->token, "Cannot inline recursive procedure '%.*s'", LIT(e->token.string)); - break; - } - } - } - } + check_for_inline_cycles(c); TIME_SECTION("check deferred procedures"); check_deferred_procedures(c); From 0476d33a6c3b0c730b0e0defc10b571b1037c14c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 20:45:26 +0100 Subject: [PATCH 044/162] Remove global `PtrMap` and store on the `TypeNamed` directly --- src/check_type.cpp | 18 +++++++++--------- src/checker.cpp | 5 ----- src/checker.hpp | 2 -- src/types.cpp | 15 ++++++++++----- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index e99909d6b..aec416921 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -267,19 +267,19 @@ gb_internal bool check_custom_align(CheckerContext *ctx, Ast *node, i64 *align_, gb_internal GenTypesData *ensure_polymorphic_record_entity_has_gen_types(CheckerContext *ctx, Type *original_type) { - mutex_lock(&ctx->info->gen_types_mutex); // @@global - GenTypesData *found_gen_types = nullptr; - auto *found_gen_types_ptr = map_get(&ctx->info->gen_types, original_type); - if (found_gen_types_ptr == nullptr) { + + GB_ASSERT(original_type->kind == Type_Named); + mutex_lock(&original_type->Named.gen_types_data_mutex); + if (original_type->Named.gen_types_data == nullptr) { GenTypesData *gen_types = gb_alloc_item(permanent_allocator(), GenTypesData); gen_types->types = array_make(heap_allocator()); - map_set(&ctx->info->gen_types, original_type, gen_types); - found_gen_types_ptr = map_get(&ctx->info->gen_types, original_type); + original_type->Named.gen_types_data = gen_types; } - found_gen_types = *found_gen_types_ptr; - GB_ASSERT(found_gen_types != nullptr); - mutex_unlock(&ctx->info->gen_types_mutex); // @@global + found_gen_types = original_type->Named.gen_types_data; + + mutex_unlock(&original_type->Named.gen_types_data_mutex); + return found_gen_types; } diff --git a/src/checker.cpp b/src/checker.cpp index e451a38e1..eb334e147 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1420,13 +1420,10 @@ gb_internal void init_checker_info(CheckerInfo *i) { array_init(&i->entities, a); map_init(&i->global_untyped); string_map_init(&i->foreigns); - // map_init(&i->gen_procs); - map_init(&i->gen_types); type_set_init(&i->min_dep_type_info_set); map_init(&i->min_dep_type_info_index_map); - // map_init(&i->type_info_map); string_map_init(&i->files); string_map_init(&i->packages); array_init(&i->variable_init_order, a); @@ -1465,8 +1462,6 @@ gb_internal void destroy_checker_info(CheckerInfo *i) { array_free(&i->entities); map_destroy(&i->global_untyped); string_map_destroy(&i->foreigns); - // map_destroy(&i->gen_procs); - map_destroy(&i->gen_types); type_set_destroy(&i->min_dep_type_info_set); map_destroy(&i->min_dep_type_info_index_map); diff --git a/src/checker.hpp b/src/checker.hpp index 5a40b10a0..e50fa40f4 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -480,8 +480,6 @@ struct CheckerInfo { RecursiveMutex lazy_mutex; // Mutex required for lazy type checking of specific files - BlockingMutex gen_types_mutex; - PtrMap gen_types; // BlockingMutex type_info_mutex; // NOT recursive // Array type_info_types; diff --git a/src/types.cpp b/src/types.cpp index 44f9394c7..3ccc74996 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -206,13 +206,18 @@ struct TypeProc { bool optional_ok; }; +struct TypeNamed { + String name; + Type * base; + Entity *type_name; /* Entity_TypeName */ + + BlockingMutex gen_types_data_mutex; + GenTypesData *gen_types_data; +}; + #define TYPE_KINDS \ TYPE_KIND(Basic, BasicType) \ - TYPE_KIND(Named, struct { \ - String name; \ - Type * base; \ - Entity *type_name; /* Entity_TypeName */ \ - }) \ + TYPE_KIND(Named, TypeNamed) \ TYPE_KIND(Generic, struct { \ i64 id; \ String name; \ From 34e3d3078057fdf22c2f91847096e0a3e098fa82 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 20:51:52 +0100 Subject: [PATCH 045/162] More thread contention removal --- src/check_expr.cpp | 2 +- src/checker.hpp | 2 +- src/parser.cpp | 10 +++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 012c50270..86b4f3aee 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7886,8 +7886,8 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O GenTypesData *found_gen_types = ensure_polymorphic_record_entity_has_gen_types(c, original_type); mutex_lock(&found_gen_types->mutex); - defer (mutex_unlock(&found_gen_types->mutex)); Entity *found_entity = find_polymorphic_record_entity(found_gen_types, param_count, ordered_operands); + mutex_unlock(&found_gen_types->mutex); if (found_entity) { operand->mode = Addressing_Type; diff --git a/src/checker.hpp b/src/checker.hpp index e50fa40f4..ddf713dad 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -417,7 +417,7 @@ struct GenProcsData { struct GenTypesData { Array types; - RecursiveMutex mutex; + BlockingMutex mutex; }; struct Defineable { diff --git a/src/parser.cpp b/src/parser.cpp index a05e183ce..a7006dd39 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1,5 +1,7 @@ #include "parser_pos.cpp" +gb_global std::atomic g_parsing_done; + gb_internal bool in_vet_packages(AstFile *file) { if (file == nullptr) { return true; @@ -176,7 +178,11 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) { return nullptr; } if (f == nullptr) { - f = node->thread_safe_file(); + if (g_parsing_done.load(std::memory_order_relaxed)) { + f = node->file(); + } else { + f = node->thread_safe_file(); + } } Ast *n = alloc_ast_node(f, node->kind); gb_memmove(n, node, ast_node_size(node->kind)); @@ -6924,6 +6930,8 @@ gb_internal ParseFileError parse_packages(Parser *p, String init_filename) { } } + g_parsing_done.store(true, std::memory_order_relaxed); + return ParseFile_None; } From 549edcc0f90d632587c427e5d4189721323bd4a8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 21:00:43 +0100 Subject: [PATCH 046/162] Use a `RwMutex` instead of `BlockingMutex` --- src/check_expr.cpp | 5 ++--- src/check_type.cpp | 4 ++-- src/checker.hpp | 2 +- src/llvm_backend_expr.cpp | 5 +++-- src/types.cpp | 13 +++++-------- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 86b4f3aee..abfd11485 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -5101,7 +5101,6 @@ gb_internal ExactValue get_constant_field_single(CheckerContext *c, ExactValue v ast_node(fv, FieldValue, elem); String name = fv->field->Ident.token.string; Selection sub_sel = lookup_field(node->tav.type, name, false); - defer (array_free(&sub_sel.index)); if (sub_sel.index.count > 0 && sub_sel.index[0] == index) { value = fv->value->tav.value; @@ -7885,9 +7884,9 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O { GenTypesData *found_gen_types = ensure_polymorphic_record_entity_has_gen_types(c, original_type); - mutex_lock(&found_gen_types->mutex); + rw_mutex_shared_lock(&found_gen_types->mutex); Entity *found_entity = find_polymorphic_record_entity(found_gen_types, param_count, ordered_operands); - mutex_unlock(&found_gen_types->mutex); + rw_mutex_shared_unlock(&found_gen_types->mutex); if (found_entity) { operand->mode = Addressing_Type; diff --git a/src/check_type.cpp b/src/check_type.cpp index aec416921..71db34afa 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -321,8 +321,8 @@ gb_internal void add_polymorphic_record_entity(CheckerContext *ctx, Ast *node, T e->TypeName.objc_metadata = original_type->Named.type_name->TypeName.objc_metadata; auto *found_gen_types = ensure_polymorphic_record_entity_has_gen_types(ctx, original_type); - mutex_lock(&found_gen_types->mutex); - defer (mutex_unlock(&found_gen_types->mutex)); + rw_mutex_lock(&found_gen_types->mutex); + defer (rw_mutex_unlock(&found_gen_types->mutex)); for (Entity *prev : found_gen_types->types) { if (prev == e) { diff --git a/src/checker.hpp b/src/checker.hpp index ddf713dad..968988962 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -417,7 +417,7 @@ struct GenProcsData { struct GenTypesData { Array types; - BlockingMutex mutex; + RwMutex mutex; }; struct Defineable { diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index ebc3ec158..cff91813e 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -2563,10 +2563,11 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { Type *dt = t; + TEMPORARY_ALLOCATOR_GUARD(); + GB_ASSERT(is_type_struct(st) || is_type_raw_union(st)); Selection sel = {}; - sel.index.allocator = heap_allocator(); - defer (array_free(&sel.index)); + sel.index.allocator = temporary_allocator(); if (lookup_subtype_polymorphic_selection(t, src_type, &sel)) { if (sel.entity == nullptr) { GB_PANIC("invalid subtype cast %s -> ", type_to_string(src_type), type_to_string(t)); diff --git a/src/types.cpp b/src/types.cpp index 3ccc74996..4515b2c60 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -435,11 +435,8 @@ gb_internal Selection make_selection(Entity *entity, Array index, bool indi } gb_internal void selection_add_index(Selection *s, isize index) { - // IMPORTANT NOTE(bill): this requires a stretchy buffer/dynamic array so it requires some form - // of heap allocation - // TODO(bill): Find a way to use a backing buffer for initial use as the general case is probably .count<3 if (s->index.data == nullptr) { - array_init(&s->index, heap_allocator()); + array_init(&s->index, permanent_allocator()); } array_add(&s->index, cast(i32)index); } @@ -447,7 +444,7 @@ gb_internal void selection_add_index(Selection *s, isize index) { gb_internal Selection selection_combine(Selection const &lhs, Selection const &rhs) { Selection new_sel = lhs; new_sel.indirect = lhs.indirect || rhs.indirect; - new_sel.index = array_make(heap_allocator(), lhs.index.count+rhs.index.count); + new_sel.index = array_make(permanent_allocator(), lhs.index.count+rhs.index.count); array_copy(&new_sel.index, lhs.index, 0); array_copy(&new_sel.index, rhs.index, lhs.index.count); return new_sel; @@ -3958,7 +3955,7 @@ gb_internal i64 type_size_of(Type *t) { TypePath path{}; type_path_init(&path); { - MUTEX_GUARD(&g_type_mutex); + // MUTEX_GUARD(&g_type_mutex); size = type_size_of_internal(t, &path); t->cached_size.store(size); } @@ -3978,7 +3975,7 @@ gb_internal i64 type_align_of(Type *t) { TypePath path{}; type_path_init(&path); { - MUTEX_GUARD(&g_type_mutex); + // MUTEX_GUARD(&g_type_mutex); t->cached_align.store(type_align_of_internal(t, &path)); } type_path_free(&path); @@ -4704,7 +4701,7 @@ gb_internal Type *alloc_type_tuple_from_field_types(Type **field_types, isize fi } Type *t = alloc_type_tuple(); - t->Tuple.variables = slice_make(heap_allocator(), field_count); + t->Tuple.variables = slice_make(permanent_allocator(), field_count); Scope *scope = nullptr; for_array(i, t->Tuple.variables) { From 992cad101c495e9a0c96a46c0eb50f6a59c7e68f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 21:16:56 +0100 Subject: [PATCH 047/162] Minor mutex rearrangement --- src/check_decl.cpp | 4 ++-- src/queue.cpp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index ff888b74e..58e4f0120 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -162,8 +162,6 @@ gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_e if (found_scope == nullptr) { return; } - rw_mutex_lock(&found_scope->mutex); - defer (rw_mutex_unlock(&found_scope->mutex)); // IMPORTANT NOTE(bill, 2021-04-10): Overriding behaviour was flawed in that the // original entity was still used check checked, but the checking was only @@ -172,7 +170,9 @@ gb_internal void override_entity_in_scope(Entity *original_entity, Entity *new_e // Therefore two things can be done: the type can be assigned to state that it // has been "evaluated" and the variant data can be copied across + rw_mutex_lock(&found_scope->mutex); string_map_set(&found_scope->elements, original_name, new_entity); + rw_mutex_unlock(&found_scope->mutex); original_entity->flags |= EntityFlag_Overridden; original_entity->type = new_entity->type; diff --git a/src/queue.cpp b/src/queue.cpp index dee9ad1f8..82f82f3e1 100644 --- a/src/queue.cpp +++ b/src/queue.cpp @@ -36,7 +36,8 @@ gb_internal void mpsc_destroy(MPSCQueue *q) { template gb_internal MPSCNode *mpsc_alloc_node(MPSCQueue *q, T const &value) { - auto new_node = gb_alloc_item(heap_allocator(), MPSCNode); + // auto new_node = gb_alloc_item(heap_allocator(), MPSCNode); + auto new_node = gb_alloc_item(permanent_allocator(), MPSCNode); new_node->value = value; return new_node; } From 5ea2e1fe60872c5d2b20e180a1514a082b6513e4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 10 Sep 2025 21:41:52 +0100 Subject: [PATCH 048/162] Minimize mutex usage when in single threaded mode. --- src/check_decl.cpp | 2 +- src/check_expr.cpp | 10 +++++----- src/check_stmt.cpp | 12 +++++++----- src/checker.cpp | 39 +++++++++++++++++++++++++-------------- src/checker.hpp | 4 ++-- src/parser.cpp | 1 + src/parser.hpp | 1 + 7 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 58e4f0120..8fbcb5e40 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -948,7 +948,7 @@ gb_internal Entity *init_entity_foreign_library(CheckerContext *ctx, Entity *e) error(ident, "foreign library names must be an identifier"); } else { String name = ident->Ident.token.string; - Entity *found = scope_lookup(ctx->scope, name); + Entity *found = scope_lookup(ctx->scope, name, ident->Ident.hash); if (found == nullptr) { if (is_blank_ident(name)) { diff --git a/src/check_expr.cpp b/src/check_expr.cpp index abfd11485..d2505c047 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1738,7 +1738,7 @@ gb_internal Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *nam o->expr = n; String name = n->Ident.token.string; - Entity *e = scope_lookup(c->scope, name); + Entity *e = scope_lookup(c->scope, name, n->Ident.hash); if (e == nullptr) { if (is_blank_ident(name)) { error(n, "'_' cannot be used as a value"); @@ -5361,7 +5361,7 @@ gb_internal Entity *check_entity_from_ident_or_selector(CheckerContext *c, Ast * } } else */if (node->kind == Ast_Ident) { String name = node->Ident.token.string; - return scope_lookup(c->scope, name); + return scope_lookup(c->scope, name, node->Ident.hash); } else if (!ident_only) if (node->kind == Ast_SelectorExpr) { ast_node(se, SelectorExpr, node); if (se->token.kind == Token_ArrowRight) { @@ -5383,7 +5383,7 @@ gb_internal Entity *check_entity_from_ident_or_selector(CheckerContext *c, Ast * if (op_expr->kind == Ast_Ident) { String op_name = op_expr->Ident.token.string; - Entity *e = scope_lookup(c->scope, op_name); + Entity *e = scope_lookup(c->scope, op_name, op_expr->Ident.hash); if (e == nullptr) { return nullptr; } @@ -5480,7 +5480,7 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod if (op_expr->kind == Ast_Ident) { String op_name = op_expr->Ident.token.string; - Entity *e = scope_lookup(c->scope, op_name); + Entity *e = scope_lookup(c->scope, op_name, op_expr->Ident.hash); add_entity_use(c, op_expr, e); expr_entity = e; @@ -5903,7 +5903,7 @@ gb_internal bool check_identifier_exists(Scope *s, Ast *node, bool nested = fals return true; } } else { - Entity *e = scope_lookup(s, name); + Entity *e = scope_lookup(s, name, i->hash); if (e != nullptr) { if (out_scope) *out_scope = e->scope; return true; diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index ba9c08fed..62cfc256a 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -484,7 +484,7 @@ gb_internal Type *check_assignment_variable(CheckerContext *ctx, Operand *lhs, O } if (ident_node != nullptr) { ast_node(i, Ident, ident_node); - e = scope_lookup(ctx->scope, i->token.string); + e = scope_lookup(ctx->scope, i->token.string, i->hash); if (e != nullptr && e->kind == Entity_Variable) { used = (e->flags & EntityFlag_Used) != 0; // NOTE(bill): Make backup just in case } @@ -1812,8 +1812,9 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) error(node, "Iteration over a bit_set of an enum is not allowed runtime type information (RTTI) has been disallowed"); } if (rs->vals.count == 1 && rs->vals[0] && rs->vals[0]->kind == Ast_Ident) { - String name = rs->vals[0]->Ident.token.string; - Entity *found = scope_lookup(ctx->scope, name); + AstIdent *ident = &rs->vals[0]->Ident; + String name = ident->token.string; + Entity *found = scope_lookup(ctx->scope, name, ident->hash); if (found && are_types_identical(found->type, t->BitSet.elem)) { ERROR_BLOCK(); gbString s = expr_to_string(expr); @@ -1857,8 +1858,9 @@ gb_internal void check_range_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) error(node, "#reverse for is not supported for map types, as maps are unordered"); } if (rs->vals.count == 1 && rs->vals[0] && rs->vals[0]->kind == Ast_Ident) { - String name = rs->vals[0]->Ident.token.string; - Entity *found = scope_lookup(ctx->scope, name); + AstIdent *ident = &rs->vals[0]->Ident; + String name = ident->token.string; + Entity *found = scope_lookup(ctx->scope, name, ident->hash); if (found && are_types_identical(found->type, t->Map.key)) { ERROR_BLOCK(); gbString s = expr_to_string(expr); diff --git a/src/checker.cpp b/src/checker.cpp index eb334e147..7da3948dc 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -380,16 +380,25 @@ gb_internal Entity *scope_lookup_current(Scope *s, String const &name) { } -gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **scope_, Entity **entity_) { +gb_global std::atomic in_single_threaded_checker_stage; + +gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **scope_, Entity **entity_, u32 hash) { + bool is_single_threaded = in_single_threaded_checker_stage.load(std::memory_order_relaxed); if (scope != nullptr) { bool gone_thru_proc = false; bool gone_thru_package = false; - StringHashKey key = string_hash_string(name); + StringHashKey key = {}; + if (hash) { + key.hash = hash; + key.string = name; + } else { + key = string_hash_string(name); + } for (Scope *s = scope; s != nullptr; s = s->parent) { Entity **found = nullptr; - rw_mutex_shared_lock(&s->mutex); + if (!is_single_threaded) rw_mutex_shared_lock(&s->mutex); found = string_map_get(&s->elements, key); - rw_mutex_shared_unlock(&s->mutex); + if (!is_single_threaded) rw_mutex_shared_unlock(&s->mutex); if (found) { Entity *e = *found; if (gone_thru_proc) { @@ -424,9 +433,9 @@ gb_internal void scope_lookup_parent(Scope *scope, String const &name, Scope **s if (scope_) *scope_ = nullptr; } -gb_internal Entity *scope_lookup(Scope *s, String const &name) { +gb_internal Entity *scope_lookup(Scope *s, String const &name, u32 hash) { Entity *entity = nullptr; - scope_lookup_parent(s, name, nullptr, &entity); + scope_lookup_parent(s, name, nullptr, &entity, hash); return entity; } @@ -507,11 +516,9 @@ end:; return result; } -gb_global bool in_single_threaded_checker_stage = false; - gb_internal Entity *scope_insert(Scope *s, Entity *entity) { String name = entity->token.string; - if (in_single_threaded_checker_stage) { + if (in_single_threaded_checker_stage.load(std::memory_order_relaxed)) { return scope_insert_with_name_no_mutex(s, name, entity); } else { return scope_insert_with_name(s, name, entity); @@ -853,9 +860,13 @@ gb_internal void check_scope_usage(Checker *c, Scope *scope, u64 vet_flags) { gb_internal void add_dependency(CheckerInfo *info, DeclInfo *d, Entity *e) { - rw_mutex_lock(&d->deps_mutex); - ptr_set_add(&d->deps, e); - rw_mutex_unlock(&d->deps_mutex); + if (in_single_threaded_checker_stage.load(std::memory_order_relaxed)) { + ptr_set_add(&d->deps, e); + } else { + rw_mutex_lock(&d->deps_mutex); + ptr_set_add(&d->deps, e); + rw_mutex_unlock(&d->deps_mutex); + } } gb_internal void add_type_info_dependency(CheckerInfo *info, DeclInfo *d, Type *type) { if (d == nullptr || type == nullptr) { @@ -4958,7 +4969,7 @@ gb_internal void check_single_global_entity(Checker *c, Entity *e, DeclInfo *d) } gb_internal void check_all_global_entities(Checker *c) { - in_single_threaded_checker_stage = true; + in_single_threaded_checker_stage.store(true, std::memory_order_relaxed); // NOTE(bill): This must be single threaded // Don't bother trying @@ -4980,7 +4991,7 @@ gb_internal void check_all_global_entities(Checker *c) { } } - in_single_threaded_checker_stage = false; + in_single_threaded_checker_stage.store(false, std::memory_order_relaxed); } diff --git a/src/checker.hpp b/src/checker.hpp index 968988962..68779c280 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -623,8 +623,8 @@ gb_internal Entity *entity_of_node(Ast *expr); gb_internal Entity *scope_lookup_current(Scope *s, String const &name); -gb_internal Entity *scope_lookup (Scope *s, String const &name); -gb_internal void scope_lookup_parent (Scope *s, String const &name, Scope **scope_, Entity **entity_); +gb_internal Entity *scope_lookup (Scope *s, String const &name, u32 hash=0); +gb_internal void scope_lookup_parent (Scope *s, String const &name, Scope **scope_, Entity **entity_, u32 hash=0); gb_internal Entity *scope_insert (Scope *s, Entity *entity); diff --git a/src/parser.cpp b/src/parser.cpp index a7006dd39..a32494c05 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -750,6 +750,7 @@ gb_internal Ast *ast_matrix_index_expr(AstFile *f, Ast *expr, Token open, Token gb_internal Ast *ast_ident(AstFile *f, Token token) { Ast *result = alloc_ast_node(f, Ast_Ident); result->Ident.token = token; + result->Ident.hash = string_hash(token.string); return result; } diff --git a/src/parser.hpp b/src/parser.hpp index d2dd22667..56447df43 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -396,6 +396,7 @@ struct AstSplitArgs { AST_KIND(Ident, "identifier", struct { \ Token token; \ Entity *entity; \ + u32 hash; \ }) \ AST_KIND(Implicit, "implicit", Token) \ AST_KIND(Uninit, "uninitialized value", Token) \ From a454633774937b6bb7a3d9fcfec38386d3fff1eb Mon Sep 17 00:00:00 2001 From: Tohei Ichikawa Date: Thu, 11 Sep 2025 09:50:31 -0400 Subject: [PATCH 049/162] Fix addObserver methods and add support for new Objc_Block --- core/sys/darwin/Foundation/NSNotification.odin | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/core/sys/darwin/Foundation/NSNotification.odin b/core/sys/darwin/Foundation/NSNotification.odin index f766d0cab..f2f4e819b 100644 --- a/core/sys/darwin/Foundation/NSNotification.odin +++ b/core/sys/darwin/Foundation/NSNotification.odin @@ -50,10 +50,22 @@ NotificationCenter_defaultCenter :: proc "c" () -> ^NotificationCenter { return msgSend(^NotificationCenter, NotificationCenter, "defaultCenter") } -@(objc_type=NotificationCenter, objc_name="addObserver") -NotificationCenter_addObserverName :: proc "c" (self: ^NotificationCenter, name: NotificationName, pObj: ^Object, pQueue: rawptr, block: ^Block) -> ^Object { - return msgSend(^Object, self, "addObserverName:object:queue:block:", name, pObj, pQueue, block) +@(objc_type=NotificationCenter, objc_name="addObserverForName") +NotificationCenter_addObserverForName :: proc{NotificationCenter_addObserverForName_old, NotificationCenter_addObserverForName_new} + +NotificationCenter_addObserverForName_old :: proc "c" (self: ^NotificationCenter, name: NotificationName, pObj: ^Object, pQueue: rawptr, block: ^Block) -> ^Object { + return msgSend(^Object, self, "addObserverForName:object:queue:usingBlock:", name, pObj, pQueue, block) } + +NotificationCenter_addObserverForName_new :: proc "c" (self: ^NotificationCenter, name: NotificationName, pObj: ^Object, pQueue: rawptr, block: ^Objc_Block) -> ^Object { + return msgSend(^Object, self, "addObserverForName:object:queue:usingBlock:", name, pObj, pQueue, block) +} + +@(objc_type=NotificationCenter, objc_name="addObserver") +NotificationCenter_addObserver :: proc "c" (self: ^NotificationCenter, observer: ^Object, selector: SEL, name: NotificationName, object: ^Object) { + msgSend(nil, self, "addObserver:selector:name:object:", observer, selector, name, object) +} + @(objc_type=NotificationCenter, objc_name="removeObserver") NotificationCenter_removeObserver :: proc "c" (self: ^NotificationCenter, pObserver: ^Object) { msgSend(nil, self, "removeObserver:", pObserver) From b9a09cebee336ad07f26be819abef4b8e9d29e48 Mon Sep 17 00:00:00 2001 From: Alex Riedl Date: Thu, 11 Sep 2025 10:55:29 -0500 Subject: [PATCH 050/162] fix for temp_file name prefix being deallocated before being used --- core/os/os2/temp_file.odin | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/core/os/os2/temp_file.odin b/core/os/os2/temp_file.odin index ad20b5706..e3e74bd11 100644 --- a/core/os/os2/temp_file.odin +++ b/core/os/os2/temp_file.odin @@ -18,7 +18,7 @@ create_temp_file :: proc(dir, pattern: string) -> (f: ^File, err: Error) { temp_allocator := TEMP_ALLOCATOR_GUARD({}) dir := dir if dir != "" else temp_directory(temp_allocator) or_return prefix, suffix := _prefix_and_suffix(pattern) or_return - prefix = temp_join_path(dir, prefix) or_return + prefix = temp_join_path(dir, prefix, temp_allocator) or_return rand_buf: [10]byte name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator) @@ -50,7 +50,7 @@ make_directory_temp :: proc(dir, pattern: string, allocator: runtime.Allocator) temp_allocator := TEMP_ALLOCATOR_GUARD({ allocator }) dir := dir if dir != "" else temp_directory(temp_allocator) or_return prefix, suffix := _prefix_and_suffix(pattern) or_return - prefix = temp_join_path(dir, prefix) or_return + prefix = temp_join_path(dir, prefix, temp_allocator) or_return rand_buf: [10]byte name_buf := make([]byte, len(prefix)+len(rand_buf)+len(suffix), temp_allocator) @@ -88,12 +88,10 @@ temp_directory :: proc(allocator: runtime.Allocator) -> (string, Error) { @(private="file") -temp_join_path :: proc(dir, name: string) -> (string, runtime.Allocator_Error) { - temp_allocator := TEMP_ALLOCATOR_GUARD({}) - +temp_join_path :: proc(dir, name: string, allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { if len(dir) > 0 && is_path_separator(dir[len(dir)-1]) { - return concatenate({dir, name}, temp_allocator,) + return concatenate({dir, name}, allocator) } - return concatenate({dir, Path_Separator_String, name}, temp_allocator) + return concatenate({dir, Path_Separator_String, name}, allocator) } From 3e62c2c79a7c3ffe41688733c4c13ead2a43e05b Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Sun, 14 Sep 2025 10:39:33 +0200 Subject: [PATCH 051/162] Add "contextless" to small_array get_safe and get_ptr_safe --- core/container/small_array/small_array.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin index 49d441079..124307c52 100644 --- a/core/container/small_array/small_array.odin +++ b/core/container/small_array/small_array.odin @@ -169,7 +169,7 @@ Output: x */ -get_safe :: proc(a: $A/Small_Array($N, $T), index: int) -> (T, bool) #no_bounds_check { +get_safe :: proc "contextless" (a: $A/Small_Array($N, $T), index: int) -> (T, bool) #no_bounds_check { if index < 0 || index >= a.len { return {}, false } @@ -183,11 +183,11 @@ Get a pointer to the item at the specified position. - `a`: A pointer to the small-array - `index`: The position of the item to get -**Returns** +**Returns** - the pointer to the element at the specified position - true if element exists, false otherwise */ -get_ptr_safe :: proc(a: ^$A/Small_Array($N, $T), index: int) -> (^T, bool) #no_bounds_check { +get_ptr_safe :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int) -> (^T, bool) #no_bounds_check { if index < 0 || index >= a.len { return {}, false } From 22261f9e719a012efd871fe04138c3b6e4c8e88c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Sep 2025 17:21:33 +0100 Subject: [PATCH 052/162] Fix `temporary_allocator` --- src/common_memory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common_memory.cpp b/src/common_memory.cpp index 0b95e1242..3658c0ffe 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -481,7 +481,7 @@ gb_internal gbAllocator permanent_allocator() { } gb_internal gbAllocator temporary_allocator() { - return {thread_arena_allocator_proc, cast(void *)cast(uintptr)ThreadArena_Permanent}; + return {thread_arena_allocator_proc, cast(void *)cast(uintptr)ThreadArena_Temporary}; } From 7db57e2d9c7d6032334d73f1056478fa23ee4b3a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Sep 2025 17:24:22 +0100 Subject: [PATCH 053/162] Temporarily disable `TEMPORARY_ALLOCATOR_GUARD` --- src/common_memory.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common_memory.cpp b/src/common_memory.cpp index 3658c0ffe..f169d7974 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -488,8 +488,8 @@ gb_internal gbAllocator temporary_allocator() { #define TEMP_ARENA_GUARD(arena) ArenaTempGuard GB_DEFER_3(_arena_guard_){arena} -// #define TEMPORARY_ALLOCATOR_GUARD() -#define TEMPORARY_ALLOCATOR_GUARD() TEMP_ARENA_GUARD(get_arena(ThreadArena_Temporary)) +// #define TEMPORARY_ALLOCATOR_GUARD() TEMP_ARENA_GUARD(get_arena(ThreadArena_Temporary)) +#define TEMPORARY_ALLOCATOR_GUARD() #define PERMANENT_ALLOCATOR_GUARD() From 11be0cb4ab41c52591a90ca07e7a04062487369e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Sep 2025 17:37:17 +0100 Subject: [PATCH 054/162] Use `permanent_allocator()` instead of `temporary_allocator()` temporarily to fix a bug --- src/common_memory.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/common_memory.cpp b/src/common_memory.cpp index f169d7974..addd43687 100644 --- a/src/common_memory.cpp +++ b/src/common_memory.cpp @@ -481,7 +481,8 @@ gb_internal gbAllocator permanent_allocator() { } gb_internal gbAllocator temporary_allocator() { - return {thread_arena_allocator_proc, cast(void *)cast(uintptr)ThreadArena_Temporary}; + // return {thread_arena_allocator_proc, cast(void *)cast(uintptr)ThreadArena_Temporary}; + return permanent_allocator(); } From 3d66625de0b3fe9cceb187b4c7ff8bb767d7babc Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Mon, 15 Sep 2025 14:40:58 +0200 Subject: [PATCH 055/162] Zero memory in small_array.resize and add non_zero_resize --- core/container/small_array/small_array.odin | 53 +++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin index 49d441079..d2f0f8130 100644 --- a/core/container/small_array/small_array.odin +++ b/core/container/small_array/small_array.odin @@ -2,6 +2,7 @@ package container_small_array import "base:builtin" import "base:runtime" +import "core:mem" _ :: runtime /* @@ -250,6 +251,8 @@ set :: proc "contextless" (a: ^$A/Small_Array($N, $T), index: int, item: T) { /* Tries to resize the small-array to the specified length. +The memory of added elements will be zeroed out. + The new length will be: - `length` if `length` <= capacity - capacity if length > capacity @@ -259,7 +262,7 @@ The new length will be: - `length`: The new desired length Example: - + import "core:container/small_array" import "core:fmt" @@ -269,7 +272,7 @@ Example: small_array.push_back(&a, 1) small_array.push_back(&a, 2) fmt.println(small_array.slice(&a)) - + small_array.resize(&a, 1) fmt.println(small_array.slice(&a)) @@ -278,12 +281,56 @@ Example: } Output: - + [1, 2] [1] [1, 2, 0, 0, 0] */ resize :: proc "contextless" (a: ^$A/Small_Array, length: int) { + prev_len := a.len + a.len = min(length, builtin.len(a.data)) + if prev_len < a.len { + mem.zero_slice(a.data[prev_len:a.len]) + } +} + +/* +Tries to resize the small-array to the specified length. + +The new length will be: + - `length` if `length` <= capacity + - capacity if length > capacity + +**Inputs** +- `a`: A pointer to the small-array +- `length`: The new desired length + +Example: + + import "core:container/small_array" + import "core:fmt" + + resize_example :: proc() { + a: small_array.Small_Array(5, int) + + small_array.push_back(&a, 1) + small_array.push_back(&a, 2) + fmt.println(small_array.slice(&a)) + + small_array.resize(&a, 1) + fmt.println(small_array.slice(&a)) + + small_array.resize(&a, 100) + fmt.println(small_array.slice(&a)) + } + +Output: + + [1, 2] + [1] + [1, 2, 0, 0, 0] +*/ +non_zero_resize :: proc "contextless" (a: ^$A/Small_Array, length: int) { a.len = min(length, builtin.len(a.data)) } From 2c3d5fe456294e2d9e9b56883b4ba44586d0baa9 Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Mon, 15 Sep 2025 14:50:33 +0200 Subject: [PATCH 056/162] Add small array resize tests --- .../core/container/test_core_small_array.odin | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/core/container/test_core_small_array.odin b/tests/core/container/test_core_small_array.odin index 21f35f112..86342c6a5 100644 --- a/tests/core/container/test_core_small_array.odin +++ b/tests/core/container/test_core_small_array.odin @@ -54,6 +54,28 @@ test_small_array_push_back_elems :: proc(t: ^testing.T) { testing.expect(t, slice_equal(small_array.slice(&array), []int { 1, 2 })) } +@(test) +test_small_array_resize :: proc(t: ^testing.T) { + + array: small_array.Small_Array(4, int) + + for i in 0..<4 { + small_array.append(&array, i+1) + } + testing.expect(t, slice_equal(small_array.slice(&array), []int{1, 2, 3, 4}), "Expected to initialize the array with 1, 2, 3, 4") + + small_array.clear(&array) + testing.expect(t, slice_equal(small_array.slice(&array), []int{}), "Expected to clear the array") + + small_array.non_zero_resize(&array, 4) + testing.expect(t, slice_equal(small_array.slice(&array), []int{1, 2, 3, 4}), "Expected non_zero_resize to set length 4 with previous values") + + small_array.clear(&array) + small_array.resize(&array, 4) + testing.expect(t, slice_equal(small_array.slice(&array), []int{0, 0, 0, 0}), "Expected resize to set length 4 with zeroed values") +} + + slice_equal :: proc(a, b: []int) -> bool { if len(a) != len(b) { return false From 7adc33d5a4c4ba9a84ce7ff3f99b3db65db1eab1 Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Mon, 15 Sep 2025 14:56:46 +0200 Subject: [PATCH 057/162] Add `@require` to core:mem import in small_array --- core/container/small_array/small_array.odin | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin index d2f0f8130..31cc8315e 100644 --- a/core/container/small_array/small_array.odin +++ b/core/container/small_array/small_array.odin @@ -1,9 +1,8 @@ package container_small_array import "base:builtin" -import "base:runtime" -import "core:mem" -_ :: runtime +@require import "base:runtime" +@require import "core:mem" /* A fixed-size stack-allocated array operated on in a dynamic fashion. From b986c534a33cb10c99653b8f7eba63f260ad938f Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Mon, 15 Sep 2025 15:01:20 +0200 Subject: [PATCH 058/162] Replace `mem.zero_slice` with `intrinsics.mem_zero` in `small_array.resize` --- core/container/small_array/small_array.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin index 31cc8315e..23d5ad6b7 100644 --- a/core/container/small_array/small_array.odin +++ b/core/container/small_array/small_array.odin @@ -1,8 +1,8 @@ package container_small_array import "base:builtin" +@require import "base:intrinsics" @require import "base:runtime" -@require import "core:mem" /* A fixed-size stack-allocated array operated on in a dynamic fashion. @@ -285,11 +285,11 @@ Output: [1] [1, 2, 0, 0, 0] */ -resize :: proc "contextless" (a: ^$A/Small_Array, length: int) { +resize :: proc "contextless" (a: ^$A/Small_Array($N, $T), length: int) { prev_len := a.len a.len = min(length, builtin.len(a.data)) if prev_len < a.len { - mem.zero_slice(a.data[prev_len:a.len]) + intrinsics.mem_zero(&a.data[prev_len], size_of(T)*(a.len-prev_len)) } } From 40c8f45a81f33f617ebf276658f465935eb843f6 Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Mon, 15 Sep 2025 15:15:44 +0200 Subject: [PATCH 059/162] Correct small_array resize examples --- core/container/small_array/small_array.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin index 23d5ad6b7..b7c72ed7a 100644 --- a/core/container/small_array/small_array.odin +++ b/core/container/small_array/small_array.odin @@ -283,7 +283,7 @@ Output: [1, 2] [1] - [1, 2, 0, 0, 0] + [1, 0, 0, 0, 0] */ resize :: proc "contextless" (a: ^$A/Small_Array($N, $T), length: int) { prev_len := a.len @@ -316,10 +316,10 @@ Example: small_array.push_back(&a, 2) fmt.println(small_array.slice(&a)) - small_array.resize(&a, 1) + small_array.non_zero_resize(&a, 1) fmt.println(small_array.slice(&a)) - small_array.resize(&a, 100) + small_array.non_zero_resize(&a, 100) fmt.println(small_array.slice(&a)) } From e163c20a02a27646ba46fc094a077bb8118d2e1e Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Mon, 15 Sep 2025 15:29:17 +0200 Subject: [PATCH 060/162] Correct set_example in small_array --- core/container/small_array/small_array.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/container/small_array/small_array.odin b/core/container/small_array/small_array.odin index b7c72ed7a..840a8a6fa 100644 --- a/core/container/small_array/small_array.odin +++ b/core/container/small_array/small_array.odin @@ -231,7 +231,7 @@ Example: fmt.println(small_array.slice(&a)) // resizing makes the change visible - small_array.resize(&a, 100) + small_array.non_zero_resize(&a, 100) fmt.println(small_array.slice(&a)) } @@ -309,7 +309,7 @@ Example: import "core:container/small_array" import "core:fmt" - resize_example :: proc() { + non_zero_resize :: proc() { a: small_array.Small_Array(5, int) small_array.push_back(&a, 1) From 403ca2fb2ea97d5567a70aea43336c87cba7a348 Mon Sep 17 00:00:00 2001 From: Tohei Ichikawa Date: Mon, 15 Sep 2025 20:40:20 -0400 Subject: [PATCH 061/162] Improve type inferencing of literals when calling proc groups --- src/check_expr.cpp | 6 + .../test_proc_group_type_inference.odin | 142 ++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 tests/internal/test_proc_group_type_inference.odin diff --git a/src/check_expr.cpp b/src/check_expr.cpp index d2505c047..417d1b9a4 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6977,6 +6977,10 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, array_unordered_remove(&procs, proc_index); continue; } + if (!pt->Proc.variadic && max_arg_count != ISIZE_MAX && param_count < max_arg_count) { + array_unordered_remove(&procs, proc_index); + continue; + } proc_index++; } } @@ -7014,6 +7018,8 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, { // NOTE(bill, 2019-07-13): This code is used to improve the type inference for procedure groups // where the same positional parameter has the same type value (and ellipsis) + + //TODO: get rid of proc_arg_count. make lhs as long as longest proc with most params. watch out for null safety isize proc_arg_count = -1; for (Entity *p : procs) { Type *pt = base_type(p->type); diff --git a/tests/internal/test_proc_group_type_inference.odin b/tests/internal/test_proc_group_type_inference.odin new file mode 100644 index 000000000..08ff2a3c5 --- /dev/null +++ b/tests/internal/test_proc_group_type_inference.odin @@ -0,0 +1,142 @@ +#+feature dynamic-literals + +package test_internal + +import "core:testing" + +@test +test_type_inference_on_literals_for_various_parameters_combinations :: proc(t: ^testing.T) { + Bit_Set :: bit_set[enum{A, B, C}] + group :: proc{proc_0, proc_1, proc_2, proc_3, proc_4, proc_5} + proc_0 :: proc() -> int { return 0 } + proc_1 :: proc(Bit_Set) -> int { return 1 } + proc_2 :: proc(int, Bit_Set) -> int { return 2 } + proc_3 :: proc(f32, Bit_Set) -> int { return 3 } + proc_4 :: proc(int, int, Bit_Set) -> int { return 4 } + proc_5 :: proc(Bit_Set, int, int, int) -> int { return 5 } + + testing.expect_value(t, group({.A}), 1) + testing.expect_value(t, group(9, {.A}), 2) + testing.expect_value(t, group(3.14, {.A}), 3) + testing.expect_value(t, group(9, 9, {.A}), 4) + testing.expect_value(t, group({.A}, 9, 9, 9), 5) +} + +@test +test_type_inference_on_literals_with_default_args :: proc(t: ^testing.T) { + { + Bit_Set :: bit_set[enum{A, B, C}] + proc_nil :: proc() { } + proc_default_arg :: proc(a: Bit_Set={.A}) -> Bit_Set { return a } + group :: proc{proc_nil, proc_default_arg} + + testing.expect_value(t, group(Bit_Set{.A}), Bit_Set{.A}) + testing.expect_value(t, group({.A}), Bit_Set{.A}) + } + { + Bit_Set :: bit_set[enum{A, B, C}] + proc_1 :: proc(a: Bit_Set={.A}) -> int { return 1 } + proc_2 :: proc(a: Bit_Set={.B}, b: Bit_Set={.C}) -> int { return 2 } + group :: proc{proc_1, proc_2} + + testing.expect_value(t, group(), 2) + testing.expect_value(t, group(Bit_Set{.A}), 2) + testing.expect_value(t, group({.A}), 2) + testing.expect_value(t, group({.B}, {.C}), 2) + } +} + +@test +test_type_inference_on_literals_for_various_types :: proc(t: ^testing.T) { + proc_nil :: proc() { } + + proc_array :: proc(a: [3]f32) -> [3]f32 { return a } + group_array :: proc{proc_nil, proc_array} + testing.expect_value(t, group_array([3]f32{1.1, 2.2, 3.3}), [3]f32{1.1, 2.2, 3.3}) + testing.expect_value(t, group_array({1.1, 2.2, 3.3}), [3]f32{1.1, 2.2, 3.3}) + testing.expect_value(t, group_array({0=1.1, 1=2.2, 2=3.3}), [3]f32{1.1, 2.2, 3.3}) + testing.expect_value(t, group_array({}), [3]f32{}) + + proc_slice_u8 :: proc(a: []u8) -> []u8 { return a } + group_slice_u8 :: proc{proc_nil, proc_slice_u8} + testing.expect_value(t, len(group_slice_u8([]u8{1, 2, 3})), 3) + testing.expect_value(t, len(group_slice_u8({1, 2, 3})), 3) + testing.expect_value(t, len(group_slice_u8({0=1, 1=2, 2=3})), 3) + testing.expect_value(t, len(group_slice_u8({})), 0) + testing.expect_value(t, group_slice_u8(nil) == nil, true) + + proc_dynamic_array :: proc(a: [dynamic]u8) -> [dynamic]u8 { return a } + group_dynamic_array :: proc{proc_nil, proc_dynamic_array} + testing.expect_value(t, len(group_dynamic_array([dynamic]u8{1, 2, 3})), 3) + testing.expect_value(t, len(group_dynamic_array({1, 2, 3})), 3) + testing.expect_value(t, len(group_dynamic_array({0=1, 1=2, 2=3})), 3) + testing.expect_value(t, len(group_dynamic_array({})), 0) + testing.expect_value(t, group_dynamic_array(nil) == nil, true) + + Enum :: enum{A, B, C} + proc_enum :: proc(a: Enum) -> Enum { return a } + group_enum :: proc{proc_nil, proc_enum} + testing.expect_value(t, group_enum(Enum.A), Enum.A) + testing.expect_value(t, group_enum(.A), Enum.A) + + proc_enumerated_array :: proc(a: [Enum]u8) -> [Enum]u8 { return a } + group_enumerated_array :: proc{proc_nil, proc_enumerated_array} + testing.expect_value(t, group_enumerated_array([Enum]u8{.A=1, .B=2, .C=3}), [Enum]u8{.A=1, .B=2, .C=3}) + testing.expect_value(t, group_enumerated_array({.A=1, .B=2, .C=3}), [Enum]u8{.A=1, .B=2, .C=3}) + + Bit_Set :: bit_set[enum{A, B, C}] + proc_bit_set :: proc(a: Bit_Set) -> Bit_Set { return a } + group_bit_set :: proc{proc_nil, proc_bit_set} + testing.expect_value(t, group_bit_set(Bit_Set{.A}), Bit_Set{.A}) + testing.expect_value(t, group_bit_set({.A}), Bit_Set{.A}) + testing.expect_value(t, group_bit_set({}), Bit_Set{}) + + Struct :: struct{a: int, b: int, c: int} + proc_struct :: proc(a: Struct) -> Struct { return a } + group_struct :: proc{proc_nil, proc_struct} + testing.expect_value(t, group_struct(Struct{a = 9}), Struct{a = 9}) + testing.expect_value(t, group_struct({a = 9}), Struct{a = 9}) + testing.expect_value(t, group_struct({}), Struct{}) + + Raw_Union :: struct #raw_union{int_: int, f32_: f32} + proc_raw_union :: proc(a: Raw_Union) -> Raw_Union { return a } + group_raw_union :: proc{proc_nil, proc_raw_union} + testing.expect_value(t, group_raw_union(Raw_Union{int_ = 9}).int_, 9) + testing.expect_value(t, group_raw_union({int_ = 9}).int_, 9) + testing.expect_value(t, group_raw_union({}).int_, 0) + + Union :: union{int, f32} + proc_union :: proc(a: Union) -> Union { return a } + group_union :: proc{proc_nil, proc_union} + testing.expect_value(t, group_union(int(9)).(int), 9) + testing.expect_value(t, group_union({}).(int), 0) + + proc_map :: proc(a: map[u8]u8) -> map[u8]u8 { return a } + group_map :: proc{proc_nil, proc_map} + testing.expect_value(t, len(group_map(map[u8]u8{1=1, 2=2})), 2) + testing.expect_value(t, len(group_map({1=1, 2=2})), 2) + testing.expect_value(t, len(group_map({})), 0) + testing.expect_value(t, group_map(nil) == nil, true) + + Bit_Field :: bit_field u16 {a: u8|4, b: u8|4, c: u8|4} + proc_bit_field :: proc(a: Bit_Field) -> Bit_Field { return a } + group_bit_field :: proc{proc_nil, proc_bit_field} + testing.expect_value(t, group_bit_field(Bit_Field{a = 1}), Bit_Field{a = 1}) + testing.expect_value(t, group_bit_field({a = 1}), Bit_Field{a = 1}) + testing.expect_value(t, group_bit_field({}), Bit_Field{}) + + SOA_Array :: #soa[2]struct{int, int} + proc_soa_array :: proc(a: SOA_Array) -> SOA_Array { return a } + group_soa_array :: proc{proc_nil, proc_soa_array} + testing.expect_value(t, len(group_soa_array(SOA_Array{{}, {}})), 2) + testing.expect_value(t, len(group_soa_array({struct{int, int}{1, 2}, struct{int, int}{1, 2}})), 1) + testing.expect_value(t, len(group_soa_array({})), 0) + testing.expect_value(t, len(soa_zip(a=[]int{1, 2}, b=[]int{3, 4})), 2) + + proc_matrix :: proc(a: matrix[2,2]f32) -> matrix[2,2]f32 { return a } + group_matrix :: proc{proc_nil, proc_matrix} + testing.expect_value(t, group_matrix(matrix[2,2]f32{1, 2, 3, 4}), matrix[2,2]f32{1, 2, 3, 4}) + testing.expect_value(t, group_matrix(1), (matrix[2,2]f32)(1)) + testing.expect_value(t, group_matrix({1, 2, 3, 4}), matrix[2,2]f32{1, 2, 3, 4}) + testing.expect_value(t, group_matrix({}), matrix[2,2]f32{}) +} From 5e71ba44562630c50fd04fb50513c5c7758e16f5 Mon Sep 17 00:00:00 2001 From: Tohei Ichikawa Date: Tue, 16 Sep 2025 10:57:54 -0400 Subject: [PATCH 062/162] Remove an outdated TODO --- src/check_expr.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 417d1b9a4..9a892245c 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7018,8 +7018,6 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, { // NOTE(bill, 2019-07-13): This code is used to improve the type inference for procedure groups // where the same positional parameter has the same type value (and ellipsis) - - //TODO: get rid of proc_arg_count. make lhs as long as longest proc with most params. watch out for null safety isize proc_arg_count = -1; for (Entity *p : procs) { Type *pt = base_type(p->type); From 3e7395eba60d147e25183961d93d00c806be244c Mon Sep 17 00:00:00 2001 From: xenobas Date: Tue, 16 Sep 2025 21:40:57 +0100 Subject: [PATCH 063/162] fix: fix segfault on string_to_string16 --- src/string.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/string.cpp b/src/string.cpp index 2087a5fee..9c08114a7 100644 --- a/src/string.cpp +++ b/src/string.cpp @@ -633,23 +633,28 @@ gb_internal String normalize_path(gbAllocator a, String const &path, String cons return WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, widechar_input, input_length, output, output_size, nullptr, nullptr); } #elif defined(GB_SYSTEM_UNIX) || defined(GB_SYSTEM_OSX) - - #include + #include gb_internal int convert_multibyte_to_widechar(char const *multibyte_input, usize input_length, wchar_t *output, usize output_size) { - iconv_t conv = iconv_open("WCHAR_T", "UTF-8"); - size_t result = iconv(conv, cast(char **)&multibyte_input, &input_length, cast(char **)&output, &output_size); - iconv_close(conv); + String string = copy_string(heap_allocator(), make_string(cast(u8 const*)multibyte_input, input_length)); /* Guarantee NULL terminator */ + u8* input = string.text; - return cast(int)result; + mbstate_t ps = { 0 }; + size_t result = mbsrtowcs(output, cast(const char**)&input, output_size, &ps); + + gb_free(heap_allocator(), string.text); + return (result == (size_t)-1) ? -1 : (int)result; } gb_internal int convert_widechar_to_multibyte(wchar_t const *widechar_input, usize input_length, char* output, usize output_size) { - iconv_t conv = iconv_open("UTF-8", "WCHAR_T"); - size_t result = iconv(conv, cast(char**) &widechar_input, &input_length, cast(char **)&output, &output_size); - iconv_close(conv); + String string = copy_string(heap_allocator(), make_string(cast(u8 const*)widechar_input, input_length)); /* Guarantee NULL terminator */ + u8* input = string.text; - return cast(int)result; + mbstate_t ps = { 0 }; + size_t result = wcsrtombs(output, cast(const wchar_t**)&input, output_size, &ps); + + gb_free(heap_allocator(), string.text); + return (result == (size_t)-1) ? -1 : (int)result; } #else #error Implement system From 710533975e6da554b943bc24fb6e38ba22fe959a Mon Sep 17 00:00:00 2001 From: rationalcoder Date: Tue, 16 Sep 2025 16:31:10 -0500 Subject: [PATCH 064/162] Fix out-of-band allocations in dynamic arenas --- core/mem/allocators.odin | 16 ++++++++-------- tests/core/mem/test_mem_dynamic_pool.odin | 15 ++++++++------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/core/mem/allocators.odin b/core/mem/allocators.odin index 59fe72fbb..5b0b178f8 100644 --- a/core/mem/allocators.odin +++ b/core/mem/allocators.odin @@ -1819,18 +1819,18 @@ memory region. */ @(require_results) dynamic_arena_alloc_bytes_non_zeroed :: proc(a: ^Dynamic_Arena, size: int, loc := #caller_location) -> ([]byte, Allocator_Error) { - n := align_formula(size, a.alignment) - if n > a.block_size { - return nil, .Invalid_Argument - } - if n >= a.out_band_size { - assert(a.block_allocator.procedure != nil, "Backing block allocator must be initialized", loc=loc) - memory, err := alloc_bytes_non_zeroed(a.block_size, a.alignment, a.block_allocator, loc) + if size >= a.out_band_size { + assert(a.out_band_allocations.allocator.procedure != nil, "Backing array allocator must be initialized", loc=loc) + memory, err := alloc_bytes_non_zeroed(size, a.alignment, a.out_band_allocations.allocator, loc) if memory != nil { append(&a.out_band_allocations, raw_data(memory), loc = loc) } return memory, err } + n := align_formula(size, a.alignment) + if n > a.block_size { + return nil, .Invalid_Argument + } if a.bytes_left < n { err := _dynamic_arena_cycle_new_block(a, loc) if err != nil { @@ -1867,7 +1867,7 @@ dynamic_arena_reset :: proc(a: ^Dynamic_Arena, loc := #caller_location) { } clear(&a.used_blocks) for allocation in a.out_band_allocations { - free(allocation, a.block_allocator, loc=loc) + free(allocation, a.out_band_allocations.allocator, loc=loc) } clear(&a.out_band_allocations) a.bytes_left = 0 // Make new allocations call `_dynamic_arena_cycle_new_block` again. diff --git a/tests/core/mem/test_mem_dynamic_pool.odin b/tests/core/mem/test_mem_dynamic_pool.odin index fa204d3b1..82cee89af 100644 --- a/tests/core/mem/test_mem_dynamic_pool.odin +++ b/tests/core/mem/test_mem_dynamic_pool.odin @@ -44,11 +44,11 @@ expect_pool_allocation :: proc(t: ^testing.T, expected_used_bytes, num_bytes, al testing.expect(t, pool.used_blocks == nil) } -expect_pool_allocation_out_of_band :: proc(t: ^testing.T, num_bytes, out_band_size: int) { +expect_pool_allocation_out_of_band :: proc(t: ^testing.T, num_bytes, block_size, out_band_size: int) { testing.expect(t, num_bytes >= out_band_size, "Sanity check failed, your test call is flawed! Make sure that num_bytes >= out_band_size!") pool: mem.Dynamic_Pool - mem.dynamic_pool_init(&pool, out_band_size = out_band_size) + mem.dynamic_pool_init(&pool, block_size = block_size, out_band_size = out_band_size) pool_allocator := mem.dynamic_pool_allocator(&pool) element, err := mem.alloc(num_bytes, allocator = pool_allocator) @@ -69,14 +69,15 @@ test_dynamic_pool_alloc_aligned :: proc(t: ^testing.T) { @(test) test_dynamic_pool_alloc_unaligned :: proc(t: ^testing.T) { - expect_pool_allocation(t, expected_used_bytes = 8, num_bytes=1, alignment=8) - expect_pool_allocation(t, expected_used_bytes = 16, num_bytes=9, alignment=8) + expect_pool_allocation(t, expected_used_bytes = 8, num_bytes = 1, alignment = 8) + expect_pool_allocation(t, expected_used_bytes = 16, num_bytes = 9, alignment = 8) } @(test) test_dynamic_pool_alloc_out_of_band :: proc(t: ^testing.T) { - expect_pool_allocation_out_of_band(t, num_bytes = 128, out_band_size = 128) - expect_pool_allocation_out_of_band(t, num_bytes = 129, out_band_size = 128) + expect_pool_allocation_out_of_band(t, num_bytes = 128, block_size = 512, out_band_size = 128) + expect_pool_allocation_out_of_band(t, num_bytes = 129, block_size = 512, out_band_size = 128) + expect_pool_allocation_out_of_band(t, num_bytes = 513, block_size = 512, out_band_size = 128) } @(test) @@ -98,4 +99,4 @@ intentionally_leaky_test :: proc(t: ^testing.T) { leak_verifier :: proc(t: ^testing.T, ta: ^mem.Tracking_Allocator) { testing.expect_value(t, len(ta.allocation_map), 1) testing.expect_value(t, len(ta.bad_free_array), 1) -} \ No newline at end of file +} From 738a72943bdb9d0998b11d38efb5300cd02d8190 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Sep 2025 15:04:16 +0100 Subject: [PATCH 065/162] Try moving parapoly procs into a separate module when doing weak monomorphization --- src/build_settings.cpp | 1 + src/llvm_backend.cpp | 2 +- src/llvm_backend.hpp | 2 ++ src/llvm_backend_debug.cpp | 2 +- src/llvm_backend_general.cpp | 63 +++++++++++++++++++++++++++++++----- src/llvm_backend_proc.cpp | 2 +- src/main.cpp | 5 +++ 7 files changed, 66 insertions(+), 11 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 077660f10..867c80ac1 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -554,6 +554,7 @@ struct BuildContext { bool internal_no_inline; bool internal_by_value; + bool internal_weak_monomorphization; bool no_threaded_checker; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 11b979774..2bc18872e 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2145,7 +2145,7 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker lbModule *m = &gen->default_module; if (USE_SEPARATE_MODULES) { - m = lb_module_of_entity(gen, e); + m = lb_module_of_entity(gen, e, m); } GB_ASSERT(m != nullptr); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index cc3dcaa4a..e4b8c07fa 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -150,6 +150,8 @@ struct lbModule { struct lbGenerator *gen; LLVMTargetMachineRef target_machine; + lbModule *polymorphic_module; + CheckerInfo *info; AstPackage *pkg; // possibly associated AstFile *file; // possibly associated diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index 182920fc7..3372165f2 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -1327,7 +1327,7 @@ gb_internal void lb_add_debug_info_for_global_constant_from_entity(lbGenerator * } lbModule *m = &gen->default_module; if (USE_SEPARATE_MODULES) { - m = lb_module_of_entity(gen, e); + m = lb_module_of_entity(gen, e, m); } GB_ASSERT(m != nullptr); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 49a04641c..4907e114d 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -46,6 +46,12 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) { } module_name = gb_string_appendc(module_name, "builtin"); } + if (m->polymorphic_module == m) { + if (gb_string_length(module_name)) { + module_name = gb_string_appendc(module_name, "-"); + } + module_name = gb_string_appendc(module_name, "$parapoly"); + } m->module_name = module_name; m->ctx = LLVMContextCreate(); @@ -147,17 +153,42 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { m->gen = gen; map_set(&gen->modules, cast(void *)pkg, m); lb_init_module(m, c); + + if (build_context.internal_weak_monomorphization) { + auto pm = gb_alloc_item(permanent_allocator(), lbModule); + pm->pkg = pkg; + pm->gen = gen; + m->polymorphic_module = pm; + pm->polymorphic_module = pm; + + map_set(&gen->modules, cast(void *)pm, pm); // point to itself just add it to the list + + lb_init_module(pm, c); + } + if (!module_per_file) { continue; } // NOTE(bill): Probably per file is not a good idea, so leave this for later for (AstFile *file : pkg->files) { - auto m = gb_alloc_item(permanent_allocator(), lbModule); + auto m = gb_alloc_item(permanent_allocator(), lbModule); m->file = file; - m->pkg = pkg; - m->gen = gen; + m->pkg = pkg; + m->gen = gen; map_set(&gen->modules, cast(void *)file, m); lb_init_module(m, c); + + + if (build_context.internal_weak_monomorphization) { + auto pm = gb_alloc_item(permanent_allocator(), lbModule); + pm->file = file; + pm->gen = gen; + pm->polymorphic_module = pm; + + map_set(&gen->modules, cast(void *)pm, pm); // point to itself just add it to the list + + lb_init_module(pm, c); + } } } } @@ -403,9 +434,9 @@ gb_internal lbModule *lb_module_of_expr(lbGenerator *gen, Ast *expr) { return &gen->default_module; } -gb_internal lbModule *lb_module_of_entity(lbGenerator *gen, Entity *e) { - GB_ASSERT(e != nullptr); +gb_internal lbModule *lb_module_of_entity_internal(lbGenerator *gen, Entity *e, lbModule *curr_module) { lbModule **found = nullptr; + if (e->kind == Entity_Procedure && e->decl_info && e->decl_info->code_gen_module) { @@ -428,6 +459,22 @@ gb_internal lbModule *lb_module_of_entity(lbGenerator *gen, Entity *e) { return &gen->default_module; } + +gb_internal lbModule *lb_module_of_entity(lbGenerator *gen, Entity *e, lbModule *curr_module) { + GB_ASSERT(e != nullptr); + GB_ASSERT(curr_module != nullptr); + lbModule *m = lb_module_of_entity_internal(gen, e, curr_module); + + if (USE_SEPARATE_MODULES && build_context.internal_weak_monomorphization) { + if (e->kind == Entity_Procedure && e->Procedure.generated_from_polymorphic) { + if (m->polymorphic_module) { + return m->polymorphic_module; + } + } + } + return m; +} + gb_internal lbAddr lb_addr(lbValue addr) { lbAddr v = {lbAddr_Default, addr}; return v; @@ -2914,7 +2961,7 @@ gb_internal lbValue lb_find_ident(lbProcedure *p, lbModule *m, Entity *e, Ast *e return lb_find_procedure_value_from_entity(m, e); } if (USE_SEPARATE_MODULES) { - lbModule *other_module = lb_module_of_entity(m->gen, e); + lbModule *other_module = lb_module_of_entity(m->gen, e, m); if (other_module != m) { String name = lb_get_entity_name(other_module, e); @@ -2962,7 +3009,7 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) lbModule *other_module = m; if (USE_SEPARATE_MODULES) { - other_module = lb_module_of_entity(gen, e); + other_module = lb_module_of_entity(gen, e, m); } if (other_module == m) { debugf("Missing Procedure (lb_find_procedure_value_from_entity): %.*s module %p\n", LIT(e->token.string), m); @@ -3145,7 +3192,7 @@ gb_internal lbValue lb_find_value_from_entity(lbModule *m, Entity *e) { } if (USE_SEPARATE_MODULES) { - lbModule *other_module = lb_module_of_entity(m->gen, e); + lbModule *other_module = lb_module_of_entity(m->gen, e, m); bool is_external = other_module != m; if (!is_external) { diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 06829efab..20d627fa2 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -84,7 +84,7 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i String link_name = {}; if (ignore_body) { - lbModule *other_module = lb_module_of_entity(m->gen, entity); + lbModule *other_module = lb_module_of_entity(m->gen, entity, m); link_name = lb_get_entity_name(other_module, entity); } else { link_name = lb_get_entity_name(m, entity); diff --git a/src/main.cpp b/src/main.cpp index 184b1eaac..130c3f31d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -403,6 +403,7 @@ enum BuildFlagKind { BuildFlag_InternalCached, BuildFlag_InternalNoInline, BuildFlag_InternalByValue, + BuildFlag_InternalWeakMonomorphization, BuildFlag_Tilde, @@ -626,6 +627,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_InternalCached, str_lit("internal-cached"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalNoInline, str_lit("internal-no-inline"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalByValue, str_lit("internal-by-value"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_InternalWeakMonomorphization, str_lit("internal-weak-monomorphization"), BuildFlagParam_None, Command_all); #if ALLOW_TILDE add_flag(&build_flags, BuildFlag_Tilde, str_lit("tilde"), BuildFlagParam_None, Command__does_build); @@ -1584,6 +1586,9 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_InternalByValue: build_context.internal_by_value = true; break; + case BuildFlag_InternalWeakMonomorphization: + build_context.internal_weak_monomorphization = true; + break; case BuildFlag_Tilde: build_context.tilde_backend = true; From 0f307fbc5d27a9b0fa880dda7a2754d2d1ca7bfd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Sep 2025 15:34:19 +0100 Subject: [PATCH 066/162] Use multiple modules per file in package runtime --- src/llvm_backend.cpp | 50 +++++++++++------------------------- src/llvm_backend.hpp | 2 ++ src/llvm_backend_general.cpp | 8 ++++-- 3 files changed, 23 insertions(+), 37 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 2bc18872e..c6a372ac2 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2303,6 +2303,7 @@ gb_internal WORKER_TASK_PROC(lb_llvm_function_pass_per_module) { struct lbLLVMModulePassWorkerData { lbModule *m; LLVMTargetMachineRef target_machine; + bool do_threading; }; gb_internal WORKER_TASK_PROC(lb_llvm_module_pass_worker_proc) { @@ -2386,6 +2387,13 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_pass_worker_proc) { return 1; } #endif + + if (wd->do_threading) { + thread_pool_add_task(lb_llvm_module_verification_worker_proc, wd->m); + } else { + lb_llvm_module_verification_worker_proc(wd->m); + } + return 0; } @@ -2468,19 +2476,16 @@ gb_internal void lb_llvm_function_passes(lbGenerator *gen, bool do_threading) { } -gb_internal void lb_llvm_module_passes(lbGenerator *gen, bool do_threading) { +gb_internal void lb_llvm_module_passes_and_verification(lbGenerator *gen, bool do_threading) { 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; + wd->do_threading = true; - if (do_threading) { - thread_pool_add_task(lb_llvm_module_pass_worker_proc, wd); - } else { - lb_llvm_module_pass_worker_proc(wd); - } + thread_pool_add_task(lb_llvm_module_pass_worker_proc, wd); } thread_pool_wait(); } else { @@ -2489,6 +2494,7 @@ gb_internal void lb_llvm_module_passes(lbGenerator *gen, bool do_threading) { auto wd = gb_alloc_item(permanent_allocator(), lbLLVMModulePassWorkerData); wd->m = m; wd->target_machine = m->target_machine; + wd->do_threading = false; lb_llvm_module_pass_worker_proc(wd); } } @@ -2569,31 +2575,6 @@ 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; - thread_pool_add_task(lb_llvm_module_verification_worker_proc, m); - } - thread_pool_wait(); - - } else { - for (auto const &entry : gen->modules) { - lbModule *m = entry.value; - if (lb_llvm_module_verification_worker_proc(m)) { - return false; - } - } - } - - return true; -} - gb_internal void lb_add_foreign_library_paths(lbGenerator *gen) { for (auto const &entry : gen->modules) { lbModule *m = entry.value; @@ -3479,11 +3460,10 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { TIME_SECTION("LLVM Function Pass"); lb_llvm_function_passes(gen, do_threading && !build_context.ODIN_DEBUG); - TIME_SECTION("LLVM Module Pass"); - lb_llvm_module_passes(gen, do_threading); + TIME_SECTION("LLVM Module Pass and Verification"); + lb_llvm_module_passes_and_verification(gen, do_threading); - TIME_SECTION("LLVM Module Verification"); - if (!lb_llvm_module_verification(gen, do_threading)) { + if (gen->module_verification_failed.load(std::memory_order_relaxed)) { return false; } diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index e4b8c07fa..b031aeb8c 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -239,6 +239,8 @@ struct lbGenerator : LinkerData { isize used_module_count; + std::atomic module_verification_failed; + lbProcedure *startup_runtime; lbProcedure *cleanup_runtime; lbProcedure *objc_names; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 4907e114d..9561f86dd 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -158,7 +158,7 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { auto pm = gb_alloc_item(permanent_allocator(), lbModule); pm->pkg = pkg; pm->gen = gen; - m->polymorphic_module = pm; + m->polymorphic_module = pm; pm->polymorphic_module = pm; map_set(&gen->modules, cast(void *)pm, pm); // point to itself just add it to the list @@ -166,7 +166,9 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { lb_init_module(pm, c); } - if (!module_per_file) { + if (pkg->kind == Package_Runtime) { + // allow this to be per file + } else if (!module_per_file) { continue; } // NOTE(bill): Probably per file is not a good idea, so leave this for later @@ -182,7 +184,9 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { if (build_context.internal_weak_monomorphization) { auto pm = gb_alloc_item(permanent_allocator(), lbModule); pm->file = file; + pm->pkg = pkg; pm->gen = gen; + m->polymorphic_module = pm; pm->polymorphic_module = pm; map_set(&gen->modules, cast(void *)pm, pm); // point to itself just add it to the list From 3954491393fe00ceed70e514d92fe80fb0a53713 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Sep 2025 15:54:05 +0100 Subject: [PATCH 067/162] Type erasure to minimize code generation size --- base/runtime/core_builtin.odin | 35 ++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 3a51d71fb..497c4b147 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -334,20 +334,19 @@ delete :: proc{ // The new built-in procedure allocates memory. The first argument is a type, not a value, and the value // return is a pointer to a newly allocated value of that type using the specified allocator, default is context.allocator @(builtin, require_results) -new :: proc($T: typeid, allocator := context.allocator, loc := #caller_location) -> (^T, Allocator_Error) #optional_allocator_error { - return new_aligned(T, align_of(T), allocator, loc) +new :: proc($T: typeid, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) #optional_allocator_error { + t = (^T)(raw_data(mem_alloc_bytes(size_of(T), align_of(T), allocator, loc) or_return)) + return } @(require_results) new_aligned :: proc($T: typeid, alignment: int, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) { - data := mem_alloc_bytes(size_of(T), alignment, allocator, loc) or_return - t = (^T)(raw_data(data)) + t = (^T)(raw_data(mem_alloc_bytes(size_of(T), alignment, allocator, loc) or_return)) return } @(builtin, require_results) new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_location) -> (t: ^T, err: Allocator_Error) #optional_allocator_error { - t_data := mem_alloc_bytes(size_of(T), align_of(T), allocator, loc) or_return - t = (^T)(raw_data(t_data)) + t = (^T)(raw_data(mem_alloc_bytes(size_of(T), align_of(T), allocator, loc) or_return)) if t != nil { t^ = data } @@ -357,14 +356,21 @@ new_clone :: proc(data: $T, allocator := context.allocator, loc := #caller_locat DEFAULT_DYNAMIC_ARRAY_CAPACITY :: 8 @(require_results) -make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { +make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocator := context.allocator, loc := #caller_location) -> (res: T, err: Allocator_Error) #optional_allocator_error { + err = _make_aligned_type_erased(&res, size_of(E), len, alignment, allocator, loc) + return +} + +@(require_results) +_make_aligned_type_erased :: proc(slice: rawptr, elem_size: int, len: int, alignment: int, allocator: Allocator, loc := #caller_location) -> Allocator_Error { make_slice_error_loc(loc, len) - data, err := mem_alloc_bytes(size_of(E)*len, alignment, allocator, loc) - if data == nil && size_of(E) != 0 { - return nil, err + data, err := mem_alloc_bytes(elem_size*len, alignment, allocator, loc) + if data == nil && elem_size != 0 { + return err } - s := Raw_Slice{raw_data(data), len} - return transmute(T)s, err + (^Raw_Slice)(slice).data = raw_data(data) + (^Raw_Slice)(slice).len = len + return err } // `make_slice` allocates and initializes a slice. Like `new`, the first argument is a type, not a value. @@ -372,8 +378,9 @@ make_aligned :: proc($T: typeid/[]$E, #any_int len: int, alignment: int, allocat // // Note: Prefer using the procedure group `make`. @(builtin, require_results) -make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { - return make_aligned(T, len, align_of(E), allocator, loc) +make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (res: T, err: Allocator_Error) #optional_allocator_error { + err = _make_aligned_type_erased(&res, size_of(E), len, align_of(E), allocator, loc) + return } // `make_dynamic_array` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. From 5559916d5c2bc571ad5f6f9b1429191cdcd3213e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Sep 2025 16:35:04 +0100 Subject: [PATCH 068/162] Mulithread startup procedure body generation --- src/llvm_backend.cpp | 65 ++++++++++++++++++++++---------------------- src/llvm_backend.hpp | 12 ++++++++ 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index c6a372ac2..1524f6c25 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1153,15 +1153,6 @@ gb_internal lbValue lb_dynamic_map_reserve(lbProcedure *p, lbValue const &map_pt return lb_emit_runtime_call(p, "__dynamic_map_reserve", args); } - -struct lbGlobalVariable { - lbValue var; - lbValue init; - DeclInfo *decl; - bool is_initialized; -}; - - gb_internal lbProcedure *lb_create_objc_names(lbModule *main_module) { if (build_context.metrics.os != TargetOs_darwin) { return nullptr; @@ -1922,33 +1913,21 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_verification_worker_proc) { } - -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); - - lbProcedure *p = lb_create_dummy_procedure(main_module, str_lit(LB_STARTUP_RUNTIME_PROC_NAME), proc_type); - p->is_startup = true; - lb_add_attribute_to_proc(p->module, p->value, "optnone"); - lb_add_attribute_to_proc(p->module, p->value, "noinline"); - - // Make sure shared libraries call their own runtime startup on Linux. - LLVMSetVisibility(p->value, LLVMHiddenVisibility); - LLVMSetLinkage(p->value, LLVMWeakAnyLinkage); - +gb_internal void lb_create_startup_runtime_generate_body(lbModule *m, lbProcedure *p) { lb_begin_procedure_body(p); - lb_setup_type_info_data(main_module); + lb_setup_type_info_data(m); - if (objc_names) { - LLVMBuildCall2(p->builder, lb_type_internal_for_procedures_raw(main_module, objc_names->type), objc_names->value, nullptr, 0, ""); + if (p->objc_names) { + LLVMBuildCall2(p->builder, lb_type_internal_for_procedures_raw(m, p->objc_names->type), p->objc_names->value, nullptr, 0, ""); } - for (auto &var : global_variables) { + for (auto &var : *p->global_variables) { if (var.is_initialized) { continue; } - lbModule *entity_module = main_module; + lbModule *entity_module = m; Entity *e = var.decl->entity; GB_ASSERT(e->kind == Entity_Variable); @@ -2001,7 +1980,7 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc gbString var_name = gb_string_make(permanent_allocator(), "__$global_any::"); gbString e_str = string_canonical_entity_name(temporary_allocator(), e); var_name = gb_string_append_length(var_name, e_str, gb_strlen(e_str)); - lbAddr g = lb_add_global_generated_with_name(main_module, var_type, {}, make_string_c(var_name)); + lbAddr g = lb_add_global_generated_with_name(m, var_type, {}, make_string_c(var_name)); lb_addr_store(p, g, var.init); lbValue gp = lb_addr_get_ptr(p, g); @@ -2022,17 +2001,36 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc } - CheckerInfo *info = main_module->gen->info; - + CheckerInfo *info = m->gen->info; + for (Entity *e : info->init_procedures) { - lbValue value = lb_find_procedure_value_from_entity(main_module, e); + lbValue value = lb_find_procedure_value_from_entity(m, e); lb_emit_call(p, value, {}, ProcInlining_none); } lb_end_procedure_body(p); +} + + +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); + + lbProcedure *p = lb_create_dummy_procedure(main_module, str_lit(LB_STARTUP_RUNTIME_PROC_NAME), proc_type); + p->is_startup = true; + lb_add_attribute_to_proc(p->module, p->value, "optnone"); + lb_add_attribute_to_proc(p->module, p->value, "noinline"); + + // Make sure shared libraries call their own runtime startup on Linux. + LLVMSetVisibility(p->value, LLVMHiddenVisibility); + LLVMSetLinkage(p->value, LLVMWeakAnyLinkage); + + p->generate_body = lb_create_startup_runtime_generate_body; + p->global_variables = &global_variables; + p->objc_names = objc_names; + + array_add(&main_module->procedures_to_generate, p); - lb_verify_function(main_module, p); return p; } @@ -2800,6 +2798,7 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) { if (p->is_done) { return; } + if (p->body != nullptr) { // Build Procedure m->curr_procedure = p; lb_begin_procedure_body(p); @@ -2807,6 +2806,8 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) { lb_end_procedure_body(p); p->is_done = true; m->curr_procedure = nullptr; + } else if (p->generate_body != nullptr) { + p->generate_body(m, p); } // Add Flags diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index b031aeb8c..ebe9fe796 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -333,6 +333,14 @@ struct lbVariadicReuseSlices { lbAddr slice_addr; }; +struct lbGlobalVariable { + lbValue var; + lbValue init; + DeclInfo *decl; + bool is_initialized; +}; + + struct lbProcedure { u32 flags; u16 state_flags; @@ -395,6 +403,10 @@ struct lbProcedure { PtrMap tuple_fix_map; Array asan_stack_locals; + + void (*generate_body)(lbModule *m, lbProcedure *p); + Array *global_variables; + lbProcedure *objc_names; }; From 111e2f0c1b9c25159bee114f170d0d20914c1c7c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Sep 2025 16:53:00 +0100 Subject: [PATCH 069/162] After global procedures and types are generated, then queue the generation of the procedures for each module --- src/llvm_backend.cpp | 73 +++++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 1524f6c25..7360bdaa7 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2061,9 +2061,26 @@ gb_internal lbProcedure *lb_create_cleanup_runtime(lbModule *main_module) { // C return p; } +gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p); + +gb_internal WORKER_TASK_PROC(lb_generate_procedures_worker_proc) { + lbModule *m = cast(lbModule *)data; + for (isize i = 0; i < m->procedures_to_generate.count; i++) { + lbProcedure *p = m->procedures_to_generate[i]; + lb_generate_procedure(p->module, p); + } + return 0; +} + + +struct lbGenerateProceduresWorkerData { + lbModule *m; + bool do_threading; +}; gb_internal WORKER_TASK_PROC(lb_generate_procedures_and_types_per_module) { - lbModule *m = cast(lbModule *)data; + lbGenerateProceduresWorkerData *wd = cast(lbGenerateProceduresWorkerData *)data; + lbModule *m = wd->m; for (Entity *e : m->global_types_to_create) { (void)lb_get_entity_name(m, e); (void)lb_type(m, e->type); @@ -2073,6 +2090,10 @@ gb_internal WORKER_TASK_PROC(lb_generate_procedures_and_types_per_module) { (void)lb_get_entity_name(m, e); array_add(&m->procedures_to_generate, lb_create_procedure(m, e)); } + + if (wd->do_threading) { + thread_pool_add_task(lb_generate_procedures_worker_proc, m); + } return 0; } @@ -2162,13 +2183,22 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker 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); + auto wd = permanent_alloc_item(); + wd->m = entry.value; + wd->do_threading = true; + thread_pool_add_task(lb_generate_procedures_and_types_per_module, wd); } } else { + for (auto const &entry : gen->modules) { + auto wd = permanent_alloc_item(); + wd->m = entry.value; + wd->do_threading = false; + lb_generate_procedures_and_types_per_module(wd); + } + for (auto const &entry : gen->modules) { lbModule *m = entry.value; - lb_generate_procedures_and_types_per_module(m); + lb_generate_procedures_worker_proc(m); } } @@ -2176,9 +2206,6 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker thread_pool_wait(); } -gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p); - - gb_internal bool lb_is_module_empty(lbModule *m) { if (LLVMGetFirstFunction(m->mod) == nullptr && LLVMGetFirstGlobal(m->mod) == nullptr) { @@ -2395,33 +2422,6 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_pass_worker_proc) { return 0; } - - -gb_internal WORKER_TASK_PROC(lb_generate_procedures_worker_proc) { - lbModule *m = cast(lbModule *)data; - for (isize i = 0; i < m->procedures_to_generate.count; i++) { - lbProcedure *p = m->procedures_to_generate[i]; - lb_generate_procedure(p->module, p); - } - return 0; -} - -gb_internal void lb_generate_procedures(lbGenerator *gen, bool 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); - } - - thread_pool_wait(); - } else { - for (auto const &entry : gen->modules) { - lbModule *m = entry.value; - lb_generate_procedures_worker_proc(m); - } - } -} - gb_internal WORKER_TASK_PROC(lb_generate_missing_procedures_to_check_worker_proc) { lbModule *m = cast(lbModule *)data; for (isize i = 0; i < m->missing_procedures_to_check.count; i++) { @@ -3332,12 +3332,9 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { do_threading = false; } - TIME_SECTION("LLVM Global Procedures and Types"); + TIME_SECTION("LLVM Global Procedures and Types + Procedure Generation"); lb_create_global_procedures_and_types(gen, info, do_threading); - TIME_SECTION("LLVM Procedure Generation"); - lb_generate_procedures(gen, do_threading); - if (build_context.command_kind == Command_test && !already_has_entry_point) { TIME_SECTION("LLVM main"); lb_create_main_procedure(default_module, gen->startup_runtime, gen->cleanup_runtime); From 5bc9d79f772cd01f9ca1e42dbee17e1f4762aad1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Sep 2025 16:56:19 +0100 Subject: [PATCH 070/162] Change mutex usage for missing procedures --- src/llvm_backend.hpp | 2 ++ src/llvm_backend_general.cpp | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index ebe9fe796..5d6f56433 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -173,6 +173,8 @@ struct lbModule { StringMap members; StringMap procedures; PtrMap procedure_values; + + BlockingMutex missing_procedures_to_check_mutex; Array missing_procedures_to_check; StringMap const_strings; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 9561f86dd..3ac405e0b 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -3030,10 +3030,11 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) } if (ignore_body) { - mutex_lock(&gen->anonymous_proc_lits_mutex); - defer (mutex_unlock(&gen->anonymous_proc_lits_mutex)); + // mutex_lock(&gen->anonymous_proc_lits_mutex); + // defer (mutex_unlock(&gen->anonymous_proc_lits_mutex)); GB_ASSERT(other_module != nullptr); + mutex_lock(&other_module->missing_procedures_to_check_mutex); rw_mutex_shared_lock(&other_module->values_mutex); auto *found = map_get(&other_module->values, e); rw_mutex_shared_unlock(&other_module->values_mutex); @@ -3042,6 +3043,7 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) lbProcedure *missing_proc_in_other_module = lb_create_procedure(other_module, e, false); array_add(&other_module->missing_procedures_to_check, missing_proc_in_other_module); } + mutex_unlock(&other_module->missing_procedures_to_check_mutex); } else { array_add(&m->missing_procedures_to_check, missing_proc); } From 37e1e00623c0a824ab8e4870f40de1f05ec4f010 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Sep 2025 16:57:21 +0100 Subject: [PATCH 071/162] Revert global procedure threading --- src/llvm_backend.cpp | 73 +++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 7360bdaa7..1524f6c25 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2061,26 +2061,9 @@ gb_internal lbProcedure *lb_create_cleanup_runtime(lbModule *main_module) { // C return p; } -gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p); - -gb_internal WORKER_TASK_PROC(lb_generate_procedures_worker_proc) { - lbModule *m = cast(lbModule *)data; - for (isize i = 0; i < m->procedures_to_generate.count; i++) { - lbProcedure *p = m->procedures_to_generate[i]; - lb_generate_procedure(p->module, p); - } - return 0; -} - - -struct lbGenerateProceduresWorkerData { - lbModule *m; - bool do_threading; -}; gb_internal WORKER_TASK_PROC(lb_generate_procedures_and_types_per_module) { - lbGenerateProceduresWorkerData *wd = cast(lbGenerateProceduresWorkerData *)data; - lbModule *m = wd->m; + lbModule *m = cast(lbModule *)data; for (Entity *e : m->global_types_to_create) { (void)lb_get_entity_name(m, e); (void)lb_type(m, e->type); @@ -2090,10 +2073,6 @@ gb_internal WORKER_TASK_PROC(lb_generate_procedures_and_types_per_module) { (void)lb_get_entity_name(m, e); array_add(&m->procedures_to_generate, lb_create_procedure(m, e)); } - - if (wd->do_threading) { - thread_pool_add_task(lb_generate_procedures_worker_proc, m); - } return 0; } @@ -2183,22 +2162,13 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker if (do_threading) { for (auto const &entry : gen->modules) { - auto wd = permanent_alloc_item(); - wd->m = entry.value; - wd->do_threading = true; - thread_pool_add_task(lb_generate_procedures_and_types_per_module, wd); + lbModule *m = entry.value; + thread_pool_add_task(lb_generate_procedures_and_types_per_module, m); } } else { - for (auto const &entry : gen->modules) { - auto wd = permanent_alloc_item(); - wd->m = entry.value; - wd->do_threading = false; - lb_generate_procedures_and_types_per_module(wd); - } - for (auto const &entry : gen->modules) { lbModule *m = entry.value; - lb_generate_procedures_worker_proc(m); + lb_generate_procedures_and_types_per_module(m); } } @@ -2206,6 +2176,9 @@ gb_internal void lb_create_global_procedures_and_types(lbGenerator *gen, Checker thread_pool_wait(); } +gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p); + + gb_internal bool lb_is_module_empty(lbModule *m) { if (LLVMGetFirstFunction(m->mod) == nullptr && LLVMGetFirstGlobal(m->mod) == nullptr) { @@ -2422,6 +2395,33 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_pass_worker_proc) { return 0; } + + +gb_internal WORKER_TASK_PROC(lb_generate_procedures_worker_proc) { + lbModule *m = cast(lbModule *)data; + for (isize i = 0; i < m->procedures_to_generate.count; i++) { + lbProcedure *p = m->procedures_to_generate[i]; + lb_generate_procedure(p->module, p); + } + return 0; +} + +gb_internal void lb_generate_procedures(lbGenerator *gen, bool 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); + } + + thread_pool_wait(); + } else { + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; + lb_generate_procedures_worker_proc(m); + } + } +} + gb_internal WORKER_TASK_PROC(lb_generate_missing_procedures_to_check_worker_proc) { lbModule *m = cast(lbModule *)data; for (isize i = 0; i < m->missing_procedures_to_check.count; i++) { @@ -3332,9 +3332,12 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { do_threading = false; } - TIME_SECTION("LLVM Global Procedures and Types + Procedure Generation"); + TIME_SECTION("LLVM Global Procedures and Types"); lb_create_global_procedures_and_types(gen, info, do_threading); + TIME_SECTION("LLVM Procedure Generation"); + lb_generate_procedures(gen, do_threading); + if (build_context.command_kind == Command_test && !already_has_entry_point) { TIME_SECTION("LLVM main"); lb_create_main_procedure(default_module, gen->startup_runtime, gen->cleanup_runtime); From 4b0a07ba27abf76aa35c364a4c7b71745a1969d7 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Sep 2025 17:06:09 +0100 Subject: [PATCH 072/162] Minor rearrangement --- src/llvm_backend.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 1524f6c25..46c319a90 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3458,6 +3458,9 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { } } + TIME_SECTION("LLVM Add Foreign Library Paths"); + lb_add_foreign_library_paths(gen); + TIME_SECTION("LLVM Function Pass"); lb_llvm_function_passes(gen, do_threading && !build_context.ODIN_DEBUG); @@ -3494,9 +3497,6 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { } } - TIME_SECTION("LLVM Add Foreign Library Paths"); - lb_add_foreign_library_paths(gen); - TIME_SECTION("LLVM Correct Entity Linkage"); lb_correct_entity_linkage(gen); From 9cf69576ab8cb220af5802a04a0aa53dc92046a5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Sep 2025 20:58:24 +0100 Subject: [PATCH 073/162] More improvements to minimize code gen size --- base/runtime/core_builtin.odin | 22 ++++++++++----- base/runtime/dynamic_map_internal.odin | 3 ++ src/build_settings.cpp | 1 + src/llvm_backend.cpp | 39 ++++++++++++++++++++------ src/llvm_backend.hpp | 4 +-- src/llvm_backend_general.cpp | 6 ++-- src/main.cpp | 6 ++++ 7 files changed, 60 insertions(+), 21 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 497c4b147..903ea7ed7 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -166,11 +166,17 @@ remove_range :: proc(array: ^$D/[dynamic]$T, #any_int lo, hi: int, loc := #calle @builtin pop :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { assert(len(array) > 0, loc=loc) - res = array[len(array)-1] - (^Raw_Dynamic_Array)(array).len -= 1 + _pop_type_erased(&res, (^Raw_Dynamic_Array)(array), size_of(E)) return res } +_pop_type_erased :: proc(res: rawptr, array: ^Raw_Dynamic_Array, elem_size: int, loc := #caller_location) { + end := rawptr(uintptr(array.data) + uintptr(elem_size*(array.len-1))) + intrinsics.mem_copy_non_overlapping(res, end, elem_size) + array.len -= 1 +} + + // `pop_safe` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. // If the operation is not possible, it will return false. @@ -387,16 +393,18 @@ make_slice :: proc($T: typeid/[]$E, #any_int len: int, allocator := context.allo // // Note: Prefer using the procedure group `make`. @(builtin, require_results) -make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { - return make_dynamic_array_len_cap(T, 0, 0, allocator, loc) +make_dynamic_array :: proc($T: typeid/[dynamic]$E, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { + err = _make_dynamic_array_len_cap((^Raw_Dynamic_Array)(&array), size_of(E), align_of(E), 0, 0, allocator, loc) + return } // `make_dynamic_array_len` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. // // Note: Prefer using the procedure group `make`. @(builtin, require_results) -make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (T, Allocator_Error) #optional_allocator_error { - return make_dynamic_array_len_cap(T, len, len, allocator, loc) +make_dynamic_array_len :: proc($T: typeid/[dynamic]$E, #any_int len: int, allocator := context.allocator, loc := #caller_location) -> (array: T, err: Allocator_Error) #optional_allocator_error { + err = _make_dynamic_array_len_cap((^Raw_Dynamic_Array)(&array), size_of(E), align_of(E), len, len, allocator, loc) + return } // `make_dynamic_array_len_cap` allocates and initializes a dynamic array. Like `new`, the first argument is a type, not a value. // Unlike `new`, `make`'s return value is the same as the type of its argument, not a pointer to it. @@ -501,7 +509,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, #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 + return __dynamic_map_reserve((^Raw_Map)(m), map_info(T), uint(capacity), loc) } // Shrinks the capacity of a map down to the current length. diff --git a/base/runtime/dynamic_map_internal.odin b/base/runtime/dynamic_map_internal.odin index 7b65a2fa0..e288d1f53 100644 --- a/base/runtime/dynamic_map_internal.odin +++ b/base/runtime/dynamic_map_internal.odin @@ -985,6 +985,9 @@ __dynamic_map_entry :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_ // IMPORTANT: USED WITHIN THE COMPILER @(private) __dynamic_map_reserve :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, new_capacity: uint, loc := #caller_location) -> Allocator_Error { + if m == nil { + return nil + } return map_reserve_dynamic(m, info, uintptr(new_capacity), loc) } diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 867c80ac1..54f11a42d 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -555,6 +555,7 @@ struct BuildContext { bool internal_no_inline; bool internal_by_value; bool internal_weak_monomorphization; + bool internal_ignore_llvm_verification; bool no_threaded_checker; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 46c319a90..5d2accd90 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -8,7 +8,11 @@ #endif #ifndef LLVM_IGNORE_VERIFICATION -#define LLVM_IGNORE_VERIFICATION 0 +#define LLVM_IGNORE_VERIFICATION build_context.internal_ignore_llvm_verification +#endif + +#ifndef LLVM_WEAK_MONOMORPHIZATION +#define LLVM_WEAK_MONOMORPHIZATION build_context.internal_weak_monomorphization #endif @@ -620,6 +624,7 @@ gb_internal lbValue lb_hasher_proc_for_type(lbModule *m, Type *type) { #define LLVM_SET_VALUE_NAME(value, name) LLVMSetValueName2((value), (name), gb_count_of((name))-1); + gb_internal lbValue lb_map_get_proc_for_type(lbModule *m, Type *type) { GB_ASSERT(!build_context.dynamic_map_calls); type = base_type(type); @@ -634,6 +639,9 @@ gb_internal lbValue lb_map_get_proc_for_type(lbModule *m, Type *type) { lbProcedure *p = lb_create_dummy_procedure(m, proc_name, t_map_get_proc); string_map_set(&m->gen_procs, proc_name, p); + + p->internal_gen_type = type; + lb_begin_procedure_body(p); defer (lb_end_procedure_body(p)); @@ -1891,6 +1899,10 @@ gb_internal void lb_verify_function(lbModule *m, lbProcedure *p, bool dump_ll=fa } gb_internal WORKER_TASK_PROC(lb_llvm_module_verification_worker_proc) { + if (LLVM_IGNORE_VERIFICATION) { + return 0; + } + char *llvm_error = nullptr; defer (LLVMDisposeMessage(llvm_error)); lbModule *m = cast(lbModule *)data; @@ -2298,6 +2310,14 @@ gb_internal WORKER_TASK_PROC(lb_llvm_function_pass_per_module) { } +void lb_remove_unused_functions_and_globals(lbGenerator *gen) { + for (auto &entry : gen->modules) { + lbModule *m = entry.value; + lb_run_remove_unused_function_pass(m); + lb_run_remove_unused_globals_pass(m); + } +} + struct lbLLVMModulePassWorkerData { lbModule *m; LLVMTargetMachineRef target_machine; @@ -2307,9 +2327,6 @@ struct lbLLVMModulePassWorkerData { gb_internal WORKER_TASK_PROC(lb_llvm_module_pass_worker_proc) { auto wd = cast(lbLLVMModulePassWorkerData *)data; - lb_run_remove_unused_function_pass(wd->m); - lb_run_remove_unused_globals_pass(wd->m); - LLVMPassManagerRef module_pass_manager = LLVMCreatePassManager(); lb_populate_module_pass_manager(wd->target_machine, module_pass_manager, build_context.optimization_level); LLVMRunPassManager(module_pass_manager, wd->m->mod); @@ -2386,6 +2403,10 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_pass_worker_proc) { } #endif + if (LLVM_IGNORE_VERIFICATION) { + return 0; + } + if (wd->do_threading) { thread_pool_add_task(lb_llvm_module_verification_worker_proc, wd->m); } else { @@ -3464,12 +3485,14 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { TIME_SECTION("LLVM Function Pass"); lb_llvm_function_passes(gen, do_threading && !build_context.ODIN_DEBUG); + TIME_SECTION("LLVM Remove Unused Functions and Globals"); + lb_remove_unused_functions_and_globals(gen); + TIME_SECTION("LLVM Module Pass and Verification"); lb_llvm_module_passes_and_verification(gen, do_threading); - if (gen->module_verification_failed.load(std::memory_order_relaxed)) { - return false; - } + TIME_SECTION("LLVM Correct Entity Linkage"); + lb_correct_entity_linkage(gen); llvm_error = nullptr; defer (LLVMDisposeMessage(llvm_error)); @@ -3497,8 +3520,6 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { } } - TIME_SECTION("LLVM Correct Entity Linkage"); - lb_correct_entity_linkage(gen); //////////////////////////////////////////// for (auto const &entry: gen->modules) { diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 5d6f56433..698e7657f 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -241,8 +241,6 @@ struct lbGenerator : LinkerData { isize used_module_count; - std::atomic module_verification_failed; - lbProcedure *startup_runtime; lbProcedure *cleanup_runtime; lbProcedure *objc_names; @@ -409,6 +407,8 @@ struct lbProcedure { void (*generate_body)(lbModule *m, lbProcedure *p); Array *global_variables; lbProcedure *objc_names; + + Type *internal_gen_type; // map_set, map_get, etc. }; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 3ac405e0b..fb9fa4cea 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -154,7 +154,7 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { map_set(&gen->modules, cast(void *)pkg, m); lb_init_module(m, c); - if (build_context.internal_weak_monomorphization) { + if (LLVM_WEAK_MONOMORPHIZATION) { auto pm = gb_alloc_item(permanent_allocator(), lbModule); pm->pkg = pkg; pm->gen = gen; @@ -181,7 +181,7 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { lb_init_module(m, c); - if (build_context.internal_weak_monomorphization) { + if (LLVM_WEAK_MONOMORPHIZATION) { auto pm = gb_alloc_item(permanent_allocator(), lbModule); pm->file = file; pm->pkg = pkg; @@ -469,7 +469,7 @@ gb_internal lbModule *lb_module_of_entity(lbGenerator *gen, Entity *e, lbModule GB_ASSERT(curr_module != nullptr); lbModule *m = lb_module_of_entity_internal(gen, e, curr_module); - if (USE_SEPARATE_MODULES && build_context.internal_weak_monomorphization) { + if (USE_SEPARATE_MODULES) { if (e->kind == Entity_Procedure && e->Procedure.generated_from_polymorphic) { if (m->polymorphic_module) { return m->polymorphic_module; diff --git a/src/main.cpp b/src/main.cpp index 130c3f31d..bbaf6f23f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -404,6 +404,7 @@ enum BuildFlagKind { BuildFlag_InternalNoInline, BuildFlag_InternalByValue, BuildFlag_InternalWeakMonomorphization, + BuildFlag_InternalLLVMVerification, BuildFlag_Tilde, @@ -628,6 +629,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_InternalNoInline, str_lit("internal-no-inline"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalByValue, str_lit("internal-by-value"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalWeakMonomorphization, str_lit("internal-weak-monomorphization"), BuildFlagParam_None, Command_all); + add_flag(&build_flags, BuildFlag_InternalLLVMVerification, str_lit("internal-ignore-llvm-verification"), BuildFlagParam_None, Command_all); #if ALLOW_TILDE add_flag(&build_flags, BuildFlag_Tilde, str_lit("tilde"), BuildFlagParam_None, Command__does_build); @@ -1589,6 +1591,10 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_InternalWeakMonomorphization: build_context.internal_weak_monomorphization = true; break; + case BuildFlag_InternalLLVMVerification: + build_context.internal_ignore_llvm_verification = true; + break; + case BuildFlag_Tilde: build_context.tilde_backend = true; From 5d14df4112368617b9b534745f8ce8934be74ffd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 18 Sep 2025 23:26:11 +0100 Subject: [PATCH 074/162] Multithread `lb_module_init` --- src/llvm_backend.hpp | 2 ++ src/llvm_backend_general.cpp | 34 ++++++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 698e7657f..370a6f2ca 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -147,6 +147,8 @@ struct lbModule { LLVMModuleRef mod; LLVMContextRef ctx; + Checker *checker; + struct lbGenerator *gen; LLVMTargetMachineRef target_machine; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index fb9fa4cea..64d574920 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -15,7 +15,9 @@ gb_global isize lb_global_type_info_member_offsets_index = 0; gb_global isize lb_global_type_info_member_usings_index = 0; gb_global isize lb_global_type_info_member_tags_index = 0; -gb_internal void lb_init_module(lbModule *m, Checker *c) { +gb_internal WORKER_TASK_PROC(lb_init_module_worker_proc) { + lbModule *m = cast(lbModule *)data; + Checker *c = m->checker; m->info = &c->info; @@ -119,6 +121,15 @@ gb_internal void lb_init_module(lbModule *m, Checker *c) { m->const_dummy_builder = LLVMCreateBuilderInContext(m->ctx); + return 0; +} + +gb_internal void lb_init_module(lbModule *m, bool do_threading) { + if (do_threading) { + thread_pool_add_task(lb_init_module_worker_proc, m); + } else { + lb_init_module_worker_proc(m); + } } gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { @@ -131,6 +142,10 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { return false; } + isize thread_count = gb_max(build_context.thread_count, 1); + isize worker_count = thread_count-1; + bool do_threading = !!(LLVMIsMultithreaded() && USE_SEPARATE_MODULES && MULTITHREAD_OBJECT_GENERATION && worker_count > 0); + String init_fullpath = c->parser->init_fullpath; linker_data_init(gen, &c->info, init_fullpath); @@ -151,19 +166,21 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { auto m = gb_alloc_item(permanent_allocator(), lbModule); m->pkg = pkg; m->gen = gen; + m->checker = c; map_set(&gen->modules, cast(void *)pkg, m); - lb_init_module(m, c); + lb_init_module(m, do_threading); if (LLVM_WEAK_MONOMORPHIZATION) { auto pm = gb_alloc_item(permanent_allocator(), lbModule); pm->pkg = pkg; pm->gen = gen; + pm->checker = c; m->polymorphic_module = pm; pm->polymorphic_module = pm; map_set(&gen->modules, cast(void *)pm, pm); // point to itself just add it to the list - lb_init_module(pm, c); + lb_init_module(pm, do_threading); } if (pkg->kind == Package_Runtime) { @@ -177,8 +194,9 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { m->file = file; m->pkg = pkg; m->gen = gen; + m->checker = c; map_set(&gen->modules, cast(void *)file, m); - lb_init_module(m, c); + lb_init_module(m, do_threading); if (LLVM_WEAK_MONOMORPHIZATION) { @@ -186,20 +204,24 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { pm->file = file; pm->pkg = pkg; pm->gen = gen; + pm->checker = c; m->polymorphic_module = pm; pm->polymorphic_module = pm; map_set(&gen->modules, cast(void *)pm, pm); // point to itself just add it to the list - lb_init_module(pm, c); + lb_init_module(pm, do_threading); } } } } gen->default_module.gen = gen; + gen->default_module.checker = c; map_set(&gen->modules, cast(void *)1, &gen->default_module); - lb_init_module(&gen->default_module, c); + lb_init_module(&gen->default_module, do_threading); + + thread_pool_wait(); for (auto const &entry : gen->modules) { lbModule *m = entry.value; From 5f76d6ce15d6518f327b89ab111a6a90a832d81d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 10:25:11 +0100 Subject: [PATCH 075/162] Support `-linker:mold` --- src/build_settings.cpp | 2 ++ src/linker.cpp | 29 ++++++++++++++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 54f11a42d..11e269776 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -418,6 +418,7 @@ enum LinkerChoice : i32 { Linker_Default = 0, Linker_lld, Linker_radlink, + Linker_mold, Linker_COUNT, }; @@ -433,6 +434,7 @@ String linker_choices[Linker_COUNT] = { str_lit("default"), str_lit("lld"), str_lit("radlink"), + str_lit("mold"), }; enum IntegerDivisionByZeroKind : u8 { diff --git a/src/linker.cpp b/src/linker.cpp index 333cb792e..f1e0335d5 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -161,21 +161,32 @@ gb_internal i32 linker_stage(LinkerData *gen) { try_cross_linking:; #if defined(GB_SYSTEM_WINDOWS) + String section_name = str_lit("msvc-link"); bool is_windows = build_context.metrics.os == TargetOs_windows; #else + String section_name = str_lit("lld-link"); bool is_windows = false; #endif bool is_osx = build_context.metrics.os == TargetOs_darwin; + switch (build_context.linker_choice) { + case Linker_Default: break; + case Linker_lld: section_name = str_lit("lld-link"); break; + #if defined(GB_SYSTEM_LINUX) + case Linker_mold: section_name = str_lit("mold-link"); break; + #endif + #if defined(GB_SYSTEM_WINDOWS) + case Linker_radlink: section_name = str_lit("rad-link"); break; + #endif + default: + gb_printf_err("'%.*s' linker is not support for this platform\n", LIT(linker_choices[build_context.linker_choice])); + return 1; + } + + if (is_windows) { - String section_name = str_lit("msvc-link"); - switch (build_context.linker_choice) { - case Linker_Default: break; - case Linker_lld: section_name = str_lit("lld-link"); break; - case Linker_radlink: section_name = str_lit("rad-link"); break; - } timings_start_section(timings, section_name); gbString lib_str = gb_string_make(heap_allocator(), ""); @@ -423,7 +434,8 @@ try_cross_linking:; } } } else { - timings_start_section(timings, str_lit("ld-link")); + + timings_start_section(timings, section_name); int const ODIN_ANDROID_API_LEVEL = build_context.ODIN_ANDROID_API_LEVEL; @@ -956,6 +968,9 @@ try_cross_linking:; if (build_context.linker_choice == Linker_lld) { link_command_line = gb_string_append_fmt(link_command_line, " -fuse-ld=lld"); result = system_exec_command_line_app("lld-link", link_command_line); + } else if (build_context.linker_choice == Linker_mold) { + link_command_line = gb_string_append_fmt(link_command_line, " -fuse-ld=mold"); + result = system_exec_command_line_app("mold-link", link_command_line); } else { result = system_exec_command_line_app("ld-link", link_command_line); } From 6ce889f4ebf10d44fc6c1e5fba794e412dfcf183 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 11:01:41 +0100 Subject: [PATCH 076/162] `Entity *` to `std::atomic` to remove the need for a PtrMap+Mutex --- src/check_decl.cpp | 8 ++++---- src/check_expr.cpp | 9 +++++---- src/checker.cpp | 2 +- src/checker.hpp | 2 +- src/llvm_backend.hpp | 3 --- src/llvm_backend_general.cpp | 21 +++++++++------------ src/llvm_backend_proc.cpp | 2 +- src/llvm_backend_type.cpp | 5 +++-- 8 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 8fbcb5e40..092222d3c 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1549,7 +1549,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { "\tother at %s", LIT(name), token_pos_to_string(pos)); } else if (name == "main") { - if (d->entity->pkg->kind != Package_Runtime) { + if (d->entity.load()->pkg->kind != Package_Runtime) { error(d->proc_lit, "The link name 'main' is reserved for internal use"); } } else { @@ -1967,8 +1967,8 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de ctx->curr_proc_sig = type; ctx->curr_proc_calling_convention = type->Proc.calling_convention; - if (decl->parent && decl->entity && decl->parent->entity) { - decl->entity->parent_proc_decl = decl->parent; + if (decl->parent && decl->entity.load() && decl->parent->entity) { + decl->entity.load()->parent_proc_decl = decl->parent; } if (ctx->pkg->name != "runtime") { @@ -2072,7 +2072,7 @@ gb_internal bool check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *de GB_ASSERT(decl->proc_checked_state != ProcCheckedState_Checked); if (decl->defer_use_checked) { GB_ASSERT(is_type_polymorphic(type, true)); - error(token, "Defer Use Checked: %.*s", LIT(decl->entity->token.string)); + error(token, "Defer Use Checked: %.*s", LIT(decl->entity.load()->token.string)); GB_ASSERT(decl->defer_use_checked == false); } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index d2505c047..d863d6cf6 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -608,7 +608,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E entity->flags |= EntityFlag_Disabled; } - d->entity = entity; + d->entity.store(entity); AstFile *file = nullptr; { @@ -8335,9 +8335,10 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c if (c->curr_proc_decl == nullptr) { error(call, "Calling a '#force_inline' procedure that enables target features is not allowed at file scope"); } else { - GB_ASSERT(c->curr_proc_decl->entity); - GB_ASSERT(c->curr_proc_decl->entity->type->kind == Type_Proc); - String scope_features = c->curr_proc_decl->entity->type->Proc.enable_target_feature; + Entity *e = c->curr_proc_decl->entity.load(); + GB_ASSERT(e); + GB_ASSERT(e->type->kind == Type_Proc); + String scope_features = e->type->Proc.enable_target_feature; if (!check_target_feature_is_superset_of(scope_features, pt->Proc.enable_target_feature, &invalid)) { ERROR_BLOCK(); error(call, "Inlined procedure enables target feature '%.*s', this requires the calling procedure to at least enable the same feature", LIT(invalid)); diff --git a/src/checker.cpp b/src/checker.cpp index 7da3948dc..32bda2e43 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2055,8 +2055,8 @@ gb_internal void add_entity_and_decl_info(CheckerContext *c, Ast *identifier, En add_entity_definition(info, identifier, e); GB_ASSERT(e->decl_info == nullptr); e->decl_info = d; - d->entity = e; e->pkg = c->pkg; + d->entity.store(e); isize queue_count = -1; bool is_lazy = false; diff --git a/src/checker.hpp b/src/checker.hpp index 68779c280..d22b99e89 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -209,7 +209,7 @@ struct DeclInfo { Scope * scope; - Entity *entity; + std::atomic entity; Ast * decl_node; Ast * type_expr; diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 370a6f2ca..7fe4651bb 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -238,9 +238,6 @@ struct lbGenerator : LinkerData { PtrMap modules_through_ctx; lbModule default_module; - RecursiveMutex anonymous_proc_lits_mutex; - PtrMap anonymous_proc_lits; - isize used_module_count; lbProcedure *startup_runtime; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 64d574920..129634ee6 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -157,7 +157,6 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { map_init(&gen->modules, gen->info->packages.count*2); map_init(&gen->modules_through_ctx, gen->info->packages.count*2); - map_init(&gen->anonymous_proc_lits, 1024); if (USE_SEPARATE_MODULES) { bool module_per_file = build_context.module_per_file && build_context.optimization_level <= 0; @@ -3084,18 +3083,14 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &prefix_name, Ast *expr, lbProcedure *parent) { - lbGenerator *gen = m->gen; + // lbGenerator *gen = m->gen; + ast_node(pl, ProcLit, expr); - mutex_lock(&gen->anonymous_proc_lits_mutex); - defer (mutex_unlock(&gen->anonymous_proc_lits_mutex)); - - TokenPos pos = ast_token(expr).pos; - lbProcedure **found = map_get(&gen->anonymous_proc_lits, expr); - if (found) { - return lb_find_procedure_value_from_entity(m, (*found)->entity); + if (pl->decl->entity.load() != nullptr) { + return lb_find_procedure_value_from_entity(m, pl->decl->entity.load()); } - ast_node(pl, ProcLit, expr); + TokenPos pos = ast_token(expr).pos; // NOTE(bill): Generate a new name // parent$count @@ -3114,15 +3109,18 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr token.string = name; Entity *e = alloc_entity_procedure(nullptr, token, type, pl->tags); e->file = expr->file(); + e->pkg = e->file->pkg; + e->scope = e->file->scope; // NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time pl->decl->code_gen_module = m; e->decl_info = pl->decl; - pl->decl->entity = e; e->parent_proc_decl = pl->decl->parent; e->Procedure.is_anonymous = true; e->flags |= EntityFlag_ProcBodyChecked; + pl->decl->entity.store(e); + lbProcedure *p = lb_create_procedure(m, e); GB_ASSERT(e->code_gen_module == m); @@ -3130,7 +3128,6 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr value.value = p->value; value.type = p->type; - map_set(&gen->anonymous_proc_lits, expr, p); array_add(&m->procedures_to_generate, p); if (parent != nullptr) { array_add(&parent->children, p); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 20d627fa2..f71b693eb 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2211,7 +2211,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu GB_ASSERT(e != nullptr); if (e->parent_proc_decl != nullptr && e->parent_proc_decl->entity != nullptr) { - procedure = e->parent_proc_decl->entity->token.string; + procedure = e->parent_proc_decl->entity.load()->token.string; } else { procedure = str_lit(""); } diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index d1e7c0559..b2eec218f 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -394,8 +394,9 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ String proc_name = {}; if (t->Named.type_name->parent_proc_decl) { DeclInfo *decl = t->Named.type_name->parent_proc_decl; - if (decl->entity && decl->entity->kind == Entity_Procedure) { - proc_name = decl->entity->token.string; + Entity *e = decl->entity.load(); + if (e && e->kind == Entity_Procedure) { + proc_name = e->token.string; } } TokenPos pos = t->Named.type_name->token.pos; From 1a4da5cb0fa5731926ba7abb7a7496be6a39831f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 11:13:18 +0100 Subject: [PATCH 077/162] Distribute anonymous procedure literals correctly across LLVM modules --- src/llvm_backend_general.cpp | 48 +++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 129634ee6..57eb869fa 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -3083,7 +3083,7 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &prefix_name, Ast *expr, lbProcedure *parent) { - // lbGenerator *gen = m->gen; + lbGenerator *gen = m->gen; ast_node(pl, ProcLit, expr); if (pl->decl->entity.load() != nullptr) { @@ -3112,8 +3112,11 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr e->pkg = e->file->pkg; e->scope = e->file->scope; + lbModule *target_module = lb_module_of_entity(gen, e, m); + GB_ASSERT(target_module != nullptr); + // NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time - pl->decl->code_gen_module = m; + pl->decl->code_gen_module = target_module; e->decl_info = pl->decl; e->parent_proc_decl = pl->decl->parent; e->Procedure.is_anonymous = true; @@ -3121,20 +3124,41 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr pl->decl->entity.store(e); - lbProcedure *p = lb_create_procedure(m, e); - GB_ASSERT(e->code_gen_module == m); - lbValue value = {}; - value.value = p->value; - value.type = p->type; + if (target_module != m) { + mutex_lock(&target_module->missing_procedures_to_check_mutex); + rw_mutex_shared_lock(&target_module->values_mutex); + lbValue *found = map_get(&target_module->values, e); + rw_mutex_shared_unlock(&target_module->values_mutex); + if (found == nullptr) { + // THIS IS THE RACE CONDITION + lbProcedure *missing_proc_in_target_module = lb_create_procedure(target_module, e, false); + array_add(&target_module->missing_procedures_to_check, missing_proc_in_target_module); + } - array_add(&m->procedures_to_generate, p); - if (parent != nullptr) { - array_add(&parent->children, p); + mutex_unlock(&target_module->missing_procedures_to_check_mutex); + + lbProcedure *p = lb_create_procedure(m, e, true); + + lbValue value = {}; + value.value = p->value; + value.type = p->type; + return value; } else { - string_map_set(&m->members, name, value); + lbProcedure *p = lb_create_procedure(m, e); + + lbValue value = {}; + value.value = p->value; + value.type = p->type; + + array_add(&m->procedures_to_generate, p); + if (parent != nullptr) { + array_add(&parent->children, p); + } else { + string_map_set(&m->members, name, value); + } + return value; } - return value; } From 6338e0a8a3db948624817c99c431e5889afc636a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 11:56:44 +0100 Subject: [PATCH 078/162] Allow unions with one variant to be constant --- src/check_decl.cpp | 2 +- src/check_expr.cpp | 28 ++++++++++++++++++++++- src/llvm_backend_const.cpp | 43 +++++++++++++++++++++++++++++++++++- src/llvm_backend_general.cpp | 13 ++++++++++- 4 files changed, 82 insertions(+), 4 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 092222d3c..bda1059fb 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1565,7 +1565,7 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { } } -gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast *type_expr, Ast *init_expr) { +gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *e, Ast *type_expr, Ast *init_expr) { GB_ASSERT(e->type == nullptr); GB_ASSERT(e->kind == Entity_Variable); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index d863d6cf6..0ea0952f9 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3500,6 +3500,21 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type return false; } +gb_internal bool is_type_union_constantable(Type *type) { + Type *bt = base_type(type); + GB_ASSERT(bt->kind == Type_Union); + + + if (bt->Union.variants.count <= 1) { + return true; + } + + // for (Type *v : bt->Union.variants) { + + // } + return false; +} + gb_internal bool check_cast_internal(CheckerContext *c, Operand *x, Type *type) { bool is_const_expr = x->mode == Addressing_Constant; @@ -3524,6 +3539,9 @@ gb_internal bool check_cast_internal(CheckerContext *c, Operand *x, Type *type) } else if (is_type_slice(type) && is_type_string(x->type)) { x->mode = Addressing_Value; } else if (is_type_union(type)) { + if (is_type_union_constantable(type)) { + return true; + } x->mode = Addressing_Value; } if (x->mode == Addressing_Value) { @@ -3582,7 +3600,11 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type, bool forb Type *final_type = type; if (is_const_expr && !is_type_constant_type(type)) { if (is_type_union(type)) { - convert_to_typed(c, x, type); + if (is_type_union_constantable(type)) { + + } else { + convert_to_typed(c, x, type); + } } final_type = default_type(x->type); } @@ -8151,7 +8173,11 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } else { // NOTE(bill): Otherwise the compiler can override the polymorphic type // as it assumes it is determining the type + AddressingMode old_mode = operand->mode; check_cast(c, operand, t); + if (old_mode == Addressing_Constant && old_mode != operand->mode) { + gb_printf_err("HERE: %d -> %d %s\n", old_mode, operand->mode, expr_to_string(operand->expr)); + } } } operand->type = t; diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index e64be49f2..98e50dd78 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -168,7 +168,7 @@ gb_internal LLVMValueRef llvm_const_named_struct(lbModule *m, Type *t, LLVMValue return llvm_const_named_struct_internal(struct_type, values, value_count_); } Type *bt = base_type(t); - GB_ASSERT(bt->kind == Type_Struct); + GB_ASSERT(bt->kind == Type_Struct || bt->kind == Type_Union); GB_ASSERT(value_count_ == bt->Struct.fields.count); @@ -585,6 +585,47 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb bool is_local = cc.allow_local && m->curr_procedure != nullptr; + if (is_type_union(type) && is_type_union_constantable(type)) { + Type *bt = base_type(type); + GB_ASSERT(bt->kind == Type_Union); + GB_ASSERT(bt->Union.variants.count <= 1); + if (bt->Union.variants.count == 0) { + return lb_const_nil(m, original_type); + } else if (bt->Union.variants.count == 1) { + Type *t = bt->Union.variants[0]; + lbValue cv = lb_const_value(m, t, value, cc); + GB_ASSERT(LLVMIsConstant(cv.value)); + + LLVMTypeRef llvm_type = lb_type(m, original_type); + + if (is_type_union_maybe_pointer(type)) { + LLVMValueRef values[1] = {cv.value}; + res.value = llvm_const_named_struct_internal(llvm_type, values, 1); + res.type = original_type; + return res; + } else { + + unsigned tag_value = 1; + if (bt->Union.kind == UnionType_no_nil) { + tag_value = 0; + } + LLVMValueRef tag = LLVMConstInt(LLVMStructGetTypeAtIndex(llvm_type, 1), tag_value, false); + LLVMValueRef padding = nullptr; + LLVMValueRef values[3] = {cv.value, tag, padding}; + + isize value_count = 2; + if (LLVMCountStructElementTypes(llvm_type) > 2) { + value_count = 3; + padding = LLVMConstNull(LLVMStructGetTypeAtIndex(llvm_type, 2)); + } + res.value = llvm_const_named_struct_internal(llvm_type, values, value_count); + res.type = original_type; + return res; + } + } + + } + // GB_ASSERT_MSG(is_type_typed(type), "%s", type_to_string(type)); if (is_type_slice(type)) { diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 57eb869fa..cae08ec2f 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -2226,6 +2226,18 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { if (is_type_union_maybe_pointer(type)) { LLVMTypeRef variant = lb_type(m, type->Union.variants[0]); array_add(&fields, variant); + } else if (type->Union.variants.count == 1) { + LLVMTypeRef block_type = lb_type(m, type->Union.variants[0]); + + LLVMTypeRef tag_type = lb_type(m, union_tag_type(type)); + array_add(&fields, block_type); + array_add(&fields, tag_type); + i64 used_size = lb_sizeof(block_type) + lb_sizeof(tag_type); + i64 padding = size - used_size; + if (padding > 0) { + LLVMTypeRef padding_type = lb_type_padding_filler(m, padding, align); + array_add(&fields, padding_type); + } } else { LLVMTypeRef block_type = nullptr; @@ -3131,7 +3143,6 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr lbValue *found = map_get(&target_module->values, e); rw_mutex_shared_unlock(&target_module->values_mutex); if (found == nullptr) { - // THIS IS THE RACE CONDITION lbProcedure *missing_proc_in_target_module = lb_create_procedure(target_module, e, false); array_add(&target_module->missing_procedures_to_check, missing_proc_in_target_module); } From be1e889abbdafbbda4223611175dbc1d6e349073 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 12:29:26 +0100 Subject: [PATCH 079/162] Remove debug message --- src/check_expr.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 0ea0952f9..18302efc3 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3504,14 +3504,17 @@ gb_internal bool is_type_union_constantable(Type *type) { Type *bt = base_type(type); GB_ASSERT(bt->kind == Type_Union); - - if (bt->Union.variants.count <= 1) { + if (bt->Union.variants.count == 0) { return true; + } else if (bt->Union.variants.count == 1) { + return is_type_constant_type(bt->Union.variants[0]); } - // for (Type *v : bt->Union.variants) { - - // } + for (Type *v : bt->Union.variants) { + if (!is_type_constant_type(v)) { + return false; + } + } return false; } @@ -8175,9 +8178,6 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c // as it assumes it is determining the type AddressingMode old_mode = operand->mode; check_cast(c, operand, t); - if (old_mode == Addressing_Constant && old_mode != operand->mode) { - gb_printf_err("HERE: %d -> %d %s\n", old_mode, operand->mode, expr_to_string(operand->expr)); - } } } operand->type = t; From 31cafda30da4b66170cf611c6e1edc40038679e5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 12:31:06 +0100 Subject: [PATCH 080/162] Remove unused variable --- src/check_expr.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 18302efc3..4462a5907 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -8176,7 +8176,6 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } else { // NOTE(bill): Otherwise the compiler can override the polymorphic type // as it assumes it is determining the type - AddressingMode old_mode = operand->mode; check_cast(c, operand, t); } } From a8b2b1a23bc40343e1642edb3b7a1e4e1ecec493 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 13:49:09 +0100 Subject: [PATCH 081/162] Temporarily revert anonymous procedure load balancing --- src/llvm_backend_general.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index cae08ec2f..49a7fb13f 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -3096,6 +3096,8 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &prefix_name, Ast *expr, lbProcedure *parent) { lbGenerator *gen = m->gen; + gb_unused(gen); + ast_node(pl, ProcLit, expr); if (pl->decl->entity.load() != nullptr) { @@ -3121,10 +3123,15 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr token.string = name; Entity *e = alloc_entity_procedure(nullptr, token, type, pl->tags); e->file = expr->file(); + +#if 0 e->pkg = e->file->pkg; e->scope = e->file->scope; lbModule *target_module = lb_module_of_entity(gen, e, m); +#else + lbModule *target_module = m; +#endif GB_ASSERT(target_module != nullptr); // NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time From ec91e2c15b9b51f1ef70055a8b4900c754b1f100 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 13:52:25 +0100 Subject: [PATCH 082/162] Separate ini global var stuff --- src/llvm_backend.cpp | 139 +++++++++++++++++++++++-------------------- 1 file changed, 76 insertions(+), 63 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 77576cfd4..688cfb550 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1924,6 +1924,74 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_verification_worker_proc) { return 0; } +gb_internal bool lb_init_global_var(lbModule *m, lbProcedure *p, Entity *e, Ast *init_expr, lbGlobalVariable &var) { + if (init_expr != nullptr) { + lbValue init = lb_build_expr(p, init_expr); + if (init.value == nullptr) { + LLVMTypeRef global_type = llvm_addr_type(p->module, var.var); + 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); + } + return true; + } + GB_PANIC("Invalid init value, got %s", expr_to_string(init_expr)); + } + + if (is_type_any(e->type) || is_type_union(e->type)) { + var.init = init; + } else if (lb_is_const_or_global(init)) { + if (!var.is_initialized) { + if (is_type_proc(init.type)) { + init.value = LLVMConstPointerCast(init.value, lb_type(p->module, init.type)); + } + LLVMSetInitializer(var.var.value, init.value); + var.is_initialized = true; + + if (e->Variable.is_rodata) { + LLVMSetGlobalConstant(var.var.value, true); + } + return true; + } + } else { + var.init = init; + } + } + + if (var.init.value != nullptr) { + GB_ASSERT(!var.is_initialized); + Type *t = type_deref(var.var.type); + + if (is_type_any(t)) { + // NOTE(bill): Edge case for 'any' type + Type *var_type = default_type(var.init.type); + gbString var_name = gb_string_make(permanent_allocator(), "__$global_any::"); + gbString e_str = string_canonical_entity_name(temporary_allocator(), e); + var_name = gb_string_append_length(var_name, e_str, gb_strlen(e_str)); + lbAddr g = lb_add_global_generated_with_name(m, var_type, {}, make_string_c(var_name)); + lb_addr_store(p, g, var.init); + lbValue gp = lb_addr_get_ptr(p, g); + + lbValue data = lb_emit_struct_ep(p, var.var, 0); + lbValue ti = lb_emit_struct_ep(p, var.var, 1); + lb_emit_store(p, data, lb_emit_conv(p, gp, t_rawptr)); + lb_emit_store(p, ti, lb_typeid(p->module, var_type)); + } else { + LLVMTypeRef vt = llvm_addr_type(p->module, var.var); + lbValue src0 = lb_emit_conv(p, var.init, t); + LLVMValueRef src = OdinLLVMBuildTransmute(p, src0.value, vt); + LLVMValueRef dst = var.var.value; + LLVMBuildStore(p->builder, src, dst); + } + + var.is_initialized = true; + } + return false; +} + gb_internal void lb_create_startup_runtime_generate_body(lbModule *m, lbProcedure *p) { lb_begin_procedure_body(p); @@ -1944,73 +2012,13 @@ gb_internal void lb_create_startup_runtime_generate_body(lbModule *m, lbProcedur Entity *e = var.decl->entity; GB_ASSERT(e->kind == Entity_Variable); e->code_gen_module = entity_module; - Ast *init_expr = var.decl->init_expr; - if (init_expr != nullptr) { - lbValue init = lb_build_expr(p, init_expr); - if (init.value == nullptr) { - LLVMTypeRef global_type = llvm_addr_type(p->module, var.var); - 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)); - } - - if (is_type_any(e->type) || is_type_union(e->type)) { - var.init = init; - } else if (lb_is_const_or_global(init)) { - if (!var.is_initialized) { - if (is_type_proc(init.type)) { - init.value = LLVMConstPointerCast(init.value, lb_type(p->module, init.type)); - } - LLVMSetInitializer(var.var.value, init.value); - var.is_initialized = true; - - if (e->Variable.is_rodata) { - LLVMSetGlobalConstant(var.var.value, true); - } - continue; - } - } else { - var.init = init; - } - } - - if (var.init.value != nullptr) { - GB_ASSERT(!var.is_initialized); - Type *t = type_deref(var.var.type); - - if (is_type_any(t)) { - // NOTE(bill): Edge case for 'any' type - Type *var_type = default_type(var.init.type); - gbString var_name = gb_string_make(permanent_allocator(), "__$global_any::"); - gbString e_str = string_canonical_entity_name(temporary_allocator(), e); - var_name = gb_string_append_length(var_name, e_str, gb_strlen(e_str)); - lbAddr g = lb_add_global_generated_with_name(m, var_type, {}, make_string_c(var_name)); - lb_addr_store(p, g, var.init); - lbValue gp = lb_addr_get_ptr(p, g); - - lbValue data = lb_emit_struct_ep(p, var.var, 0); - lbValue ti = lb_emit_struct_ep(p, var.var, 1); - lb_emit_store(p, data, lb_emit_conv(p, gp, t_rawptr)); - lb_emit_store(p, ti, lb_typeid(p->module, var_type)); - } else { - LLVMTypeRef vt = llvm_addr_type(p->module, var.var); - lbValue src0 = lb_emit_conv(p, var.init, t); - LLVMValueRef src = OdinLLVMBuildTransmute(p, src0.value, vt); - LLVMValueRef dst = var.var.value; - LLVMBuildStore(p->builder, src, dst); - } - - var.is_initialized = true; + if (init_expr == nullptr && var.init.value == nullptr) { + continue; } + lb_init_global_var(m, p, e, init_expr, var); } CheckerInfo *info = m->gen->info; @@ -2448,7 +2456,12 @@ gb_internal WORKER_TASK_PROC(lb_generate_missing_procedures_to_check_worker_proc for (isize i = 0; i < m->missing_procedures_to_check.count; i++) { lbProcedure *p = m->missing_procedures_to_check[i]; debugf("Generate missing procedure: %.*s module %p\n", LIT(p->name), m); + isize count = m->procedures_to_generate.count; lb_generate_procedure(m, p); + isize new_count = m->procedures_to_generate.count; + if (count != new_count) { + gb_printf_err("NEW STUFF!\n"); + } } return 0; } From f1e3977cf94dfc0457f05d499cc280d8e1329086 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 13:57:52 +0100 Subject: [PATCH 083/162] Split up startup call into separate calls --- src/llvm_backend.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 688cfb550..5c75d2570 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2001,6 +2001,8 @@ gb_internal void lb_create_startup_runtime_generate_body(lbModule *m, lbProcedur if (p->objc_names) { LLVMBuildCall2(p->builder, lb_type_internal_for_procedures_raw(m, p->objc_names->type), p->objc_names->value, nullptr, 0, ""); } + Type *dummy_type = alloc_type_proc(nullptr, nullptr, 0, nullptr, 0, false, ProcCC_Odin); + LLVMTypeRef raw_dummy_type = lb_type_internal_for_procedures_raw(m, dummy_type); for (auto &var : *p->global_variables) { if (var.is_initialized) { @@ -2018,8 +2020,25 @@ gb_internal void lb_create_startup_runtime_generate_body(lbModule *m, lbProcedur continue; } - lb_init_global_var(m, p, e, init_expr, var); + if (type_size_of(e->type) > 32) { + String ename = lb_get_entity_name(m, e); + gbString name = gb_string_make(permanent_allocator(), ""); + name = gb_string_appendc(name, "__$startup$"); + name = gb_string_append_length(name, ename.text, ename.len); + lbProcedure *dummy = lb_create_dummy_procedure(m, make_string_c(name), dummy_type); + LLVMSetVisibility(dummy->value, LLVMHiddenVisibility); + LLVMSetLinkage(dummy->value, LLVMWeakAnyLinkage); + + lb_begin_procedure_body(dummy); + lb_init_global_var(m, dummy, e, init_expr, var); + lb_end_procedure_body(dummy); + + LLVMValueRef context_ptr = lb_find_or_generate_context_ptr(p).addr.value; + LLVMBuildCall2(p->builder, raw_dummy_type, dummy->value, &context_ptr, 1, ""); + } else { + lb_init_global_var(m, p, e, init_expr, var); + } } CheckerInfo *info = m->gen->info; From ee3b10335b09181808fd98ccf46fe9369a11db13 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 13:59:58 +0100 Subject: [PATCH 084/162] Split further --- src/llvm_backend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 5c75d2570..f18d4af64 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2020,7 +2020,7 @@ gb_internal void lb_create_startup_runtime_generate_body(lbModule *m, lbProcedur continue; } - if (type_size_of(e->type) > 32) { + if (type_size_of(e->type) > 8) { String ename = lb_get_entity_name(m, e); gbString name = gb_string_make(permanent_allocator(), ""); name = gb_string_appendc(name, "__$startup$"); From 6bca1475edb8e2aec4bcbe942835bcc54f9e837e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 14:15:25 +0100 Subject: [PATCH 085/162] Convert `procedures_to_generate` to a queue --- src/llvm_backend.cpp | 54 +++++++++++++++++++++--------------- src/llvm_backend.hpp | 7 ++++- src/llvm_backend_general.cpp | 18 ++++++++++-- src/llvm_backend_proc.cpp | 2 +- src/llvm_backend_stmt.cpp | 2 +- 5 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index f18d4af64..3ac5970f8 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -246,26 +246,12 @@ gb_internal String lb_internal_gen_name_from_type(char const *prefix, Type *type return proc_name; } - -gb_internal lbValue lb_equal_proc_for_type(lbModule *m, Type *type) { - type = base_type(type); - GB_ASSERT(is_type_comparable(type)); +gb_internal void lb_equal_proc_generate_body(lbModule *m, lbProcedure *p) { + Type *type = p->internal_gen_type; Type *pt = alloc_type_pointer(type); LLVMTypeRef ptr_type = lb_type(m, pt); - String proc_name = lb_internal_gen_name_from_type("__$equal", type); - lbProcedure **found = string_map_get(&m->gen_procs, proc_name); - lbProcedure *compare_proc = nullptr; - if (found) { - compare_proc = *found; - GB_ASSERT(compare_proc != nullptr); - return {compare_proc->value, compare_proc->type}; - } - - - lbProcedure *p = lb_create_dummy_procedure(m, proc_name, t_equal_proc); - string_map_set(&m->gen_procs, proc_name, p); lb_begin_procedure_body(p); LLVMSetLinkage(p->value, LLVMInternalLinkage); @@ -393,9 +379,29 @@ gb_internal lbValue lb_equal_proc_for_type(lbModule *m, Type *type) { } lb_end_procedure_body(p); +} - compare_proc = p; - return {compare_proc->value, compare_proc->type}; +gb_internal lbValue lb_equal_proc_for_type(lbModule *m, Type *type) { + type = base_type(type); + GB_ASSERT(is_type_comparable(type)); + + String proc_name = lb_internal_gen_name_from_type("__$equal", type); + lbProcedure **found = string_map_get(&m->gen_procs, proc_name); + if (found) { + lbProcedure *p = *found; + GB_ASSERT(p != nullptr); + return {p->value, p->type}; + } + + lbProcedure *p = lb_create_dummy_procedure(m, proc_name, t_equal_proc); + string_map_set(&m->gen_procs, proc_name, p); + p->internal_gen_type = type; + p->generate_body = lb_equal_proc_generate_body; + + // p->generate_body(m, p); + mpsc_enqueue(&m->procedures_to_generate, p); + + return {p->value, p->type}; } gb_internal lbValue lb_simple_compare_hash(lbProcedure *p, Type *type, lbValue data, lbValue seed) { @@ -2068,7 +2074,7 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc p->global_variables = &global_variables; p->objc_names = objc_names; - array_add(&main_module->procedures_to_generate, p); + mpsc_enqueue(&main_module->procedures_to_generate, p); return p; } @@ -2110,7 +2116,7 @@ gb_internal WORKER_TASK_PROC(lb_generate_procedures_and_types_per_module) { 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)); + mpsc_enqueue(&m->procedures_to_generate, lb_create_procedure(m, e)); } return 0; } @@ -2298,7 +2304,7 @@ gb_internal WORKER_TASK_PROC(lb_llvm_function_pass_per_module) { lb_llvm_function_pass_per_function_internal(m, m->gen->objc_names); } - for (lbProcedure *p : m->procedures_to_generate) { + MUTEX_GUARD_BLOCK(&m->generated_procedures_mutex) for (lbProcedure *p : m->generated_procedures) { if (p->body != nullptr) { // Build Procedure lbFunctionPassManagerKind pass_manager_kind = lbFunctionPassManager_default; if (p->flags & lbProcedureFlag_WithoutMemcpyPass) { @@ -2447,8 +2453,7 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_pass_worker_proc) { gb_internal WORKER_TASK_PROC(lb_generate_procedures_worker_proc) { lbModule *m = cast(lbModule *)data; - for (isize i = 0; i < m->procedures_to_generate.count; i++) { - lbProcedure *p = m->procedures_to_generate[i]; + for (lbProcedure *p = nullptr; mpsc_dequeue(&m->procedures_to_generate, &p); /**/) { lb_generate_procedure(p->module, p); } return 0; @@ -2876,6 +2881,9 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) { } lb_verify_function(m, p, true); + + MUTEX_GUARD(&m->generated_procedures_mutex); + array_add(&m->generated_procedures, p); } diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 7fe4651bb..6d94df399 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -186,10 +186,13 @@ struct lbModule { StringMap gen_procs; // key is the canonicalized name - Array procedures_to_generate; + MPSCQueue procedures_to_generate; Array global_procedures_to_create; Array global_types_to_create; + BlockingMutex generated_procedures_mutex; + Array generated_procedures; + lbProcedure *curr_procedure; LLVMBuilderRef const_dummy_builder; @@ -238,6 +241,8 @@ struct lbGenerator : LinkerData { PtrMap modules_through_ctx; lbModule default_module; + lbModule *equal_module; + isize used_module_count; lbProcedure *startup_runtime; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 49a7fb13f..c84edc178 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -97,12 +97,16 @@ gb_internal WORKER_TASK_PROC(lb_init_module_worker_proc) { map_init(&m->function_type_map); string_map_init(&m->gen_procs); if (USE_SEPARATE_MODULES) { - array_init(&m->procedures_to_generate, a, 0, 1<<10); + mpsc_init(&m->procedures_to_generate, a); map_init(&m->procedure_values, 1<<11); + array_init(&m->generated_procedures, a, 0, 1<<10); } else { - array_init(&m->procedures_to_generate, a, 0, c->info.all_procedures.count); + mpsc_init(&m->procedures_to_generate, a); map_init(&m->procedure_values, c->info.all_procedures.count*2); + array_init(&m->generated_procedures, a, 0, c->info.all_procedures.count*2); } + + 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); @@ -213,6 +217,14 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { } } } + + if (LLVM_WEAK_MONOMORPHIZATION) { + lbModule *m = gb_alloc_item(permanent_allocator(), lbModule); + gen->equal_module = m; + m->checker = c; + map_set(&gen->modules, cast(void *)m, m); // point to itself just add it to the list + lb_init_module(m, do_threading); + } } gen->default_module.gen = gen; @@ -3169,7 +3181,7 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr value.value = p->value; value.type = p->type; - array_add(&m->procedures_to_generate, p); + mpsc_enqueue(&m->procedures_to_generate, p); if (parent != nullptr) { array_add(&parent->children, p); } else { diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index f71b693eb..3b8a829b7 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -835,7 +835,7 @@ gb_internal void lb_build_nested_proc(lbProcedure *p, AstProcLit *pd, Entity *e) lb_add_entity(m, e, value); array_add(&p->children, nested_proc); - array_add(&m->procedures_to_generate, nested_proc); + mpsc_enqueue(&m->procedures_to_generate, nested_proc); } diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index 590920b59..f247fa2a7 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -92,7 +92,7 @@ gb_internal void lb_build_constant_value_decl(lbProcedure *p, AstValueDecl *vd) value.value = nested_proc->value; value.type = nested_proc->type; - array_add(&p->module->procedures_to_generate, nested_proc); + mpsc_enqueue(&p->module->procedures_to_generate, nested_proc); array_add(&p->children, nested_proc); string_map_set(&p->module->members, name, value); } From 76705c680087d58b6b50492f848a4804318d1ba8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 14:18:09 +0100 Subject: [PATCH 086/162] Convert `missing_procedures_to_check` to a queue --- src/llvm_backend.cpp | 14 +++++++------- src/llvm_backend.hpp | 3 +-- src/llvm_backend_general.cpp | 12 ++++-------- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 3ac5970f8..02df76243 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -2477,15 +2477,9 @@ gb_internal void lb_generate_procedures(lbGenerator *gen, bool do_threading) { gb_internal WORKER_TASK_PROC(lb_generate_missing_procedures_to_check_worker_proc) { lbModule *m = cast(lbModule *)data; - for (isize i = 0; i < m->missing_procedures_to_check.count; i++) { - lbProcedure *p = m->missing_procedures_to_check[i]; + for (lbProcedure *p = nullptr; mpsc_dequeue(&m->missing_procedures_to_check, &p); /**/) { debugf("Generate missing procedure: %.*s module %p\n", LIT(p->name), m); - isize count = m->procedures_to_generate.count; lb_generate_procedure(m, p); - isize new_count = m->procedures_to_generate.count; - if (count != new_count) { - gb_printf_err("NEW STUFF!\n"); - } } return 0; } @@ -2505,6 +2499,12 @@ gb_internal void lb_generate_missing_procedures(lbGenerator *gen, bool do_thread lb_generate_missing_procedures_to_check_worker_proc(m); } } + + for (auto const &entry : gen->modules) { + lbModule *m = entry.value; + GB_ASSERT(m->missing_procedures_to_check.count == 0); + GB_ASSERT(m->procedures_to_generate.count == 0); + } } gb_internal void lb_debug_info_complete_types_and_finalize(lbGenerator *gen) { diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 6d94df399..559ec8300 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -176,8 +176,7 @@ struct lbModule { StringMap procedures; PtrMap procedure_values; - BlockingMutex missing_procedures_to_check_mutex; - Array missing_procedures_to_check; + MPSCQueue missing_procedures_to_check; StringMap const_strings; String16Map const_string16s; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index c84edc178..cb8c9406e 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -109,7 +109,7 @@ gb_internal WORKER_TASK_PROC(lb_init_module_worker_proc) { 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); + mpsc_init(&m->missing_procedures_to_check, a); map_init(&m->debug_values); string_map_init(&m->objc_classes); @@ -3079,18 +3079,16 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) // defer (mutex_unlock(&gen->anonymous_proc_lits_mutex)); GB_ASSERT(other_module != nullptr); - mutex_lock(&other_module->missing_procedures_to_check_mutex); rw_mutex_shared_lock(&other_module->values_mutex); auto *found = map_get(&other_module->values, e); rw_mutex_shared_unlock(&other_module->values_mutex); if (found == nullptr) { // THIS IS THE RACE CONDITION lbProcedure *missing_proc_in_other_module = lb_create_procedure(other_module, e, false); - array_add(&other_module->missing_procedures_to_check, missing_proc_in_other_module); + mpsc_enqueue(&other_module->missing_procedures_to_check, missing_proc_in_other_module); } - mutex_unlock(&other_module->missing_procedures_to_check_mutex); } else { - array_add(&m->missing_procedures_to_check, missing_proc); + mpsc_enqueue(&m->missing_procedures_to_check, missing_proc); } rw_mutex_shared_lock(&m->values_mutex); @@ -3157,16 +3155,14 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr if (target_module != m) { - mutex_lock(&target_module->missing_procedures_to_check_mutex); rw_mutex_shared_lock(&target_module->values_mutex); lbValue *found = map_get(&target_module->values, e); rw_mutex_shared_unlock(&target_module->values_mutex); if (found == nullptr) { lbProcedure *missing_proc_in_target_module = lb_create_procedure(target_module, e, false); - array_add(&target_module->missing_procedures_to_check, missing_proc_in_target_module); + mpsc_enqueue(&target_module->missing_procedures_to_check, missing_proc_in_target_module); } - mutex_unlock(&target_module->missing_procedures_to_check_mutex); lbProcedure *p = lb_create_procedure(m, e, true); From 655176e5e7ab10b66f7fd936a23f7472a818e5f2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 15:17:38 +0100 Subject: [PATCH 087/162] Remove comments of dead code --- src/llvm_backend_general.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index cb8c9406e..f42087f6b 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -3075,9 +3075,6 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) } if (ignore_body) { - // mutex_lock(&gen->anonymous_proc_lits_mutex); - // defer (mutex_unlock(&gen->anonymous_proc_lits_mutex)); - GB_ASSERT(other_module != nullptr); rw_mutex_shared_lock(&other_module->values_mutex); auto *found = map_get(&other_module->values, e); @@ -3163,7 +3160,6 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr mpsc_enqueue(&target_module->missing_procedures_to_check, missing_proc_in_target_module); } - lbProcedure *p = lb_create_procedure(m, e, true); lbValue value = {}; From 9b8771b475a2e0a75205408b2615f69d65a329bd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 16:15:04 +0100 Subject: [PATCH 088/162] Handle missing procedures better --- src/llvm_backend.cpp | 16 +++++++++++----- src/llvm_backend.hpp | 6 +++--- src/llvm_backend_general.cpp | 9 +++++++-- src/llvm_backend_proc.cpp | 1 - 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 02df76243..1742271b6 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -12,7 +12,7 @@ #endif #ifndef LLVM_WEAK_MONOMORPHIZATION -#define LLVM_WEAK_MONOMORPHIZATION build_context.internal_weak_monomorphization +#define LLVM_WEAK_MONOMORPHIZATION 1 #endif @@ -2478,8 +2478,14 @@ gb_internal void lb_generate_procedures(lbGenerator *gen, bool do_threading) { gb_internal WORKER_TASK_PROC(lb_generate_missing_procedures_to_check_worker_proc) { lbModule *m = cast(lbModule *)data; for (lbProcedure *p = nullptr; mpsc_dequeue(&m->missing_procedures_to_check, &p); /**/) { - debugf("Generate missing procedure: %.*s module %p\n", LIT(p->name), m); - lb_generate_procedure(m, p); + if (!p->is_done.load(std::memory_order_relaxed)) { + debugf("Generate missing procedure: %.*s module %p\n", LIT(p->name), m); + lb_generate_procedure(m, p); + } + + for (lbProcedure *nested = nullptr; mpsc_dequeue(&m->procedures_to_generate, &nested); /**/) { + mpsc_enqueue(&m->missing_procedures_to_check, nested); + } } return 0; } @@ -2860,7 +2866,7 @@ gb_internal lbProcedure *lb_create_main_procedure(lbModule *m, lbProcedure *star } gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) { - if (p->is_done) { + if (p->is_done.load(std::memory_order_relaxed)) { return; } @@ -2869,7 +2875,7 @@ gb_internal void lb_generate_procedure(lbModule *m, lbProcedure *p) { lb_begin_procedure_body(p); lb_build_stmt(p, p->body); lb_end_procedure_body(p); - p->is_done = true; + p->is_done.store(true, std::memory_order_relaxed); m->curr_procedure = nullptr; } else if (p->generate_body != nullptr) { p->generate_body(m, p); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 559ec8300..cee46701f 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -366,9 +366,9 @@ struct lbProcedure { lbFunctionType *abi_function_type; - LLVMValueRef value; - LLVMBuilderRef builder; - bool is_done; + LLVMValueRef value; + LLVMBuilderRef builder; + std::atomic is_done; lbAddr return_ptr; Array defer_stmts; diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index f42087f6b..02f67b1b8 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -3082,9 +3082,12 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) if (found == nullptr) { // THIS IS THE RACE CONDITION lbProcedure *missing_proc_in_other_module = lb_create_procedure(other_module, e, false); - mpsc_enqueue(&other_module->missing_procedures_to_check, missing_proc_in_other_module); + if (!missing_proc_in_other_module->is_done.load(std::memory_order_relaxed)) { + mpsc_enqueue(&other_module->missing_procedures_to_check, missing_proc_in_other_module); + } } } else { + GB_PANIC("missing procedure: %.*s", LIT(missing_proc->name)); mpsc_enqueue(&m->missing_procedures_to_check, missing_proc); } @@ -3157,7 +3160,9 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr rw_mutex_shared_unlock(&target_module->values_mutex); if (found == nullptr) { lbProcedure *missing_proc_in_target_module = lb_create_procedure(target_module, e, false); - mpsc_enqueue(&target_module->missing_procedures_to_check, missing_proc_in_target_module); + if (!missing_proc_in_target_module->is_done.load(std::memory_order_relaxed)) { + mpsc_enqueue(&target_module->missing_procedures_to_check, missing_proc_in_target_module); + } } lbProcedure *p = lb_create_procedure(m, e, true); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 3b8a829b7..ee17ef771 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -99,7 +99,6 @@ gb_internal lbProcedure *lb_create_procedure(lbModule *m, Entity *entity, bool i } } - lbProcedure *p = gb_alloc_item(permanent_allocator(), lbProcedure); p->module = m; From f36ade8c640b78c7c2343d3d2bed54a224ef3deb Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Sep 2025 16:44:57 +0100 Subject: [PATCH 089/162] Remove extra checks --- src/llvm_backend_general.cpp | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 02f67b1b8..4b2e01c40 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -3082,12 +3082,9 @@ gb_internal lbValue lb_find_procedure_value_from_entity(lbModule *m, Entity *e) if (found == nullptr) { // THIS IS THE RACE CONDITION lbProcedure *missing_proc_in_other_module = lb_create_procedure(other_module, e, false); - if (!missing_proc_in_other_module->is_done.load(std::memory_order_relaxed)) { - mpsc_enqueue(&other_module->missing_procedures_to_check, missing_proc_in_other_module); - } + mpsc_enqueue(&other_module->missing_procedures_to_check, missing_proc_in_other_module); } } else { - GB_PANIC("missing procedure: %.*s", LIT(missing_proc->name)); mpsc_enqueue(&m->missing_procedures_to_check, missing_proc); } @@ -3133,15 +3130,9 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr token.string = name; Entity *e = alloc_entity_procedure(nullptr, token, type, pl->tags); e->file = expr->file(); - -#if 0 - e->pkg = e->file->pkg; e->scope = e->file->scope; - lbModule *target_module = lb_module_of_entity(gen, e, m); -#else lbModule *target_module = m; -#endif GB_ASSERT(target_module != nullptr); // NOTE(bill): this is to prevent a race condition since these procedure literals can be created anywhere at any time @@ -3160,9 +3151,7 @@ gb_internal lbValue lb_generate_anonymous_proc_lit(lbModule *m, String const &pr rw_mutex_shared_unlock(&target_module->values_mutex); if (found == nullptr) { lbProcedure *missing_proc_in_target_module = lb_create_procedure(target_module, e, false); - if (!missing_proc_in_target_module->is_done.load(std::memory_order_relaxed)) { - mpsc_enqueue(&target_module->missing_procedures_to_check, missing_proc_in_target_module); - } + mpsc_enqueue(&target_module->missing_procedures_to_check, missing_proc_in_target_module); } lbProcedure *p = lb_create_procedure(m, e, true); From c9eef7c835b46ace09e0e149ef52af44f7ff7f36 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 22 Sep 2025 21:10:01 +0100 Subject: [PATCH 090/162] Single thread `lb_create_startup_runtime_generate_body` --- src/llvm_backend.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 1742271b6..d0e9d293d 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -12,7 +12,7 @@ #endif #ifndef LLVM_WEAK_MONOMORPHIZATION -#define LLVM_WEAK_MONOMORPHIZATION 1 +#define LLVM_WEAK_MONOMORPHIZATION (USE_SEPARATE_MODULES && 1) #endif @@ -2070,11 +2070,10 @@ gb_internal lbProcedure *lb_create_startup_runtime(lbModule *main_module, lbProc LLVMSetVisibility(p->value, LLVMHiddenVisibility); LLVMSetLinkage(p->value, LLVMWeakAnyLinkage); - p->generate_body = lb_create_startup_runtime_generate_body; p->global_variables = &global_variables; p->objc_names = objc_names; - mpsc_enqueue(&main_module->procedures_to_generate, p); + lb_create_startup_runtime_generate_body(main_module, p); return p; } From 2f132b51ce133cb21986987b38f1d75fa560fa1b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 23 Sep 2025 01:23:01 +0100 Subject: [PATCH 091/162] Add missing `gen` --- src/llvm_backend_general.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 4b2e01c40..3fa7a076e 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -221,7 +221,8 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { if (LLVM_WEAK_MONOMORPHIZATION) { lbModule *m = gb_alloc_item(permanent_allocator(), lbModule); gen->equal_module = m; - m->checker = c; + m->gen = gen; + m->checker = c; map_set(&gen->modules, cast(void *)m, m); // point to itself just add it to the list lb_init_module(m, do_threading); } From e9d20a9b4a069815f76a23ce5f429862b155b2d6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 23 Sep 2025 11:38:32 +0100 Subject: [PATCH 092/162] Reimplement `RwMutex` on non-windows systems --- src/threading.cpp | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/threading.cpp b/src/threading.cpp index a35176ce6..b1a0af2e4 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -423,28 +423,44 @@ gb_internal void semaphore_wait(Semaphore *s) { } struct RwMutex { - // TODO(bill): make this a proper RW mutex - BlockingMutex mutex; + BlockingMutex lock; + Condition cond; + int32_t readers; }; gb_internal void rw_mutex_lock(RwMutex *m) { - mutex_lock(&m->mutex); + mutex_lock(&m->lock); + while (m->readers != 0) { + condition_wait(&m->cond, &m->lock); + } } gb_internal bool rw_mutex_try_lock(RwMutex *m) { - return mutex_try_lock(&m->mutex); + // TODO(bill): rw_mutex_try_lock + rw_mutex_lock(m); + return true; } gb_internal void rw_mutex_unlock(RwMutex *m) { - mutex_unlock(&m->mutex); + condition_signal(&m->cond); + mutex_unlock(&m->lock); } gb_internal void rw_mutex_shared_lock(RwMutex *m) { - mutex_lock(&m->mutex); + mutex_lock(&m->lock); + m->readers += 1; + mutex_unlock(&m->lock); } gb_internal bool rw_mutex_try_shared_lock(RwMutex *m) { - return mutex_try_lock(&m->mutex); + // TODO(bill): rw_mutex_try_shared_lock + rw_mutex_shared_lock(m); + return true; } gb_internal void rw_mutex_shared_unlock(RwMutex *m) { - mutex_unlock(&m->mutex); + mutex_lock(&m->lock); + m->readers -= 1; + if (m->readers == 0) { + condition_signal(&m->cond); + } + mutex_unlock(&m->lock); } #endif From eca2758d8b4768ab370d9539c1098235f8a08076 Mon Sep 17 00:00:00 2001 From: Lucas Perlind Date: Wed, 24 Sep 2025 12:40:01 +1000 Subject: [PATCH 093/162] Revert "Reimplement `RwMutex` on non-windows systems" This reverts commit e9d20a9b4a069815f76a23ce5f429862b155b2d6. --- src/threading.cpp | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/src/threading.cpp b/src/threading.cpp index b1a0af2e4..a35176ce6 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -423,44 +423,28 @@ gb_internal void semaphore_wait(Semaphore *s) { } struct RwMutex { - BlockingMutex lock; - Condition cond; - int32_t readers; + // TODO(bill): make this a proper RW mutex + BlockingMutex mutex; }; gb_internal void rw_mutex_lock(RwMutex *m) { - mutex_lock(&m->lock); - while (m->readers != 0) { - condition_wait(&m->cond, &m->lock); - } + mutex_lock(&m->mutex); } gb_internal bool rw_mutex_try_lock(RwMutex *m) { - // TODO(bill): rw_mutex_try_lock - rw_mutex_lock(m); - return true; + return mutex_try_lock(&m->mutex); } gb_internal void rw_mutex_unlock(RwMutex *m) { - condition_signal(&m->cond); - mutex_unlock(&m->lock); + mutex_unlock(&m->mutex); } gb_internal void rw_mutex_shared_lock(RwMutex *m) { - mutex_lock(&m->lock); - m->readers += 1; - mutex_unlock(&m->lock); + mutex_lock(&m->mutex); } gb_internal bool rw_mutex_try_shared_lock(RwMutex *m) { - // TODO(bill): rw_mutex_try_shared_lock - rw_mutex_shared_lock(m); - return true; + return mutex_try_lock(&m->mutex); } gb_internal void rw_mutex_shared_unlock(RwMutex *m) { - mutex_lock(&m->lock); - m->readers -= 1; - if (m->readers == 0) { - condition_signal(&m->cond); - } - mutex_unlock(&m->lock); + mutex_unlock(&m->mutex); } #endif From 443b6e1641cad88fef72264a2f9b08c9448e6f99 Mon Sep 17 00:00:00 2001 From: Wrath Date: Tue, 23 Sep 2025 23:03:20 -0400 Subject: [PATCH 094/162] Unify filepath.join behavior on between unix/windows --- core/path/filepath/path_windows.odin | 29 +++++++++++++--------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/core/path/filepath/path_windows.odin b/core/path/filepath/path_windows.odin index 24c6e00a5..b3f4eee9e 100644 --- a/core/path/filepath/path_windows.odin +++ b/core/path/filepath/path_windows.odin @@ -32,7 +32,6 @@ is_UNC :: proc(path: string) -> bool { return volume_name_len(path) > 2 } - is_abs :: proc(path: string) -> bool { if is_reserved_name(path) { return true @@ -50,7 +49,6 @@ is_abs :: proc(path: string) -> bool { return is_slash(path[0]) } - @(private) temp_full_path :: proc(name: string) -> (path: string, err: os.Error) { ta := context.temp_allocator @@ -76,8 +74,6 @@ temp_full_path :: proc(name: string) -> (path: string, err: os.Error) { return win32.utf16_to_utf8(buf[:n], ta) } - - abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator) full_path, err := temp_full_path(path) @@ -88,17 +84,16 @@ abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { return p, true } - -join :: proc(elems: []string, allocator := context.allocator) -> string { +join :: proc(elems: []string, allocator := context.allocator) -> (string, runtime.Allocator_Error) #optional_allocator_error { for e, i in elems { if e != "" { return join_non_empty(elems[i:], allocator) } } - return "" + return "", nil } -join_non_empty :: proc(elems: []string, allocator := context.allocator) -> string { +join_non_empty :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) { context.allocator = allocator runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = allocator == context.temp_allocator) @@ -110,23 +105,25 @@ join_non_empty :: proc(elems: []string, allocator := context.allocator) -> strin break } } - s := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) - s = strings.concatenate({elems[0], s}, context.temp_allocator) + s := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return + s = strings.concatenate({elems[0], s}, context.temp_allocator) or_return return clean(s) } - p := clean(strings.join(elems, SEPARATOR_STRING, context.temp_allocator)) + p := strings.join(elems, SEPARATOR_STRING, context.temp_allocator) or_return + p = clean(p) or_return if !is_UNC(p) { - return p + return p, nil } - - head := clean(elems[0], context.temp_allocator) + + head := clean(elems[0], context.temp_allocator) or_return if is_UNC(head) { - return p + return p, nil } delete(p) // It is not needed now - tail := clean(strings.join(elems[1:], SEPARATOR_STRING, context.temp_allocator), context.temp_allocator) + tail := strings.join(elems[1:], SEPARATOR_STRING, context.temp_allocator) or_return + tail = clean(tail, context.temp_allocator) or_return if head[len(head)-1] == SEPARATOR { return strings.concatenate({head, tail}) } From 15b4b9277a58e0c10e4da698701fbf806d0c45b9 Mon Sep 17 00:00:00 2001 From: Lucas Perlind Date: Wed, 24 Sep 2025 12:42:20 +1000 Subject: [PATCH 095/162] spin in recursive mutex lock; use compare exchange for broadcast --- src/thread_pool.cpp | 20 ++++++++++++++------ src/threading.cpp | 12 ++++++++++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/thread_pool.cpp b/src/thread_pool.cpp index 8363a4553..ca6483fd9 100644 --- a/src/thread_pool.cpp +++ b/src/thread_pool.cpp @@ -19,6 +19,11 @@ enum GrabState { Grab_Failed = 2, }; +enum BroadcastWaitState { + Nobody_Waiting = 0, + Someone_Waiting = 1, +}; + struct ThreadPool { gbAllocator threads_allocator; Slice threads; @@ -54,8 +59,8 @@ gb_internal void thread_pool_destroy(ThreadPool *pool) { for_array_off(i, 1, pool->threads) { Thread *t = &pool->threads[i]; - pool->tasks_available.fetch_add(1, std::memory_order_acquire); - futex_broadcast(&pool->tasks_available); + pool->tasks_available.store(Nobody_Waiting); + futex_broadcast(&t->pool->tasks_available); thread_join_and_destroy(t); } @@ -87,8 +92,10 @@ void thread_pool_queue_push(Thread *thread, WorkerTask task) { thread->queue.bottom.store(bot + 1, std::memory_order_relaxed); thread->pool->tasks_left.fetch_add(1, std::memory_order_release); - thread->pool->tasks_available.fetch_add(1, std::memory_order_relaxed); - futex_broadcast(&thread->pool->tasks_available); + i32 state = Someone_Waiting; + if (thread->pool->tasks_available.compare_exchange_strong(state, Nobody_Waiting)) { + futex_broadcast(&thread->pool->tasks_available); + } } GrabState thread_pool_queue_take(Thread *thread, WorkerTask *task) { @@ -230,12 +237,13 @@ gb_internal THREAD_PROC(thread_pool_thread_proc) { } // if we've done all our work, and there's nothing to steal, go to sleep - state = pool->tasks_available.load(std::memory_order_acquire); + pool->tasks_available.store(Someone_Waiting); if (!pool->running) { break; } - futex_wait(&pool->tasks_available, state); + futex_wait(&pool->tasks_available, Someone_Waiting); main_loop_continue:; } return 0; } + diff --git a/src/threading.cpp b/src/threading.cpp index a35176ce6..84f09912d 100644 --- a/src/threading.cpp +++ b/src/threading.cpp @@ -195,7 +195,13 @@ gb_internal void mutex_lock(RecursiveMutex *m) { // inside the lock return; } - futex_wait(&m->owner, prev_owner); + + // NOTE(lucas): we are doing spin lock since futex signal is expensive on OSX. The recursive locks are + // very short lived so we don't hit this mega often and I see no perform regression on windows (with + // a performance uplift on OSX). + + //futex_wait(&m->owner, prev_owner); + yield_thread(); } } gb_internal bool mutex_try_lock(RecursiveMutex *m) { @@ -216,7 +222,9 @@ gb_internal void mutex_unlock(RecursiveMutex *m) { return; } m->owner.exchange(0, std::memory_order_release); - futex_signal(&m->owner); + // NOTE(lucas): see comment about spin lock in mutex_lock above + + // futex_signal(&m->owner); // outside the lock } From 90d1229ead934ae78678dac835dc380db6cfd7ef Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Sep 2025 09:13:48 +0100 Subject: [PATCH 096/162] Make `LLVM_WEAK_MONOMORPHIZATION` opt-in again --- src/llvm_backend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index d0e9d293d..33b03e235 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -12,7 +12,7 @@ #endif #ifndef LLVM_WEAK_MONOMORPHIZATION -#define LLVM_WEAK_MONOMORPHIZATION (USE_SEPARATE_MODULES && 1) +#define LLVM_WEAK_MONOMORPHIZATION (USE_SEPARATE_MODULES && build_context.internal_weak_monomorphization) #endif From 31f0aaa62f2799d7c5e38c10cabe034284ae8e5d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Sep 2025 09:55:22 +0100 Subject: [PATCH 097/162] Try to improve const `union` LLVM construction --- src/llvm_backend.cpp | 2 +- src/llvm_backend_const.cpp | 94 ++++++++++++++++++++++++++++++++++++ src/llvm_backend_expr.cpp | 15 ++++++ src/llvm_backend_general.cpp | 80 +++++++++++++++++++++++------- 4 files changed, 173 insertions(+), 18 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 33b03e235..3af473c3a 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1914,7 +1914,7 @@ gb_internal WORKER_TASK_PROC(lb_llvm_module_verification_worker_proc) { lbModule *m = cast(lbModule *)data; if (LLVMVerifyModule(m->mod, LLVMReturnStatusAction, &llvm_error)) { - gb_printf_err("LLVM Error:\n%s\n", llvm_error); + gb_printf_err("LLVM Error in module %s:\n%s\n", m->module_name, llvm_error); if (build_context.keep_temp_files) { TIME_SECTION("LLVM Print Module to File"); String filepath_ll = lb_filepath_ll_for_module(m); diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 98e50dd78..781cc52b8 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -537,6 +537,100 @@ gb_internal bool lb_is_nested_possibly_constant(Type *ft, Selection const &sel, return lb_is_elem_const(elem, ft); } +gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef variant_value, Type *variant_type, Type *union_type) { + Type *bt = base_type(union_type); + GB_ASSERT(bt->kind == Type_Union); + GB_ASSERT(lb_type(m, variant_type) == LLVMTypeOf(variant_value)); + + if (bt->Union.variants.count == 0) { + GB_ASSERT(LLVMIsNull(variant_value)); + return variant_value; + } + + LLVMTypeRef llvm_type = lb_type(m, union_type); + LLVMTypeRef llvm_variant_type = lb_type(m, variant_type); + + if (is_type_union_maybe_pointer(bt)) { + GB_ASSERT(lb_sizeof(LLVMTypeOf(variant_value)) == lb_sizeof(llvm_type)); + return LLVMConstBitCast(variant_value, llvm_type); + } + + if (bt->Union.variants.count == 1) { + unsigned long long the_tag = cast(unsigned long long)union_variant_index(union_type, variant_type); + LLVMTypeRef tag_type = lb_type(m, union_tag_type(bt)); + + LLVMValueRef values[3] = {}; + unsigned i = 0; + values[i++] = variant_value; + values[i++] = LLVMConstInt(tag_type, the_tag, false); + + i64 used_size = bt->Union.variant_block_size + lb_sizeof(tag_type); + i64 padding = type_size_of(union_type) - used_size; + i64 align = type_align_of(union_type); + if (padding > 0) { + LLVMTypeRef padding_type = lb_type_padding_filler(m, padding, align); + values[i++] = LLVMConstNull(padding_type); + } + + return LLVMConstNamedStruct(llvm_type, values, i); + } + + LLVMTypeRef block_type = LLVMStructGetTypeAtIndex(llvm_type, 0); + + LLVMTypeRef block_padding = nullptr; + i64 block_padding_size = bt->Union.variant_block_size - type_size_of(variant_type); + if (block_padding_size > 0) { + block_padding = lb_type_padding_filler(m, block_padding_size, type_align_of(variant_type)); + return nullptr; + } + + LLVMTypeRef tag_type = lb_type(m, union_tag_type(bt)); + + i64 used_size = bt->Union.variant_block_size + lb_sizeof(tag_type); + i64 padding = type_size_of(union_type) - used_size; + i64 align = type_align_of(union_type); + LLVMTypeRef padding_type = nullptr; + if (padding > 0) { + padding_type = lb_type_padding_filler(m, padding, align); + } + + + unsigned i = 0; + LLVMValueRef values[3] = {}; + + LLVMValueRef variant_value_wrapped = variant_value; + + if (lb_sizeof(llvm_variant_type) == 0) { + variant_value_wrapped = LLVMConstNull(block_type); + } else if (block_type != llvm_variant_type) { + return nullptr; + } + + values[i++] = variant_value_wrapped; + + if (block_padding_size > 0) { + values[i++] = LLVMConstNull(block_padding); + } + unsigned long long the_tag = cast(unsigned long long)union_variant_index(union_type, variant_type); + values[i++] = LLVMConstInt(tag_type, the_tag, false); + if (padding > 0) { + values[i++] = LLVMConstNull(padding_type); + } + return LLVMConstNamedStruct(llvm_type, values, i); +} + +gb_internal bool lb_try_construct_const_union(lbModule *m, lbValue *value, Type *variant_type, Type *union_type) { + if (lb_is_const(*value)) { + LLVMValueRef res = lb_construct_const_union(m, value->value, variant_type, union_type); + if (res != nullptr) { + *value = {res, union_type}; + return true; + } + } + return false; +} + + gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lbConstContext cc) { if (cc.allow_local) { cc.is_rodata = false; diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index cff91813e..4a1f39c19 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -2495,6 +2495,11 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { Type *vt = dst->Union.variants[0]; if (internal_check_is_assignable_to(src_type, vt)) { value = lb_emit_conv(p, value, vt); + if (lb_is_const(value)) { + LLVMValueRef res = lb_construct_const_union(m, value.value, vt, t); + return {res, t}; + } + lbAddr parent = lb_add_local_generated(p, t, true); lb_emit_store_union_variant(p, parent.addr, value, vt); return lb_addr_load(p, parent); @@ -2503,11 +2508,18 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { for (Type *vt : dst->Union.variants) { if (src_type == t_llvm_bool && is_type_boolean(vt)) { value = lb_emit_conv(p, value, vt); + if (lb_try_construct_const_union(m, &value, vt, t)) { + return value; + } + lbAddr parent = lb_add_local_generated(p, t, true); lb_emit_store_union_variant(p, parent.addr, value, vt); return lb_addr_load(p, parent); } if (are_types_identical(src_type, vt)) { + if (lb_try_construct_const_union(m, &value, vt, t)) { + return value; + } lbAddr parent = lb_add_local_generated(p, t, true); lb_emit_store_union_variant(p, parent.addr, value, vt); return lb_addr_load(p, parent); @@ -2545,6 +2557,9 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { if (valid_count == 1) { Type *vt = dst->Union.variants[first_success_index]; value = lb_emit_conv(p, value, vt); + if (lb_try_construct_const_union(m, &value, vt, t)) { + return value; + } lbAddr parent = lb_add_local_generated(p, t, true); lb_emit_store_union_variant(p, parent.addr, value, vt); return lb_addr_load(p, parent); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 3fa7a076e..fc770102c 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -1719,8 +1719,69 @@ gb_internal LLVMTypeRef lb_type_internal_for_procedures_raw(lbModule *m, Type *t map_set(&m->func_raw_types, type, new_abi_fn_type); return new_abi_fn_type; - } + + +gb_internal LLVMTypeRef lb_type_internal_union_block_type(lbModule *m, Type *type) { + GB_ASSERT(type->kind == Type_Union); + + if (type->Union.variants.count <= 0) { + return nullptr; + } + if (type->Union.variants.count == 1) { + return lb_type(m, type->Union.variants[0]); + } + + i64 align = type_align_of(type); + i64 size = type_size_of(type); + gb_unused(size); + + unsigned block_size = cast(unsigned)type->Union.variant_block_size; + + bool all_pointers = align == build_context.ptr_size; + for (isize i = 0; all_pointers && i < type->Union.variants.count; i++) { + Type *t = type->Union.variants[i]; + if (!is_type_internally_pointer_like(t)) { + all_pointers = false; + } + } + if (all_pointers) { + return lb_type(m, t_rawptr); + } + + { + Type *pt = type->Union.variants[0]; + for (isize i = 1; i < type->Union.variants.count; i++) { + Type *t = type->Union.variants[i]; + if (!are_types_identical(pt, t)) { + goto end_check_for_all_the_same; + } + } + return lb_type(m, pt); + } end_check_for_all_the_same:; + + { + Type *first_different = nullptr; + for (isize i = 0; i < type->Union.variants.count; i++) { + Type *t = type->Union.variants[i]; + if (type_size_of(t) == 0) { + continue; + } + if (first_different == nullptr) { + first_different = t; + } else if (!are_types_identical(first_different, t)) { + goto end_rest_zero_except_one; + } + } + if (first_different != nullptr) { + return lb_type(m, first_different); + } + } end_rest_zero_except_one:; + + return lb_type_padding_filler(m, block_size, align); +} + + gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { LLVMContextRef ctx = m->ctx; i64 size = type_size_of(type); // Check size @@ -2233,8 +2294,6 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { return LLVMStructTypeInContext(ctx, fields, gb_count_of(fields), false); } - unsigned block_size = cast(unsigned)type->Union.variant_block_size; - auto fields = array_make(temporary_allocator(), 0, 3); if (is_type_union_maybe_pointer(type)) { LLVMTypeRef variant = lb_type(m, type->Union.variants[0]); @@ -2252,20 +2311,7 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { array_add(&fields, padding_type); } } else { - LLVMTypeRef block_type = nullptr; - - bool all_pointers = align == build_context.ptr_size; - for (isize i = 0; all_pointers && i < type->Union.variants.count; i++) { - Type *t = type->Union.variants[i]; - if (!is_type_internally_pointer_like(t)) { - all_pointers = false; - } - } - if (all_pointers) { - block_type = lb_type(m, t_rawptr); - } else { - block_type = lb_type_padding_filler(m, block_size, align); - } + LLVMTypeRef block_type = lb_type_internal_union_block_type(m, type); LLVMTypeRef tag_type = lb_type(m, union_tag_type(type)); array_add(&fields, block_type); From bad495519b604081e34e69bbafd33c81f4d3fc1e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Sep 2025 10:08:25 +0100 Subject: [PATCH 098/162] Improve const union attemps --- src/llvm_backend_const.cpp | 26 +++++++++++++++++++++++--- src/llvm_backend_general.cpp | 22 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 781cc52b8..9069f3bdf 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -547,6 +547,9 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari return variant_value; } + i64 block_size = bt->Union.variant_block_size; + i64 variant_size = type_size_of(variant_type); + LLVMTypeRef llvm_type = lb_type(m, union_type); LLVMTypeRef llvm_variant_type = lb_type(m, variant_type); @@ -564,7 +567,7 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari values[i++] = variant_value; values[i++] = LLVMConstInt(tag_type, the_tag, false); - i64 used_size = bt->Union.variant_block_size + lb_sizeof(tag_type); + i64 used_size = block_size + lb_sizeof(tag_type); i64 padding = type_size_of(union_type) - used_size; i64 align = type_align_of(union_type); if (padding > 0) { @@ -578,7 +581,7 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari LLVMTypeRef block_type = LLVMStructGetTypeAtIndex(llvm_type, 0); LLVMTypeRef block_padding = nullptr; - i64 block_padding_size = bt->Union.variant_block_size - type_size_of(variant_type); + i64 block_padding_size = block_size - variant_size; if (block_padding_size > 0) { block_padding = lb_type_padding_filler(m, block_padding_size, type_align_of(variant_type)); return nullptr; @@ -586,7 +589,7 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari LLVMTypeRef tag_type = lb_type(m, union_tag_type(bt)); - i64 used_size = bt->Union.variant_block_size + lb_sizeof(tag_type); + i64 used_size = block_size + lb_sizeof(tag_type); i64 padding = type_size_of(union_type) - used_size; i64 align = type_align_of(union_type); LLVMTypeRef padding_type = nullptr; @@ -603,9 +606,26 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari if (lb_sizeof(llvm_variant_type) == 0) { variant_value_wrapped = LLVMConstNull(block_type); } else if (block_type != llvm_variant_type) { + if (block_size != variant_size) { + return nullptr; + } + if (LLVMGetTypeKind(block_type) == LLVMIntegerTypeKind) { + switch (LLVMGetTypeKind(llvm_variant_type)) { + case LLVMHalfTypeKind: + case LLVMFloatTypeKind: + case LLVMDoubleTypeKind: + variant_value_wrapped = LLVMConstBitCast(variant_value_wrapped, block_type); + goto assign_value_wrapped; + case LLVMPointerTypeKind: + variant_value_wrapped = LLVMConstPtrToInt(variant_value_wrapped, block_type); + goto assign_value_wrapped; + } + } + return nullptr; } +assign_value_wrapped:; values[i++] = variant_value_wrapped; if (block_padding_size > 0) { diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index fc770102c..4c60d9f4c 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -1778,6 +1778,28 @@ gb_internal LLVMTypeRef lb_type_internal_union_block_type(lbModule *m, Type *typ } } end_rest_zero_except_one:; + // { + // LLVMTypeRef first_different = nullptr; + // for (isize i = 0; i < type->Union.variants.count; i++) { + // Type *t = type->Union.variants[i]; + // if (type_size_of(t) == 0) { + // continue; + // } + // if (first_different == nullptr) { + // first_different = lb_type(m, base_type(t)); + // } else { + // LLVMTypeRef llvm_t = lb_type(m, base_type(t)); + // if (llvm_t != first_different) { + // goto end_rest_zero_except_one_llvm_like; + // } + // } + // } + // if (first_different != nullptr) { + // return first_different; + // } + // } end_rest_zero_except_one_llvm_like:; + + return lb_type_padding_filler(m, block_size, align); } From ad85ec765bcf3b3e501f445ab4e35169bc7576d7 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Sep 2025 10:21:38 +0100 Subject: [PATCH 099/162] More const union improvements --- src/llvm_backend_const.cpp | 71 ++++++++++++++++++++++++++++-------- src/llvm_backend_general.cpp | 5 ++- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 9069f3bdf..219700ffd 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -542,6 +542,12 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari GB_ASSERT(bt->kind == Type_Union); GB_ASSERT(lb_type(m, variant_type) == LLVMTypeOf(variant_value)); + LLVMTypeRef llvm_type = lb_type(m, union_type); + + if (LLVMIsNull(variant_value)) { + return LLVMConstNull(llvm_type); + } + if (bt->Union.variants.count == 0) { GB_ASSERT(LLVMIsNull(variant_value)); return variant_value; @@ -550,7 +556,6 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari i64 block_size = bt->Union.variant_block_size; i64 variant_size = type_size_of(variant_type); - LLVMTypeRef llvm_type = lb_type(m, union_type); LLVMTypeRef llvm_variant_type = lb_type(m, variant_type); if (is_type_union_maybe_pointer(bt)) { @@ -580,13 +585,6 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari LLVMTypeRef block_type = LLVMStructGetTypeAtIndex(llvm_type, 0); - LLVMTypeRef block_padding = nullptr; - i64 block_padding_size = block_size - variant_size; - if (block_padding_size > 0) { - block_padding = lb_type_padding_filler(m, block_padding_size, type_align_of(variant_type)); - return nullptr; - } - LLVMTypeRef tag_type = lb_type(m, union_tag_type(bt)); i64 used_size = block_size + lb_sizeof(tag_type); @@ -601,12 +599,55 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari unsigned i = 0; LLVMValueRef values[3] = {}; - LLVMValueRef variant_value_wrapped = variant_value; + LLVMValueRef block_value = variant_value; if (lb_sizeof(llvm_variant_type) == 0) { - variant_value_wrapped = LLVMConstNull(block_type); + block_value = LLVMConstNull(block_type); } else if (block_type != llvm_variant_type) { if (block_size != variant_size) { + + if (LLVMGetTypeKind(block_type) == LLVMArrayTypeKind && + LLVMGetTypeKind(llvm_variant_type) == LLVMIntegerTypeKind) { + LLVMTypeRef elem = LLVMGetElementType(block_type); + unsigned count = LLVMGetArrayLength(block_type); + if (elem == llvm_variant_type) { + LLVMValueRef *elems = temporary_alloc_array(count); + elems[0] = variant_value; + for (unsigned j = 1; j < count; j++) { + elems[j] = LLVMConstNull(elem); + } + block_value = LLVMConstArray(elem, elems, count); + + goto assign_value_wrapped; + } else if (!is_type_different_to_arch_endianness(variant_type)) { + i64 elem_size = lb_sizeof(elem); + i64 variant_size = lb_sizeof(llvm_variant_type); + if (elem_size > variant_size) { + u64 val = LLVMConstIntGetZExtValue(variant_value); + + LLVMValueRef *elems = temporary_alloc_array(count); + elems[0] = LLVMConstInt(elem, val, false); + for (unsigned j = 1; j < count; j++) { + elems[j] = LLVMConstNull(elem); + } + block_value = LLVMConstArray(elem, elems, count); + + goto assign_value_wrapped; + } + } + } else if (LLVMGetTypeKind(block_type) == LLVMIntegerTypeKind && + LLVMGetTypeKind(llvm_variant_type) == LLVMIntegerTypeKind) { + if (!is_type_different_to_arch_endianness(variant_type)) { + i64 variant_size = lb_sizeof(llvm_variant_type); + if (block_size > variant_size) { + u64 val = LLVMConstIntGetZExtValue(variant_value); + block_value = LLVMConstInt(block_type, val, false); + + goto assign_value_wrapped; + } + } + } + return nullptr; } if (LLVMGetTypeKind(block_type) == LLVMIntegerTypeKind) { @@ -614,10 +655,10 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari case LLVMHalfTypeKind: case LLVMFloatTypeKind: case LLVMDoubleTypeKind: - variant_value_wrapped = LLVMConstBitCast(variant_value_wrapped, block_type); + block_value = LLVMConstBitCast(block_value, block_type); goto assign_value_wrapped; case LLVMPointerTypeKind: - variant_value_wrapped = LLVMConstPtrToInt(variant_value_wrapped, block_type); + block_value = LLVMConstPtrToInt(block_value, block_type); goto assign_value_wrapped; } } @@ -626,11 +667,8 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari } assign_value_wrapped:; - values[i++] = variant_value_wrapped; + values[i++] = block_value; - if (block_padding_size > 0) { - values[i++] = LLVMConstNull(block_padding); - } unsigned long long the_tag = cast(unsigned long long)union_variant_index(union_type, variant_type); values[i++] = LLVMConstInt(tag_type, the_tag, false); if (padding > 0) { @@ -646,6 +684,7 @@ gb_internal bool lb_try_construct_const_union(lbModule *m, lbValue *value, Type *value = {res, union_type}; return true; } + // gb_printf_err("%s -> %s\n", LLVMPrintValueToString(value->value), LLVMPrintTypeToString(lb_type(m, union_type))); } return false; } diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 4c60d9f4c..6e513a075 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -1733,10 +1733,11 @@ gb_internal LLVMTypeRef lb_type_internal_union_block_type(lbModule *m, Type *typ } i64 align = type_align_of(type); - i64 size = type_size_of(type); - gb_unused(size); unsigned block_size = cast(unsigned)type->Union.variant_block_size; + if (block_size == 0) { + return lb_type_padding_filler(m, block_size, align); + } bool all_pointers = align == build_context.ptr_size; for (isize i = 0; all_pointers && i < type->Union.variants.count; i++) { From 43e0d6966ec4943750684c851bd6ca2ec6c0b3bb Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Sep 2025 14:07:09 +0100 Subject: [PATCH 100/162] More improves for const union stuff! --- src/llvm_backend_const.cpp | 218 ++++++++++++++++++++++++++++++++----- 1 file changed, 193 insertions(+), 25 deletions(-) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 219700ffd..39a223027 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -537,6 +537,84 @@ gb_internal bool lb_is_nested_possibly_constant(Type *ft, Selection const &sel, return lb_is_elem_const(elem, ft); } +gb_internal Slice lb_construct_const_union_flatten_values(lbModule *m, LLVMValueRef variant_value, Type *variant_type, LLVMTypeRef elem) { + LLVMTypeRef llvm_variant_type = lb_type(m, variant_type); + LLVMTypeKind variant_kind = LLVMGetTypeKind(llvm_variant_type); + + if (are_types_identical(base_type(variant_type), t_string)) { + LLVMValueRef *elems = temporary_alloc_array(2); + elems[0] = llvm_const_extract_value(m, variant_value, 0); + elems[0] = LLVMConstPtrToInt(elems[0], elem); + + elems[1] = llvm_const_extract_value(m, variant_value, 1); + + return {elems, 2}; + } + + if (is_type_struct(variant_type)) { + // Type *st = base_type(variant_type); + // if (st->Struct.fields.count == 1) { + // return lb_construct_const_union_flatten_values(m, llvm_const_extract_value(m, variant_value, 0), st->Struct.fields[0]->type, elem); + // } + } else if (variant_kind == LLVMIntegerTypeKind) { + if (elem == llvm_variant_type) { + LLVMValueRef *elems = temporary_alloc_array(1); + elems[0] = variant_value; + return {elems, 1}; + } else if (!is_type_different_to_arch_endianness(variant_type)) { + i64 elem_size = lb_sizeof(elem); + i64 variant_size = lb_sizeof(llvm_variant_type); + if (elem_size > variant_size) { + u64 val = LLVMConstIntGetZExtValue(variant_value); + + LLVMValueRef *elems = temporary_alloc_array(1); + elems[0] = LLVMConstInt(elem, val, false); + return {elems, 1}; + } + } + } else if (!is_type_different_to_arch_endianness(variant_type)) { + switch (variant_kind) { + case LLVMHalfTypeKind: + { + LLVMBool loses = false; + f64 res = LLVMConstRealGetDouble(variant_value, &loses); + u16 val = f32_to_f16(cast(f32)res); + + LLVMValueRef *elems = temporary_alloc_array(1); + elems[0] = LLVMConstInt(elem, val, false); + return {elems, 1}; + } + break; + case LLVMFloatTypeKind: + { + LLVMBool loses = false; + f64 res = LLVMConstRealGetDouble(variant_value, &loses); + union { f32 f; u32 i; } val = {}; + val.f = cast(f32)res; + + LLVMValueRef *elems = temporary_alloc_array(1); + elems[0] = LLVMConstInt(elem, val.i, false); + return {elems, 1}; + } + break; + case LLVMDoubleTypeKind: + { + LLVMBool loses = false; + f64 res = LLVMConstRealGetDouble(variant_value, &loses); + union { f64 f; u64 i; } val = {}; + val.f = res; + + LLVMValueRef *elems = temporary_alloc_array(1); + elems[0] = LLVMConstInt(elem, val.i, false); + return {elems, 1}; + } + break; + } + } + + return {}; +} + gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef variant_value, Type *variant_type, Type *union_type) { Type *bt = base_type(union_type); GB_ASSERT(bt->kind == Type_Union); @@ -601,43 +679,98 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari LLVMValueRef block_value = variant_value; - if (lb_sizeof(llvm_variant_type) == 0) { + if (block_size == 0) { + block_value = LLVMConstNull(block_type); + } else if (lb_sizeof(llvm_variant_type) == 0) { block_value = LLVMConstNull(block_type); } else if (block_type != llvm_variant_type) { - if (block_size != variant_size) { + LLVMTypeKind block_kind = LLVMGetTypeKind(block_type); + LLVMTypeKind variant_kind = LLVMGetTypeKind(llvm_variant_type); - if (LLVMGetTypeKind(block_type) == LLVMArrayTypeKind && - LLVMGetTypeKind(llvm_variant_type) == LLVMIntegerTypeKind) { + if (block_size != variant_size) { + if (block_kind == LLVMArrayTypeKind) { LLVMTypeRef elem = LLVMGetElementType(block_type); unsigned count = LLVMGetArrayLength(block_type); - if (elem == llvm_variant_type) { - LLVMValueRef *elems = temporary_alloc_array(count); - elems[0] = variant_value; - for (unsigned j = 1; j < count; j++) { - elems[j] = LLVMConstNull(elem); - } - block_value = LLVMConstArray(elem, elems, count); - - goto assign_value_wrapped; - } else if (!is_type_different_to_arch_endianness(variant_type)) { - i64 elem_size = lb_sizeof(elem); - i64 variant_size = lb_sizeof(llvm_variant_type); - if (elem_size > variant_size) { - u64 val = LLVMConstIntGetZExtValue(variant_value); + if (variant_kind == LLVMIntegerTypeKind) { + if (elem == llvm_variant_type) { LLVMValueRef *elems = temporary_alloc_array(count); - elems[0] = LLVMConstInt(elem, val, false); + elems[0] = variant_value; for (unsigned j = 1; j < count; j++) { elems[j] = LLVMConstNull(elem); } block_value = LLVMConstArray(elem, elems, count); - goto assign_value_wrapped; + } else if (!is_type_different_to_arch_endianness(variant_type)) { + i64 elem_size = lb_sizeof(elem); + i64 variant_size = lb_sizeof(llvm_variant_type); + if (elem_size > variant_size) { + u64 val = LLVMConstIntGetZExtValue(variant_value); + + LLVMValueRef *elems = temporary_alloc_array(count); + elems[0] = LLVMConstInt(elem, val, false); + for (unsigned j = 1; j < count; j++) { + elems[j] = LLVMConstNull(elem); + } + block_value = LLVMConstArray(elem, elems, count); + goto assign_value_wrapped; + } + } + } else if (!is_type_different_to_arch_endianness(variant_type)) { + switch (variant_kind) { + case LLVMHalfTypeKind: + { + LLVMBool loses = false; + f64 res = LLVMConstRealGetDouble(variant_value, &loses); + u16 val = f32_to_f16(cast(f32)res); + + LLVMValueRef *elems = temporary_alloc_array(count); + elems[0] = LLVMConstInt(elem, val, false); + for (unsigned j = 1; j < count; j++) { + elems[j] = LLVMConstNull(elem); + } + block_value = LLVMConstArray(elem, elems, count); + goto assign_value_wrapped; + } + break; + case LLVMFloatTypeKind: + { + LLVMBool loses = false; + f64 res = LLVMConstRealGetDouble(variant_value, &loses); + union { f32 f; u32 i; } val = {}; + val.f = cast(f32)res; + + LLVMValueRef *elems = temporary_alloc_array(count); + elems[0] = LLVMConstInt(elem, val.i, false); + for (unsigned j = 1; j < count; j++) { + elems[j] = LLVMConstNull(elem); + } + block_value = LLVMConstArray(elem, elems, count); + goto assign_value_wrapped; + } + break; + case LLVMDoubleTypeKind: + { + LLVMBool loses = false; + f64 res = LLVMConstRealGetDouble(variant_value, &loses); + union { f64 f; u64 i; } val = {}; + val.f = res; + + LLVMValueRef *elems = temporary_alloc_array(count); + elems[0] = LLVMConstInt(elem, val.i, false); + for (unsigned j = 1; j < count; j++) { + elems[j] = LLVMConstNull(elem); + } + block_value = LLVMConstArray(elem, elems, count); + goto assign_value_wrapped; + } + break; } } - } else if (LLVMGetTypeKind(block_type) == LLVMIntegerTypeKind && - LLVMGetTypeKind(llvm_variant_type) == LLVMIntegerTypeKind) { - if (!is_type_different_to_arch_endianness(variant_type)) { + + + } else if (block_kind == LLVMIntegerTypeKind && !is_type_different_to_arch_endianness(variant_type)) { + if (variant_kind == LLVMIntegerTypeKind) { i64 variant_size = lb_sizeof(llvm_variant_type); if (block_size > variant_size) { u64 val = LLVMConstIntGetZExtValue(variant_value); @@ -645,13 +778,48 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari goto assign_value_wrapped; } + } else { + switch (variant_kind) { + case LLVMHalfTypeKind: + { + LLVMBool loses = false; + f64 res = LLVMConstRealGetDouble(variant_value, &loses); + u16 val = f32_to_f16(cast(f32)res); + + block_value = LLVMConstInt(block_type, val, false); + goto assign_value_wrapped; + } + break; + case LLVMFloatTypeKind: + { + LLVMBool loses = false; + f64 res = LLVMConstRealGetDouble(variant_value, &loses); + union { f32 f; u32 i; } val = {}; + val.f = cast(f32)res; + + block_value = LLVMConstInt(block_type, val.i, false); + goto assign_value_wrapped; + } + break; + case LLVMDoubleTypeKind: + { + LLVMBool loses = false; + f64 res = LLVMConstRealGetDouble(variant_value, &loses); + union { f64 f; u64 i; } val = {}; + val.f = res; + + block_value = LLVMConstInt(block_type, val.i, false); + goto assign_value_wrapped; + } + break; + } } } return nullptr; } - if (LLVMGetTypeKind(block_type) == LLVMIntegerTypeKind) { - switch (LLVMGetTypeKind(llvm_variant_type)) { + if (block_kind == LLVMIntegerTypeKind) { + switch (variant_kind) { case LLVMHalfTypeKind: case LLVMFloatTypeKind: case LLVMDoubleTypeKind: From 5d3092bf2d00a46311a5f785658f04c823a9f3fa Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 24 Sep 2025 14:27:44 +0100 Subject: [PATCH 101/162] Again, better const union stuff --- src/llvm_abi.cpp | 34 +++---- src/llvm_backend_const.cpp | 192 +++++++++++-------------------------- 2 files changed, 72 insertions(+), 154 deletions(-) diff --git a/src/llvm_abi.cpp b/src/llvm_abi.cpp index 8dbb8fa76..9f2faaa08 100644 --- a/src/llvm_abi.cpp +++ b/src/llvm_abi.cpp @@ -522,6 +522,23 @@ namespace lbAbiAmd64Win64 { } }; + +gb_internal bool is_llvm_type_slice_like(LLVMTypeRef type) { + if (!lb_is_type_kind(type, LLVMStructTypeKind)) { + return false; + } + if (LLVMCountStructElementTypes(type) != 2) { + return false; + } + LLVMTypeRef fields[2] = {}; + LLVMGetStructElementTypes(type, fields); + if (!lb_is_type_kind(fields[0], LLVMPointerTypeKind)) { + return false; + } + return lb_is_type_kind(fields[1], LLVMIntegerTypeKind) && lb_sizeof(fields[1]) == 8; + +} + // NOTE(bill): I hate `namespace` in C++ but this is just because I don't want to prefix everything namespace lbAbiAmd64SysV { enum RegClass { @@ -652,23 +669,6 @@ namespace lbAbiAmd64SysV { return false; } - gb_internal bool is_llvm_type_slice_like(LLVMTypeRef type) { - if (!lb_is_type_kind(type, LLVMStructTypeKind)) { - return false; - } - if (LLVMCountStructElementTypes(type) != 2) { - return false; - } - LLVMTypeRef fields[2] = {}; - LLVMGetStructElementTypes(type, fields); - if (!lb_is_type_kind(fields[0], LLVMPointerTypeKind)) { - return false; - } - return lb_is_type_kind(fields[1], LLVMIntegerTypeKind) && lb_sizeof(fields[1]) == 8; - - } - - gb_internal bool is_aggregate(LLVMTypeRef type) { LLVMTypeKind kind = LLVMGetTypeKind(type); switch (kind) { diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 39a223027..edcc241dc 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -540,22 +540,38 @@ gb_internal bool lb_is_nested_possibly_constant(Type *ft, Selection const &sel, gb_internal Slice lb_construct_const_union_flatten_values(lbModule *m, LLVMValueRef variant_value, Type *variant_type, LLVMTypeRef elem) { LLVMTypeRef llvm_variant_type = lb_type(m, variant_type); LLVMTypeKind variant_kind = LLVMGetTypeKind(llvm_variant_type); - - if (are_types_identical(base_type(variant_type), t_string)) { - LLVMValueRef *elems = temporary_alloc_array(2); - elems[0] = llvm_const_extract_value(m, variant_value, 0); - elems[0] = LLVMConstPtrToInt(elems[0], elem); - - elems[1] = llvm_const_extract_value(m, variant_value, 1); - - return {elems, 2}; - } + LLVMTypeKind elem_kind = LLVMGetTypeKind(elem); if (is_type_struct(variant_type)) { - // Type *st = base_type(variant_type); - // if (st->Struct.fields.count == 1) { - // return lb_construct_const_union_flatten_values(m, llvm_const_extract_value(m, variant_value, 0), st->Struct.fields[0]->type, elem); - // } + Type *st = base_type(variant_type); + GB_ASSERT(st->kind == Type_Struct); + if (st->Struct.fields.count == 1) { + LLVMValueRef f = llvm_const_extract_value(m, variant_value, 0); + return lb_construct_const_union_flatten_values(m, f, st->Struct.fields[0]->type, elem); + } + } else if (is_llvm_type_slice_like(llvm_variant_type)) { + if (lb_sizeof(elem) == build_context.ptr_size) { + LLVMValueRef *elems = temporary_alloc_array(2); + elems[0] = llvm_const_extract_value(m, variant_value, 0); + elems[0] = LLVMConstPtrToInt(elems[0], elem); + + elems[1] = llvm_const_extract_value(m, variant_value, 1); + + return {elems, 2}; + } + } else if (is_type_array_like(variant_type)) { + Type *array_elem = base_array_type(variant_type); + isize array_count = get_array_type_count(variant_type); + Slice array = temporary_slice_make(array_count); + for (isize i = 0; i < array_count; i++) { + LLVMValueRef v = llvm_const_extract_value(m, variant_value, 0); + auto res = lb_construct_const_union_flatten_values(m, v, array_elem, elem); + if (res.count != 1) { + return {}; + } + array[i] = res[0]; + } + return array; } else if (variant_kind == LLVMIntegerTypeKind) { if (elem == llvm_variant_type) { LLVMValueRef *elems = temporary_alloc_array(1); @@ -572,7 +588,8 @@ gb_internal Slice lb_construct_const_union_flatten_values(lbModule return {elems, 1}; } } - } else if (!is_type_different_to_arch_endianness(variant_type)) { + } else if (!is_type_different_to_arch_endianness(variant_type) && + elem_kind == LLVMIntegerTypeKind) { switch (variant_kind) { case LLVMHalfTypeKind: { @@ -687,138 +704,39 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari LLVMTypeKind block_kind = LLVMGetTypeKind(block_type); LLVMTypeKind variant_kind = LLVMGetTypeKind(llvm_variant_type); - if (block_size != variant_size) { - if (block_kind == LLVMArrayTypeKind) { - LLVMTypeRef elem = LLVMGetElementType(block_type); - unsigned count = LLVMGetArrayLength(block_type); - if (variant_kind == LLVMIntegerTypeKind) { - if (elem == llvm_variant_type) { - LLVMValueRef *elems = temporary_alloc_array(count); - elems[0] = variant_value; - for (unsigned j = 1; j < count; j++) { - elems[j] = LLVMConstNull(elem); - } - block_value = LLVMConstArray(elem, elems, count); - goto assign_value_wrapped; - } else if (!is_type_different_to_arch_endianness(variant_type)) { - i64 elem_size = lb_sizeof(elem); - i64 variant_size = lb_sizeof(llvm_variant_type); - if (elem_size > variant_size) { - u64 val = LLVMConstIntGetZExtValue(variant_value); + if (block_kind == LLVMArrayTypeKind) { + LLVMTypeRef elem = LLVMGetElementType(block_type); + unsigned count = LLVMGetArrayLength(block_type); - LLVMValueRef *elems = temporary_alloc_array(count); - elems[0] = LLVMConstInt(elem, val, false); - for (unsigned j = 1; j < count; j++) { - elems[j] = LLVMConstNull(elem); - } - block_value = LLVMConstArray(elem, elems, count); - goto assign_value_wrapped; - } - } - } else if (!is_type_different_to_arch_endianness(variant_type)) { - switch (variant_kind) { - case LLVMHalfTypeKind: - { - LLVMBool loses = false; - f64 res = LLVMConstRealGetDouble(variant_value, &loses); - u16 val = f32_to_f16(cast(f32)res); + Slice partial_elems = lb_construct_const_union_flatten_values(m, variant_value, variant_type, elem); + if (partial_elems.count == count) { + block_value = LLVMConstArray(elem, partial_elems.data, count); + goto assign_value_wrapped; + } - LLVMValueRef *elems = temporary_alloc_array(count); - elems[0] = LLVMConstInt(elem, val, false); - for (unsigned j = 1; j < count; j++) { - elems[j] = LLVMConstNull(elem); - } - block_value = LLVMConstArray(elem, elems, count); - goto assign_value_wrapped; - } - break; - case LLVMFloatTypeKind: - { - LLVMBool loses = false; - f64 res = LLVMConstRealGetDouble(variant_value, &loses); - union { f32 f; u32 i; } val = {}; - val.f = cast(f32)res; + Slice full_elems = temporary_slice_make(count); + slice_copy(&full_elems, partial_elems); + for (isize j = partial_elems.count; j < count; j++) { + full_elems[j] = LLVMConstNull(elem); + } + block_value = LLVMConstArray(elem, full_elems.data, count); + goto assign_value_wrapped; - LLVMValueRef *elems = temporary_alloc_array(count); - elems[0] = LLVMConstInt(elem, val.i, false); - for (unsigned j = 1; j < count; j++) { - elems[j] = LLVMConstNull(elem); - } - block_value = LLVMConstArray(elem, elems, count); - goto assign_value_wrapped; - } - break; - case LLVMDoubleTypeKind: - { - LLVMBool loses = false; - f64 res = LLVMConstRealGetDouble(variant_value, &loses); - union { f64 f; u64 i; } val = {}; - val.f = res; - - LLVMValueRef *elems = temporary_alloc_array(count); - elems[0] = LLVMConstInt(elem, val.i, false); - for (unsigned j = 1; j < count; j++) { - elems[j] = LLVMConstNull(elem); - } - block_value = LLVMConstArray(elem, elems, count); - goto assign_value_wrapped; - } - break; - } - } - - - } else if (block_kind == LLVMIntegerTypeKind && !is_type_different_to_arch_endianness(variant_type)) { - if (variant_kind == LLVMIntegerTypeKind) { - i64 variant_size = lb_sizeof(llvm_variant_type); - if (block_size > variant_size) { - u64 val = LLVMConstIntGetZExtValue(variant_value); - block_value = LLVMConstInt(block_type, val, false); - - goto assign_value_wrapped; - } - } else { - switch (variant_kind) { - case LLVMHalfTypeKind: - { - LLVMBool loses = false; - f64 res = LLVMConstRealGetDouble(variant_value, &loses); - u16 val = f32_to_f16(cast(f32)res); - - block_value = LLVMConstInt(block_type, val, false); - goto assign_value_wrapped; - } - break; - case LLVMFloatTypeKind: - { - LLVMBool loses = false; - f64 res = LLVMConstRealGetDouble(variant_value, &loses); - union { f32 f; u32 i; } val = {}; - val.f = cast(f32)res; - - block_value = LLVMConstInt(block_type, val.i, false); - goto assign_value_wrapped; - } - break; - case LLVMDoubleTypeKind: - { - LLVMBool loses = false; - f64 res = LLVMConstRealGetDouble(variant_value, &loses); - union { f64 f; u64 i; } val = {}; - val.f = res; - - block_value = LLVMConstInt(block_type, val.i, false); - goto assign_value_wrapped; - } - break; - } + } else if (block_size != variant_size) { + if (block_kind == LLVMIntegerTypeKind && !is_type_different_to_arch_endianness(variant_type)) { + Slice partial_elems = lb_construct_const_union_flatten_values(m, variant_value, variant_type, block_type); + if (partial_elems.count == 1) { + block_value = partial_elems[0]; + goto assign_value_wrapped; } } return nullptr; } if (block_kind == LLVMIntegerTypeKind) { + GB_ASSERT(block_size == variant_size); + switch (variant_kind) { case LLVMHalfTypeKind: case LLVMFloatTypeKind: From 3c1238991ba6f68f4ea31ca8ed368387821fa7d7 Mon Sep 17 00:00:00 2001 From: Tohei Ichikawa Date: Wed, 24 Sep 2025 18:52:48 -0400 Subject: [PATCH 102/162] Fix test_proc_group_type_inference.odin --- .../test_proc_group_type_inference.odin | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/internal/test_proc_group_type_inference.odin b/tests/internal/test_proc_group_type_inference.odin index 08ff2a3c5..e26634992 100644 --- a/tests/internal/test_proc_group_type_inference.odin +++ b/tests/internal/test_proc_group_type_inference.odin @@ -29,16 +29,16 @@ test_type_inference_on_literals_with_default_args :: proc(t: ^testing.T) { proc_nil :: proc() { } proc_default_arg :: proc(a: Bit_Set={.A}) -> Bit_Set { return a } group :: proc{proc_nil, proc_default_arg} - + testing.expect_value(t, group(Bit_Set{.A}), Bit_Set{.A}) testing.expect_value(t, group({.A}), Bit_Set{.A}) } - { + { Bit_Set :: bit_set[enum{A, B, C}] proc_1 :: proc(a: Bit_Set={.A}) -> int { return 1 } proc_2 :: proc(a: Bit_Set={.B}, b: Bit_Set={.C}) -> int { return 2 } group :: proc{proc_1, proc_2} - + testing.expect_value(t, group(), 2) testing.expect_value(t, group(Bit_Set{.A}), 2) testing.expect_value(t, group({.A}), 2) @@ -56,7 +56,7 @@ test_type_inference_on_literals_for_various_types :: proc(t: ^testing.T) { testing.expect_value(t, group_array({1.1, 2.2, 3.3}), [3]f32{1.1, 2.2, 3.3}) testing.expect_value(t, group_array({0=1.1, 1=2.2, 2=3.3}), [3]f32{1.1, 2.2, 3.3}) testing.expect_value(t, group_array({}), [3]f32{}) - + proc_slice_u8 :: proc(a: []u8) -> []u8 { return a } group_slice_u8 :: proc{proc_nil, proc_slice_u8} testing.expect_value(t, len(group_slice_u8([]u8{1, 2, 3})), 3) @@ -72,7 +72,7 @@ test_type_inference_on_literals_for_various_types :: proc(t: ^testing.T) { testing.expect_value(t, len(group_dynamic_array({0=1, 1=2, 2=3})), 3) testing.expect_value(t, len(group_dynamic_array({})), 0) testing.expect_value(t, group_dynamic_array(nil) == nil, true) - + Enum :: enum{A, B, C} proc_enum :: proc(a: Enum) -> Enum { return a } group_enum :: proc{proc_nil, proc_enum} @@ -90,14 +90,14 @@ test_type_inference_on_literals_for_various_types :: proc(t: ^testing.T) { testing.expect_value(t, group_bit_set(Bit_Set{.A}), Bit_Set{.A}) testing.expect_value(t, group_bit_set({.A}), Bit_Set{.A}) testing.expect_value(t, group_bit_set({}), Bit_Set{}) - + Struct :: struct{a: int, b: int, c: int} proc_struct :: proc(a: Struct) -> Struct { return a } group_struct :: proc{proc_nil, proc_struct} testing.expect_value(t, group_struct(Struct{a = 9}), Struct{a = 9}) testing.expect_value(t, group_struct({a = 9}), Struct{a = 9}) testing.expect_value(t, group_struct({}), Struct{}) - + Raw_Union :: struct #raw_union{int_: int, f32_: f32} proc_raw_union :: proc(a: Raw_Union) -> Raw_Union { return a } group_raw_union :: proc{proc_nil, proc_raw_union} @@ -109,8 +109,8 @@ test_type_inference_on_literals_for_various_types :: proc(t: ^testing.T) { proc_union :: proc(a: Union) -> Union { return a } group_union :: proc{proc_nil, proc_union} testing.expect_value(t, group_union(int(9)).(int), 9) - testing.expect_value(t, group_union({}).(int), 0) - + testing.expect_value(t, group_union({}), nil) + proc_map :: proc(a: map[u8]u8) -> map[u8]u8 { return a } group_map :: proc{proc_nil, proc_map} testing.expect_value(t, len(group_map(map[u8]u8{1=1, 2=2})), 2) @@ -129,8 +129,8 @@ test_type_inference_on_literals_for_various_types :: proc(t: ^testing.T) { proc_soa_array :: proc(a: SOA_Array) -> SOA_Array { return a } group_soa_array :: proc{proc_nil, proc_soa_array} testing.expect_value(t, len(group_soa_array(SOA_Array{{}, {}})), 2) - testing.expect_value(t, len(group_soa_array({struct{int, int}{1, 2}, struct{int, int}{1, 2}})), 1) - testing.expect_value(t, len(group_soa_array({})), 0) + testing.expect_value(t, len(group_soa_array({struct{int, int}{1, 2}, struct{int, int}{1, 2}})), 2) + testing.expect_value(t, len(group_soa_array({})), 2) testing.expect_value(t, len(soa_zip(a=[]int{1, 2}, b=[]int{3, 4})), 2) proc_matrix :: proc(a: matrix[2,2]f32) -> matrix[2,2]f32 { return a } From 654c5b2c0662934c5965cef483a23e58a3c8ea3f Mon Sep 17 00:00:00 2001 From: Tohei Ichikawa Date: Wed, 24 Sep 2025 21:25:25 -0400 Subject: [PATCH 103/162] Fix memory leaks in type inference test --- .../test_proc_group_type_inference.odin | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/tests/internal/test_proc_group_type_inference.odin b/tests/internal/test_proc_group_type_inference.odin index e26634992..32498c836 100644 --- a/tests/internal/test_proc_group_type_inference.odin +++ b/tests/internal/test_proc_group_type_inference.odin @@ -65,13 +65,20 @@ test_type_inference_on_literals_for_various_types :: proc(t: ^testing.T) { testing.expect_value(t, len(group_slice_u8({})), 0) testing.expect_value(t, group_slice_u8(nil) == nil, true) - proc_dynamic_array :: proc(a: [dynamic]u8) -> [dynamic]u8 { return a } + proc_dynamic_array :: proc(t: ^testing.T, array: [dynamic]u8, expected_len: int) { + if expected_len < 0 { + testing.expect_value(t, array == nil, true) + } else { + testing.expect_value(t, len(array), expected_len) + } + delete(array) + } group_dynamic_array :: proc{proc_nil, proc_dynamic_array} - testing.expect_value(t, len(group_dynamic_array([dynamic]u8{1, 2, 3})), 3) - testing.expect_value(t, len(group_dynamic_array({1, 2, 3})), 3) - testing.expect_value(t, len(group_dynamic_array({0=1, 1=2, 2=3})), 3) - testing.expect_value(t, len(group_dynamic_array({})), 0) - testing.expect_value(t, group_dynamic_array(nil) == nil, true) + group_dynamic_array(t, [dynamic]u8{1, 2, 3}, 3) + group_dynamic_array(t, {1, 2, 3}, 3) + group_dynamic_array(t, {0=1, 1=2, 2=3}, 3) + group_dynamic_array(t, {}, 0) + group_dynamic_array(t, nil, -1) Enum :: enum{A, B, C} proc_enum :: proc(a: Enum) -> Enum { return a } @@ -111,12 +118,19 @@ test_type_inference_on_literals_for_various_types :: proc(t: ^testing.T) { testing.expect_value(t, group_union(int(9)).(int), 9) testing.expect_value(t, group_union({}), nil) - proc_map :: proc(a: map[u8]u8) -> map[u8]u8 { return a } + proc_map :: proc(t: ^testing.T, map_: map[u8]u8, expected_len: int) { + if expected_len < 0 { + testing.expect_value(t, map_ == nil, true) + } else { + testing.expect_value(t, len(map_), expected_len) + } + delete(map_) + } group_map :: proc{proc_nil, proc_map} - testing.expect_value(t, len(group_map(map[u8]u8{1=1, 2=2})), 2) - testing.expect_value(t, len(group_map({1=1, 2=2})), 2) - testing.expect_value(t, len(group_map({})), 0) - testing.expect_value(t, group_map(nil) == nil, true) + group_map(t, map[u8]u8{1=1, 2=2}, 2) + group_map(t, {1=1, 2=2}, 2) + group_map(t, {}, 0) + group_map(t, nil, -1) Bit_Field :: bit_field u16 {a: u8|4, b: u8|4, c: u8|4} proc_bit_field :: proc(a: Bit_Field) -> Bit_Field { return a } From 286e3eafe65c198d53d6c6e589baf54d42501254 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 25 Sep 2025 08:55:03 +0100 Subject: [PATCH 104/162] Change inlining semantics for some builtin calls --- base/runtime/core_builtin.odin | 8 ++++---- base/runtime/internal.odin | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index c0a0204f9..0f72e1336 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -543,7 +543,7 @@ delete_key :: proc(m: ^$T/map[$K]$V, key: K) -> (deleted_key: K, deleted_value: return } -_append_elem :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, arg_ptr: rawptr, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +_append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, arg_ptr: rawptr, should_zero: bool, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { return } @@ -588,7 +588,7 @@ non_zero_append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc : } } -_append_elems :: #force_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, should_zero: bool, loc := #caller_location, args: rawptr, arg_len: int) -> (n: int, err: Allocator_Error) #optional_allocator_error { +_append_elems :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, should_zero: bool, loc := #caller_location, args: rawptr, arg_len: int) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { return 0, nil } @@ -837,7 +837,7 @@ clear_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) { // `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). // // Note: Prefer the procedure group `reserve`. -_reserve_dynamic_array :: #force_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { +_reserve_dynamic_array :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { if a == nil { return nil } @@ -881,7 +881,7 @@ non_zero_reserve_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int capacity } -_resize_dynamic_array :: #force_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { +_resize_dynamic_array :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, length: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { if a == nil { return nil } diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index 77fe09ca8..8af083d07 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -123,7 +123,7 @@ mem_copy_non_overlapping :: proc "contextless" (dst, src: rawptr, len: int) -> r DEFAULT_ALIGNMENT :: 2*align_of(rawptr) -mem_alloc_bytes :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +mem_alloc_bytes :: #force_no_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) if size == 0 || allocator.procedure == nil{ return nil, nil @@ -131,7 +131,7 @@ mem_alloc_bytes :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNM return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc) } -mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +mem_alloc :: #force_no_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) if size == 0 || allocator.procedure == nil { return nil, nil @@ -139,7 +139,7 @@ mem_alloc :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, a return allocator.procedure(allocator.data, .Alloc, size, alignment, nil, 0, loc) } -mem_alloc_non_zeroed :: #force_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { +mem_alloc_non_zeroed :: #force_no_inline proc(size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, loc := #caller_location) -> ([]byte, Allocator_Error) { assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) if size == 0 || allocator.procedure == nil { return nil, nil @@ -147,7 +147,7 @@ mem_alloc_non_zeroed :: #force_inline proc(size: int, alignment: int = DEFAULT_A return allocator.procedure(allocator.data, .Alloc_Non_Zeroed, size, alignment, nil, 0, loc) } -mem_free :: #force_inline proc(ptr: rawptr, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { +mem_free :: #force_no_inline proc(ptr: rawptr, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { if ptr == nil || allocator.procedure == nil { return nil } @@ -155,7 +155,7 @@ mem_free :: #force_inline proc(ptr: rawptr, allocator := context.allocator, loc return err } -mem_free_with_size :: #force_inline proc(ptr: rawptr, byte_count: int, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { +mem_free_with_size :: #force_no_inline proc(ptr: rawptr, byte_count: int, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { if ptr == nil || allocator.procedure == nil { return nil } @@ -163,7 +163,7 @@ mem_free_with_size :: #force_inline proc(ptr: rawptr, byte_count: int, allocator return err } -mem_free_bytes :: #force_inline proc(bytes: []byte, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { +mem_free_bytes :: #force_no_inline proc(bytes: []byte, allocator := context.allocator, loc := #caller_location) -> Allocator_Error { if bytes == nil || allocator.procedure == nil { return nil } @@ -172,14 +172,14 @@ mem_free_bytes :: #force_inline proc(bytes: []byte, allocator := context.allocat } -mem_free_all :: #force_inline proc(allocator := context.allocator, loc := #caller_location) -> (err: Allocator_Error) { +mem_free_all :: #force_no_inline proc(allocator := context.allocator, loc := #caller_location) -> (err: Allocator_Error) { if allocator.procedure != nil { _, err = allocator.procedure(allocator.data, .Free_All, 0, 0, nil, 0, loc) } return } -_mem_resize :: #force_inline proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, should_zero: bool, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { +_mem_resize :: #force_no_inline proc(ptr: rawptr, old_size, new_size: int, alignment: int = DEFAULT_ALIGNMENT, allocator := context.allocator, should_zero: bool, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { assert(is_power_of_two_int(alignment), "Alignment must be a power of two", loc) if allocator.procedure == nil { return nil, nil @@ -667,7 +667,7 @@ quaternion256_eq :: #force_inline proc "contextless" (a, b: quaternion256) -> bo quaternion256_ne :: #force_inline proc "contextless" (a, b: quaternion256) -> bool { return real(a) != real(b) || imag(a) != imag(b) || jmag(a) != jmag(b) || kmag(a) != kmag(b) } -string_decode_rune :: #force_inline proc "contextless" (s: string) -> (rune, int) { +string_decode_rune :: proc "contextless" (s: string) -> (rune, int) { // NOTE(bill): Duplicated here to remove dependency on package unicode/utf8 @(static, rodata) accept_sizes := [256]u8{ @@ -782,7 +782,7 @@ string_decode_last_rune :: proc "contextless" (s: string) -> (rune, int) { } -string16_decode_rune :: #force_inline proc "contextless" (s: string16) -> (rune, int) { +string16_decode_rune :: proc "contextless" (s: string16) -> (rune, int) { REPLACEMENT_CHAR :: '\ufffd' _surr1 :: 0xd800 _surr2 :: 0xdc00 From 1a191b99ac898b3086d367b301ea3e8e527be100 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 25 Sep 2025 12:05:27 +0100 Subject: [PATCH 105/162] Disable some of `lb_construct_const_union` for the time being. --- src/llvm_backend_const.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index edcc241dc..39e2024b0 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -701,6 +701,11 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari } else if (lb_sizeof(llvm_variant_type) == 0) { block_value = LLVMConstNull(block_type); } else if (block_type != llvm_variant_type) { + if (true) { + // TODO(bill): ignore this for the time being + return nullptr; + } + LLVMTypeKind block_kind = LLVMGetTypeKind(block_type); LLVMTypeKind variant_kind = LLVMGetTypeKind(llvm_variant_type); From 8b7a35fae2e35c094c178199e31e83879170a2f8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 25 Sep 2025 12:07:08 +0100 Subject: [PATCH 106/162] `MAKE_VERSION` be `"contextless"` --- vendor/vulkan/_gen/create_vulkan_odin_wrapper.py | 2 +- vendor/vulkan/core.odin | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py b/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py index 9da3ee239..2d78179e7 100644 --- a/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py +++ b/vendor/vulkan/_gen/create_vulkan_odin_wrapper.py @@ -905,7 +905,7 @@ API_VERSION_1_2 :: (1<<22) | (2<<12) | (0) API_VERSION_1_3 :: (1<<22) | (3<<12) | (0) API_VERSION_1_4 :: (1<<22) | (4<<12) | (0) -MAKE_VERSION :: proc(major, minor, patch: u32) -> u32 { +MAKE_VERSION :: proc "contextless" (major, minor, patch: u32) -> u32 { \treturn (major<<22) | (minor<<12) | (patch) } diff --git a/vendor/vulkan/core.odin b/vendor/vulkan/core.odin index b258bc85a..a80bdc73b 100644 --- a/vendor/vulkan/core.odin +++ b/vendor/vulkan/core.odin @@ -9,7 +9,7 @@ API_VERSION_1_2 :: (1<<22) | (2<<12) | (0) API_VERSION_1_3 :: (1<<22) | (3<<12) | (0) API_VERSION_1_4 :: (1<<22) | (4<<12) | (0) -MAKE_VERSION :: proc(major, minor, patch: u32) -> u32 { +MAKE_VERSION :: proc "contextless" (major, minor, patch: u32) -> u32 { return (major<<22) | (minor<<12) | (patch) } From 0ae86dbe879ed58a4ca2220abf69b82f715eec35 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 25 Sep 2025 12:10:18 +0100 Subject: [PATCH 107/162] Ignore further --- src/llvm_backend_const.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 39e2024b0..aa5696ba3 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -754,6 +754,9 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari } } + return nullptr; + } else { + // TODO(bill): ignore this for the time being return nullptr; } From 36098cec0f6d026617b3bf31e5a55177a507cda1 Mon Sep 17 00:00:00 2001 From: Yuriy Grynevych Date: Thu, 25 Sep 2025 15:14:42 +0300 Subject: [PATCH 108/162] [core:sys/info] doc: Remove duplicated line --- core/sys/info/doc.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/core/sys/info/doc.odin b/core/sys/info/doc.odin index aef444f98..2a4f71642 100644 --- a/core/sys/info/doc.odin +++ b/core/sys/info/doc.odin @@ -30,7 +30,6 @@ Example: fmt.printfln("OS: %v", si.os_version.as_string) fmt.printfln("OS: %#v", si.os_version) fmt.printfln("CPU: %v", si.cpu.name) - fmt.printfln("CPU: %v", si.cpu.name) fmt.printfln("CPU cores: %vc/%vt", si.cpu.physical_cores, si.cpu.logical_cores) fmt.printfln("RAM: %#.1M", si.ram.total_ram) From a6d5ec2de85ae446a1b69f6bff16010900db55fe Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 26 Sep 2025 09:31:10 +0100 Subject: [PATCH 109/162] Early short circuit `lb_construct_const_union` --- src/llvm_backend_const.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index aa5696ba3..efd97121a 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -676,6 +676,9 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari } return LLVMConstNamedStruct(llvm_type, values, i); + } else if (true) { + // TODO(bill): ignore this for the time being + return nullptr; } LLVMTypeRef block_type = LLVMStructGetTypeAtIndex(llvm_type, 0); @@ -701,10 +704,6 @@ gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef vari } else if (lb_sizeof(llvm_variant_type) == 0) { block_value = LLVMConstNull(block_type); } else if (block_type != llvm_variant_type) { - if (true) { - // TODO(bill): ignore this for the time being - return nullptr; - } LLVMTypeKind block_kind = LLVMGetTypeKind(block_type); LLVMTypeKind variant_kind = LLVMGetTypeKind(llvm_variant_type); From ed2b79a63e63a8f663d1c1ef67943e921b9ed5cd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 26 Sep 2025 09:35:51 +0100 Subject: [PATCH 110/162] Completely comment out `lb_construct_const_union` --- src/llvm_backend_const.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index efd97121a..69b2f830c 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -633,6 +633,9 @@ gb_internal Slice lb_construct_const_union_flatten_values(lbModule } gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef variant_value, Type *variant_type, Type *union_type) { +#if 1 + return nullptr; +#else 0 Type *bt = base_type(union_type); GB_ASSERT(bt->kind == Type_Union); GB_ASSERT(lb_type(m, variant_type) == LLVMTypeOf(variant_value)); @@ -768,6 +771,7 @@ assign_value_wrapped:; values[i++] = LLVMConstNull(padding_type); } return LLVMConstNamedStruct(llvm_type, values, i); +#endif } gb_internal bool lb_try_construct_const_union(lbModule *m, lbValue *value, Type *variant_type, Type *union_type) { From 42b9039a1fd0307f8b46cd1c750e11db653acea1 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 26 Sep 2025 09:41:08 +0100 Subject: [PATCH 111/162] Check for `nullptr` --- src/llvm_backend_expr.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 4a1f39c19..80cab9722 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -2497,7 +2497,9 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { value = lb_emit_conv(p, value, vt); if (lb_is_const(value)) { LLVMValueRef res = lb_construct_const_union(m, value.value, vt, t); - return {res, t}; + if (res != nullptr) { + return {res, t}; + } } lbAddr parent = lb_add_local_generated(p, t, true); From ade4eafcad6de7462df6d26fccde86a36dea5883 Mon Sep 17 00:00:00 2001 From: samwega Date: Fri, 26 Sep 2025 11:53:53 +0300 Subject: [PATCH 112/162] -fix: typo --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index be0ea8b82..acc4773c0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2837,7 +2837,7 @@ gb_internal int print_show_help(String const arg0, String command, String option print_usage_line(2, "Errs on unneeded tokens, such as unneeded semicolons."); print_usage_line(2, "Errs on missing trailing commas followed by a newline."); print_usage_line(2, "Errs on deprecated syntax."); - print_usage_line(2, "Errs when the attached-brace style in not adhered to (also known as 1TBS)."); + print_usage_line(2, "Errs when the attached-brace style is not adhered to (also known as 1TBS)."); print_usage_line(2, "Errs when 'case' labels are not in the same column as the associated 'switch' token."); } } From 01c10f3f5eefb0473cce3a0cdd381b6db39cf1bb Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 26 Sep 2025 10:18:46 +0100 Subject: [PATCH 113/162] Use `RecursiveMutex` to fix a race condition with parapoly records --- src/check_expr.cpp | 10 +++++++--- src/check_type.cpp | 5 +++-- src/checker.hpp | 2 +- src/entity.cpp | 1 + src/types.cpp | 6 ++++-- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 4462a5907..71d0244d3 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1293,6 +1293,11 @@ gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *typ error_line("\t Got: %s\n", s_got); gb_string_free(s_got); gb_string_free(s_expected); + + Type *tx = x->Proc.params->Tuple.variables[0]->type; + Type *ty = y->Proc.params->Tuple.variables[0]->type; + gb_printf_err("%s kind:%.*s e:%p ot:%p\n", type_to_string(tx), LIT(type_strings[tx->kind]), tx->Named.type_name, tx->Named.type_name->TypeName.original_type_for_parapoly); + gb_printf_err("%s kind:%.*s e:%p ot:%p\n", type_to_string(ty), LIT(type_strings[ty->kind]), ty->Named.type_name, ty->Named.type_name->TypeName.original_type_for_parapoly); } else { gbString s_expected = type_to_string(y); gbString s_got = type_to_string(x); @@ -7908,11 +7913,10 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O { GenTypesData *found_gen_types = ensure_polymorphic_record_entity_has_gen_types(c, original_type); + mutex_lock(&found_gen_types->mutex); + defer (mutex_unlock(&found_gen_types->mutex)); - rw_mutex_shared_lock(&found_gen_types->mutex); Entity *found_entity = find_polymorphic_record_entity(found_gen_types, param_count, ordered_operands); - rw_mutex_shared_unlock(&found_gen_types->mutex); - if (found_entity) { operand->mode = Addressing_Type; operand->type = found_entity->type; diff --git a/src/check_type.cpp b/src/check_type.cpp index 71db34afa..0819de217 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -312,6 +312,7 @@ gb_internal void add_polymorphic_record_entity(CheckerContext *ctx, Ast *node, T e->state = EntityState_Resolved; e->file = ctx->file; e->pkg = pkg; + e->TypeName.original_type_for_parapoly = original_type; add_entity_use(ctx, node, e); } @@ -321,8 +322,8 @@ gb_internal void add_polymorphic_record_entity(CheckerContext *ctx, Ast *node, T e->TypeName.objc_metadata = original_type->Named.type_name->TypeName.objc_metadata; auto *found_gen_types = ensure_polymorphic_record_entity_has_gen_types(ctx, original_type); - rw_mutex_lock(&found_gen_types->mutex); - defer (rw_mutex_unlock(&found_gen_types->mutex)); + mutex_lock(&found_gen_types->mutex); + defer (mutex_unlock(&found_gen_types->mutex)); for (Entity *prev : found_gen_types->types) { if (prev == e) { diff --git a/src/checker.hpp b/src/checker.hpp index d22b99e89..9693c3e38 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -417,7 +417,7 @@ struct GenProcsData { struct GenTypesData { Array types; - RwMutex mutex; + RecursiveMutex mutex; }; struct Defineable { diff --git a/src/entity.cpp b/src/entity.cpp index e07e882f3..d6d8f58de 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -234,6 +234,7 @@ struct Entity { } Variable; struct { Type * type_parameter_specialization; + Type * original_type_for_parapoly; String ir_mangled_name; bool is_type_alias; bool objc_is_implementation; diff --git a/src/types.cpp b/src/types.cpp index 4515b2c60..8604c5363 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -18,8 +18,8 @@ enum BasicKind { Basic_u16, Basic_i32, Basic_u32, - Basic_i64, - Basic_u64, + Basic_i64 +, Basic_u64, Basic_i128, Basic_u128, @@ -2860,6 +2860,7 @@ gb_internal bool are_types_identical(Type *x, Type *y) { return false; } + // MUTEX_GUARD(&g_type_mutex); return are_types_identical_internal(x, y, false); } gb_internal bool are_types_identical_unique_tuples(Type *x, Type *y) { @@ -2887,6 +2888,7 @@ gb_internal bool are_types_identical_unique_tuples(Type *x, Type *y) { return false; } + // MUTEX_GUARD(&g_type_mutex); return are_types_identical_internal(x, y, true); } From dfb86db159d4b3e02a0ebbd28df1f2d55a30f441 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 26 Sep 2025 10:50:16 +0100 Subject: [PATCH 114/162] Fix absolutely random change between `,` and newline --- src/types.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types.cpp b/src/types.cpp index 8604c5363..0c6702103 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -18,8 +18,8 @@ enum BasicKind { Basic_u16, Basic_i32, Basic_u32, - Basic_i64 -, Basic_u64, + Basic_i64, + Basic_u64, Basic_i128, Basic_u128, From af097f6ea1ee48740406594b5407ef4c8c478904 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 26 Sep 2025 12:05:16 +0200 Subject: [PATCH 115/162] Make CodeCov less touchy. --- codecov.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index 959972a69..da629b510 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1 +1,6 @@ -comment: false \ No newline at end of file +comment: false +coverage: + status: + project: + default: + threshold: 1% \ No newline at end of file From ac01d1b5bf0050e8929756e046db425c92b8b1dd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 27 Sep 2025 09:58:28 +0100 Subject: [PATCH 116/162] Add `runtime.conditional_mem_zero` to improve `heap_allocator` performance on non-Windows systems --- base/runtime/heap_allocator.odin | 10 ++++--- base/runtime/internal.odin | 50 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/base/runtime/heap_allocator.odin b/base/runtime/heap_allocator.odin index f2c887759..e2667a78c 100644 --- a/base/runtime/heap_allocator.odin +++ b/base/runtime/heap_allocator.odin @@ -71,10 +71,12 @@ heap_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, new_memory = aligned_alloc(new_size, new_alignment, p, old_size, zero_memory) or_return - // NOTE: heap_resize does not zero the new memory, so we do it - if zero_memory && new_size > old_size { - new_region := raw_data(new_memory[old_size:]) - intrinsics.mem_zero(new_region, new_size - old_size) + when ODIN_OS != .Windows { + // NOTE: heap_resize does not zero the new memory, so we do it + if zero_memory && new_size > old_size { + new_region := raw_data(new_memory[old_size:]) + conditional_mem_zero(new_region, new_size - old_size) + } } return } diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index 8af083d07..50be890f7 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -230,6 +230,56 @@ non_zero_mem_resize :: proc(ptr: rawptr, old_size, new_size: int, alignment: int return _mem_resize(ptr, old_size, new_size, alignment, allocator, false, loc) } +conditional_mem_zero :: proc "contextless" (data: rawptr, n_: int) #no_bounds_check { + // When acquiring memory from the OS for the first time it's likely that the + // OS already gives the zero page mapped multiple times for the request. The + // actual allocation does not have physical pages allocated to it until those + // pages are written to which causes a page-fault. This is often called COW + // (Copy on Write) + // + // You do not want to actually zero out memory in this case because it would + // cause a bunch of page faults decreasing the speed of allocations and + // increase the amount of actual resident physical memory used. + // + // Instead a better technique is to check if memory is zerored before zeroing + // it. This turns out to be an important optimization in practice, saving + // nearly half (or more) the amount of physical memory used by an application. + // This is why every implementation of calloc in libc does this optimization. + // + // It may seem counter-intuitive but most allocations in an application are + // wasted and never used. When you consider something like a [dynamic]T which + // always doubles in capacity on resize but you rarely ever actually use the + // full capacity of a dynamic array it means you have a lot of resident waste + // if you actually zeroed the remainder of the memory. + // + // Keep in mind the OS is already guaranteed to give you zeroed memory by + // mapping in this zero page multiple times so in the best case there is no + // need to actually zero anything. As for testing all this memory for a zero + // value, it costs nothing because the the same zero page is used for the + // whole allocation and will exist in L1 cache for the entire zero checking + // process. + + if n_ <= 0 { + return + } + n := uint(n_) + + n_words := n / size_of(uintptr) + n_bytes := n % size_of(uintptr) + p_words := ([^]uintptr)(data)[:n_words] + p_bytes := ([^]byte)(data)[size_of(uintptr) * n_words:n] + for &p_word in p_words { + if p_word != 0 { + p_word = 0 + } + } + for &p_byte in p_bytes { + if p_byte != 0 { + p_byte = 0 + } + } +} + memory_equal :: proc "contextless" (x, y: rawptr, n: int) -> bool { switch { case n == 0: return true From 2baa19f73ce72045f0eea941c90a5eaac16fc1a6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 27 Sep 2025 10:10:25 +0100 Subject: [PATCH 117/162] Remove unused variable --- base/runtime/internal.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index 50be890f7..0e674aca8 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -265,7 +265,6 @@ conditional_mem_zero :: proc "contextless" (data: rawptr, n_: int) #no_bounds_ch n := uint(n_) n_words := n / size_of(uintptr) - n_bytes := n % size_of(uintptr) p_words := ([^]uintptr)(data)[:n_words] p_bytes := ([^]byte)(data)[size_of(uintptr) * n_words:n] for &p_word in p_words { From a7af6055b00e6f6d47d454ad91591abad9e1ea2d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 27 Sep 2025 10:27:08 +0100 Subject: [PATCH 118/162] Move memory mutex guard around for resize in virtual.Arena allocator --- core/mem/virtual/arena.odin | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/core/mem/virtual/arena.odin b/core/mem/virtual/arena.odin index 4f7bd445d..b515aa3cf 100644 --- a/core/mem/virtual/arena.odin +++ b/core/mem/virtual/arena.odin @@ -108,6 +108,16 @@ arena_alloc :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_l } sync.mutex_guard(&arena.mutex) + return arena_alloc_unguarded(arena, size, alignment, loc) +} + +// Allocates memory from the provided arena. +@(require_results, no_sanitize_address, private) +arena_alloc_unguarded :: proc(arena: ^Arena, size: uint, alignment: uint, loc := #caller_location) -> (data: []byte, err: Allocator_Error) { + size := size + if size == 0 { + return nil, nil + } switch arena.kind { case .Growing: @@ -345,7 +355,11 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, case size == 0: err = .Mode_Not_Implemented return - case uintptr(old_data) & uintptr(alignment-1) == 0: + } + + sync.mutex_guard(&arena.mutex) + + if uintptr(old_data) & uintptr(alignment-1) == 0 { if size < old_size { // shrink data in-place data = old_data[:size] @@ -369,7 +383,7 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, } } - new_memory := arena_alloc(arena, size, alignment, location) or_return + new_memory := arena_alloc_unguarded(arena, size, alignment, location) or_return if new_memory == nil { return } From 4165e8e8888f4894c039c0a55cf67c24fae83af0 Mon Sep 17 00:00:00 2001 From: Jwaxy <98110672+jwaxy@users.noreply.github.com> Date: Sat, 27 Sep 2025 13:53:04 +0300 Subject: [PATCH 119/162] Prevent returning struct containing compound literal slice --- src/check_stmt.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index 62cfc256a..f509267fb 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -2671,6 +2671,20 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { 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"); + } else if (expr->kind == Ast_CompoundLit && is_type_struct(o.type)) { + ast_node(cl, CompoundLit, expr); + for (Ast *elem : cl->elems) { + if (elem->kind == Ast_FieldValue) { + ast_node(fv, FieldValue, elem); + if (fv->value->kind == Ast_CompoundLit && is_type_slice(entity_of_node(fv->field)->type)) { + ast_node(sl, CompoundLit, fv->value); + if (sl->elems.count == 0) { + continue; + } + unsafe_return_error(o, "a compound literal containing a slice"); + } + } + } } } From 15fe0bfe599cd4300d3651b8e74b171cb50599f0 Mon Sep 17 00:00:00 2001 From: Jwaxy <98110672+jwaxy@users.noreply.github.com> Date: Sat, 27 Sep 2025 14:45:46 +0300 Subject: [PATCH 120/162] Make return struct with slice check recursive --- src/check_stmt.cpp | 135 ++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 69 deletions(-) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index f509267fb..f6e201011 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -2541,6 +2541,71 @@ gb_internal void check_if_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { check_close_scope(ctx); } +// NOTE(bill): This is very basic escape analysis +// This needs to be improved tremendously, and a lot of it done during the +// middle-end (or LLVM side) to improve checks and error messages +void check_unsafe_return(Operand const &o, Type *type, Ast *expr) { + auto unsafe_return_error = [](Operand const &o, char const *msg, Type *extra_type=nullptr) { + gbString s = expr_to_string(o.expr); + if (extra_type) { + gbString t = type_to_string(extra_type); + error(o.expr, "It is unsafe to return %s ('%s') of type ('%s') from a procedure, as it uses the current stack frame's memory", msg, s, t); + gb_string_free(t); + } else { + error(o.expr, "It is unsafe to return %s ('%s') from a procedure, as it uses the current stack frame's memory", msg, s); + } + gb_string_free(s); + }; + + if (expr->kind == Ast_CompoundLit && is_type_slice(type)) { + ast_node(cl, CompoundLit, expr); + if (cl->elems.count == 0) { + return; + } + unsafe_return_error(o, "a compound literal of a slice"); + } else if (expr->kind == Ast_UnaryExpr && expr->UnaryExpr.op.kind == Token_And) { + Ast *x = unparen_expr(expr->UnaryExpr.expr); + Entity *e = entity_of_node(x); + if (is_entity_local_variable(e)) { + unsafe_return_error(o, "the address of a local variable"); + } else if (x->kind == Ast_CompoundLit) { + unsafe_return_error(o, "the address of a compound literal"); + } else if (x->kind == Ast_IndexExpr) { + Entity *f = entity_of_node(x->IndexExpr.expr); + if (f && (is_type_array_like(f->type) || is_type_matrix(f->type))) { + if (is_entity_local_variable(f)) { + unsafe_return_error(o, "the address of an indexed variable", f->type); + } + } + } else if (x->kind == Ast_MatrixIndexExpr) { + Entity *f = entity_of_node(x->MatrixIndexExpr.expr); + if (f && (is_type_matrix(f->type) && is_entity_local_variable(f))) { + unsafe_return_error(o, "the address of an indexed variable", f->type); + } + } + } else if (expr->kind == Ast_SliceExpr) { + Ast *x = unparen_expr(expr->SliceExpr.expr); + Entity *e = entity_of_node(x); + if (is_entity_local_variable(e) && is_type_array(e->type)) { + unsafe_return_error(o, "a slice of a local variable"); + } else if (x->kind == Ast_CompoundLit) { + unsafe_return_error(o, "a slice of a compound literal"); + } + } else if (o.mode == Addressing_Constant && is_type_slice(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"); + } else if (expr->kind == Ast_CompoundLit) { + ast_node(cl, CompoundLit, expr); + for (Ast *elem : cl->elems) { + if (elem->kind == Ast_FieldValue) { + ast_node(fv, FieldValue, elem); + check_unsafe_return(o, entity_of_node(fv->field)->type, fv->value); + } + } + } +} + gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { ast_node(rs, ReturnStmt, node); @@ -2617,75 +2682,7 @@ gb_internal void check_return_stmt(CheckerContext *ctx, Ast *node) { expr = unparen_expr(arg); } - auto unsafe_return_error = [](Operand const &o, char const *msg, Type *extra_type=nullptr) { - gbString s = expr_to_string(o.expr); - if (extra_type) { - gbString t = type_to_string(extra_type); - error(o.expr, "It is unsafe to return %s ('%s') of type ('%s') from a procedure, as it uses the current stack frame's memory", msg, s, t); - gb_string_free(t); - } else { - error(o.expr, "It is unsafe to return %s ('%s') from a procedure, as it uses the current stack frame's memory", msg, s); - } - gb_string_free(s); - }; - - - // NOTE(bill): This is very basic escape analysis - // This needs to be improved tremendously, and a lot of it done during the - // middle-end (or LLVM side) to improve checks and error messages - if (expr->kind == Ast_CompoundLit && is_type_slice(o.type)) { - ast_node(cl, CompoundLit, expr); - if (cl->elems.count == 0) { - continue; - } - unsafe_return_error(o, "a compound literal of a slice"); - } else if (expr->kind == Ast_UnaryExpr && expr->UnaryExpr.op.kind == Token_And) { - Ast *x = unparen_expr(expr->UnaryExpr.expr); - Entity *e = entity_of_node(x); - if (is_entity_local_variable(e)) { - unsafe_return_error(o, "the address of a local variable"); - } else if (x->kind == Ast_CompoundLit) { - unsafe_return_error(o, "the address of a compound literal"); - } else if (x->kind == Ast_IndexExpr) { - Entity *f = entity_of_node(x->IndexExpr.expr); - if (f && (is_type_array_like(f->type) || is_type_matrix(f->type))) { - if (is_entity_local_variable(f)) { - unsafe_return_error(o, "the address of an indexed variable", f->type); - } - } - } else if (x->kind == Ast_MatrixIndexExpr) { - Entity *f = entity_of_node(x->MatrixIndexExpr.expr); - if (f && (is_type_matrix(f->type) && is_entity_local_variable(f))) { - unsafe_return_error(o, "the address of an indexed variable", f->type); - } - } - } else if (expr->kind == Ast_SliceExpr) { - Ast *x = unparen_expr(expr->SliceExpr.expr); - Entity *e = entity_of_node(x); - if (is_entity_local_variable(e) && is_type_array(e->type)) { - unsafe_return_error(o, "a slice of a local variable"); - } else if (x->kind == Ast_CompoundLit) { - unsafe_return_error(o, "a slice of a compound literal"); - } - } 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"); - } else if (expr->kind == Ast_CompoundLit && is_type_struct(o.type)) { - ast_node(cl, CompoundLit, expr); - for (Ast *elem : cl->elems) { - if (elem->kind == Ast_FieldValue) { - ast_node(fv, FieldValue, elem); - if (fv->value->kind == Ast_CompoundLit && is_type_slice(entity_of_node(fv->field)->type)) { - ast_node(sl, CompoundLit, fv->value); - if (sl->elems.count == 0) { - continue; - } - unsafe_return_error(o, "a compound literal containing a slice"); - } - } - } - } + check_unsafe_return(o, o.type, expr); } } From af83c30b6f5303dfddf8e38019c0431658b6e176 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 27 Sep 2025 14:13:16 +0100 Subject: [PATCH 121/162] And extra safety checks --- src/check_stmt.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index f6e201011..5e7a8e323 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -2545,7 +2545,7 @@ gb_internal void check_if_stmt(CheckerContext *ctx, Ast *node, u32 mod_flags) { // This needs to be improved tremendously, and a lot of it done during the // middle-end (or LLVM side) to improve checks and error messages void check_unsafe_return(Operand const &o, Type *type, Ast *expr) { - auto unsafe_return_error = [](Operand const &o, char const *msg, Type *extra_type=nullptr) { + auto const unsafe_return_error = [](Operand const &o, char const *msg, Type *extra_type=nullptr) { gbString s = expr_to_string(o.expr); if (extra_type) { gbString t = type_to_string(extra_type); @@ -2557,6 +2557,10 @@ void check_unsafe_return(Operand const &o, Type *type, Ast *expr) { gb_string_free(s); }; + if (type == nullptr || expr == nullptr) { + return; + } + if (expr->kind == Ast_CompoundLit && is_type_slice(type)) { ast_node(cl, CompoundLit, expr); if (cl->elems.count == 0) { @@ -2600,7 +2604,10 @@ void check_unsafe_return(Operand const &o, Type *type, Ast *expr) { for (Ast *elem : cl->elems) { if (elem->kind == Ast_FieldValue) { ast_node(fv, FieldValue, elem); - check_unsafe_return(o, entity_of_node(fv->field)->type, fv->value); + Entity *e = entity_of_node(fv->field); + if (e != nullptr) { + check_unsafe_return(o, e->type, fv->value); + } } } } From e9f6456b523984267461154a340f04dffe375608 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 27 Sep 2025 14:13:28 +0100 Subject: [PATCH 122/162] Remove `_test.odin` filter --- src/build_settings.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 11e269776..a8d74d1da 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -968,14 +968,6 @@ gb_internal bool is_excluded_target_filename(String name) { return true; } - if (build_context.command_kind != Command_test) { - String test_suffix = str_lit("_test"); - if (string_ends_with(name, test_suffix) && name != test_suffix) { - // Ignore *_test.odin files - return true; - } - } - String str1 = {}; String str2 = {}; isize n = 0; From 0233dc5d31e1db7b4da9e6c5a4a614e2cbb550a9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 27 Sep 2025 14:15:51 +0100 Subject: [PATCH 123/162] Remove stray `0` --- src/llvm_backend_const.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 69b2f830c..3aeba0891 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -635,7 +635,7 @@ gb_internal Slice lb_construct_const_union_flatten_values(lbModule gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef variant_value, Type *variant_type, Type *union_type) { #if 1 return nullptr; -#else 0 +#else Type *bt = base_type(union_type); GB_ASSERT(bt->kind == Type_Union); GB_ASSERT(lb_type(m, variant_type) == LLVMTypeOf(variant_value)); From a974c51d573618c9cc4496a32885b7f871c317b2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 19:52:52 +0100 Subject: [PATCH 124/162] First step towards constant unions --- src/check_expr.cpp | 2 +- src/llvm_backend.hpp | 2 +- src/llvm_backend_const.cpp | 87 ++++++++++++++++++++++++++------------ src/llvm_backend_expr.cpp | 22 ++++++++-- 4 files changed, 81 insertions(+), 32 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index fdc3bc181..a825ec7bf 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3520,7 +3520,7 @@ gb_internal bool is_type_union_constantable(Type *type) { return false; } } - return false; + return true; } gb_internal bool check_cast_internal(CheckerContext *c, Operand *x, Type *type) { diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index cee46701f..76ec2fe1f 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -455,7 +455,7 @@ static lbConstContext const LB_CONST_CONTEXT_DEFAULT_NO_LOCAL = {false, false, { gb_internal lbValue lb_const_nil(lbModule *m, Type *type); gb_internal lbValue lb_const_undef(lbModule *m, Type *type); -gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lbConstContext cc = LB_CONST_CONTEXT_DEFAULT); +gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lbConstContext cc = LB_CONST_CONTEXT_DEFAULT, Type *value_type=nullptr); gb_internal lbValue lb_const_bool(lbModule *m, Type *type, bool value); gb_internal lbValue lb_const_int(lbModule *m, Type *type, u64 value); diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 3aeba0891..415d6743b 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -787,7 +787,7 @@ gb_internal bool lb_try_construct_const_union(lbModule *m, lbValue *value, Type } -gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lbConstContext cc) { +gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lbConstContext cc, Type *value_type) { if (cc.allow_local) { cc.is_rodata = false; } @@ -838,7 +838,6 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb if (is_type_union(type) && is_type_union_constantable(type)) { Type *bt = base_type(type); GB_ASSERT(bt->kind == Type_Union); - GB_ASSERT(bt->Union.variants.count <= 1); if (bt->Union.variants.count == 0) { return lb_const_nil(m, original_type); } else if (bt->Union.variants.count == 1) { @@ -872,6 +871,37 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb res.type = original_type; return res; } + } else { + GB_ASSERT(value_type != nullptr); + + i64 block_size = bt->Union.variant_block_size; + + lbValue cv = lb_const_value(m, value_type, value, cc, value_type); + Type *variant_type = cv.type; + + LLVMValueRef values[4] = {}; + unsigned value_count = 0; + + values[value_count++] = cv.value; + if (type_size_of(variant_type) != block_size) { + LLVMTypeRef padding_type = lb_type_padding_filler(m, block_size - type_size_of(variant_type), 1); + values[value_count++] = LLVMConstNull(padding_type); + } + + Type *tag_type = union_tag_type(bt); + LLVMTypeRef llvm_tag_type = lb_type(m, tag_type); + i64 tag_index = union_variant_index(bt, variant_type); + values[value_count++] = LLVMConstInt(llvm_tag_type, tag_index, false); + i64 used_size = block_size + type_size_of(tag_type); + i64 union_size = type_size_of(bt); + i64 padding = union_size - used_size; + if (padding > 0) { + LLVMTypeRef padding_type = lb_type_padding_filler(m, padding, 1); + values[value_count++] = LLVMConstNull(padding_type); + } + + res.value = LLVMConstStructInContext(m->ctx, values, value_count, true); + return res; } } @@ -909,7 +939,11 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb array_data = llvm_alloca(p, llvm_type, 16); - LLVMBuildStore(p->builder, backing_array.value, array_data); + { + LLVMValueRef ptr = array_data; + ptr = LLVMBuildPointerCast(p->builder, ptr, LLVMPointerType(LLVMTypeOf(backing_array.value), 0), ""); + LLVMBuildStore(p->builder, backing_array.value, ptr); + } { LLVMValueRef indices[2] = {llvm_zero(m), llvm_zero(m)}; @@ -931,7 +965,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb String name = make_string(cast(u8 const *)str, gb_string_length(str)); Entity *e = alloc_entity_constant(nullptr, make_token_ident(name), t, value); - array_data = LLVMAddGlobal(m->mod, lb_type(m, t), str); + array_data = LLVMAddGlobal(m->mod, LLVMTypeOf(backing_array.value), str); LLVMSetInitializer(array_data, backing_array.value); if (cc.link_section.len > 0) { @@ -942,15 +976,14 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb } lbValue g = {}; - g.value = array_data; + g.value = LLVMConstPointerCast(array_data, LLVMPointerType(lb_type(m, t), 0)); g.type = t; lb_add_entity(m, e, g); lb_add_member(m, name, g); { - LLVMValueRef indices[2] = {llvm_zero(m), llvm_zero(m)}; - LLVMValueRef ptr = LLVMConstInBoundsGEP2(lb_type(m, t), array_data, indices, 2); + LLVMValueRef ptr = g.value; LLVMValueRef len = LLVMConstInt(lb_type(m, t_int), count, true); LLVMValueRef values[2] = {ptr, len}; @@ -1272,7 +1305,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; for (i64 k = lo; k < hi; k++) { aos_values[value_index++] = val; } @@ -1287,7 +1320,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; aos_values[value_index++] = val; found = true; break; @@ -1340,7 +1373,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb for (isize i = 0; i < elem_count; i++) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - aos_values[i] = lb_const_value(m, elem_type, tav.value, cc).value; + aos_values[i] = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; } for (isize i = elem_count; i < type->Struct.soa_count; i++) { aos_values[i] = nullptr; @@ -1407,7 +1440,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -1422,7 +1455,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; values[value_index++] = val; found = true; break; @@ -1437,12 +1470,12 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, cc); return res; - } else if (value.value_compound->tav.type == elem_type) { + } else if (are_types_identical(value.value_compound->tav.type, elem_type)) { // Compound is of array item type; expand its value to all items in array. LLVMValueRef* values = gb_alloc_array(temporary_allocator(), LLVMValueRef, cast(isize)type->Array.count); for (isize i = 0; i < type->Array.count; i++) { - values[i] = lb_const_value(m, elem_type, value, cc).value; + values[i] = lb_const_value(m, elem_type, value, cc, elem_type).value; } res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, cc); @@ -1456,7 +1489,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb for (isize i = 0; i < elem_count; i++) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - values[i] = lb_const_value(m, elem_type, tav.value, cc).value; + values[i] = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; } for (isize i = elem_count; i < type->Array.count; i++) { values[i] = LLVMConstNull(lb_type(m, elem_type)); @@ -1502,7 +1535,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -1517,7 +1550,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; values[value_index++] = val; found = true; break; @@ -1540,7 +1573,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb for (isize i = 0; i < elem_count; i++) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - values[i] = lb_const_value(m, elem_type, tav.value, cc).value; + values[i] = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; } for (isize i = elem_count; i < type->EnumeratedArray.count; i++) { values[i] = LLVMConstNull(lb_type(m, elem_type)); @@ -1585,7 +1618,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb } if (lo == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; for (i64 k = lo; k < hi; k++) { values[value_index++] = val; } @@ -1600,7 +1633,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb i64 index = exact_value_to_i64(index_tav.value); if (index == i) { TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; values[value_index++] = val; found = true; break; @@ -1619,7 +1652,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb for (isize i = 0; i < elem_count; i++) { TypeAndValue tav = cl->elems[i]->tav; GB_ASSERT(tav.mode != Addressing_Invalid); - values[i] = lb_const_value(m, elem_type, tav.value, cc).value; + values[i] = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; } LLVMTypeRef et = lb_type(m, elem_type); @@ -1668,7 +1701,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb i32 index = field_remapping[f->Variable.field_index]; if (elem_type_can_be_constant(f->type)) { if (sel.index.count == 1) { - values[index] = lb_const_value(m, f->type, tav.value, cc).value; + values[index] = lb_const_value(m, f->type, tav.value, cc, tav.type).value; visited[index] = true; } else { if (!visited[index]) { @@ -1714,7 +1747,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb } } if (is_constant) { - LLVMValueRef elem_value = lb_const_value(m, tav.type, tav.value, cc).value; + LLVMValueRef elem_value = lb_const_value(m, tav.type, tav.value, cc, tav.type).value; if (LLVMIsConstant(elem_value) && LLVMIsConstant(values[index])) { values[index] = llvm_const_insert_value(m, values[index], elem_value, idx_list, idx_list_len); } else if (is_local) { @@ -1768,7 +1801,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb i32 index = field_remapping[f->Variable.field_index]; if (elem_type_can_be_constant(f->type)) { - values[index] = lb_const_value(m, f->type, val, cc).value; + values[index] = lb_const_value(m, f->type, val, cc, tav.type).value; visited[index] = true; } } @@ -1902,7 +1935,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; for (i64 k = lo; k < hi; k++) { i64 offset = matrix_row_major_index_to_offset(type, k); GB_ASSERT(values[offset] == nullptr); @@ -1914,7 +1947,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb i64 index = exact_value_to_i64(index_tav.value); GB_ASSERT(index < max_count); TypeAndValue tav = fv->value->tav; - LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc).value; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; i64 offset = matrix_row_major_index_to_offset(type, index); GB_ASSERT(values[offset] == nullptr); values[offset] = val; @@ -1938,7 +1971,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb GB_ASSERT(tav.mode != Addressing_Invalid); i64 offset = 0; offset = matrix_row_major_index_to_offset(type, i); - values[offset] = lb_const_value(m, elem_type, tav.value, cc).value; + values[offset] = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; } for (isize i = 0; i < total_count; i++) { if (values[i] == nullptr) { diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 80cab9722..0e8562194 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -3947,6 +3947,20 @@ gb_internal lbValue lb_build_expr(lbProcedure *p, Ast *expr) { return res; } +gb_internal Type *lb_build_expr_original_const_type(Ast *expr) { + expr = unparen_expr(expr); + Type *type = type_of_expr(expr); + if (is_type_union(type)) { + if (expr->kind == Ast_CallExpr) { + if (expr->CallExpr.proc->tav.mode == Addressing_Type) { + Type *res = lb_build_expr_original_const_type(expr->CallExpr.args[0]); + return res; + } + } + } + return type_of_expr(expr); +} + gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) { lbModule *m = p->module; @@ -3958,9 +3972,11 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) { GB_ASSERT_MSG(tv.mode != Addressing_Invalid, "invalid expression '%s' (tv.mode = %d, tv.type = %s) @ %s\n Current Proc: %.*s : %s", expr_to_string(expr), tv.mode, type_to_string(tv.type), token_pos_to_string(expr_pos), LIT(p->name), type_to_string(p->type)); + if (tv.value.kind != ExactValue_Invalid) { + Type *original_type = lb_build_expr_original_const_type(expr); // NOTE(bill): Short on constant values - return lb_const_value(p->module, type, tv.value, LB_CONST_CONTEXT_DEFAULT_ALLOW_LOCAL); + return lb_const_value(p->module, type, tv.value, LB_CONST_CONTEXT_DEFAULT_ALLOW_LOCAL, original_type); } else if (tv.mode == Addressing_Type) { // NOTE(bill, 2023-01-16): is this correct? I hope so at least return lb_typeid(m, tv.type); @@ -4041,7 +4057,7 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) { TypeAndValue tav = type_and_value_of_expr(expr); GB_ASSERT(tav.mode == Addressing_Constant); - return lb_const_value(p->module, type, tv.value); + return lb_const_value(p->module, type, tv.value, LB_CONST_CONTEXT_DEFAULT_ALLOW_LOCAL, tv.type); case_end; case_ast_node(se, SelectorCallExpr, expr); @@ -4322,7 +4338,7 @@ gb_internal lbAddr lb_build_addr_from_entity(lbProcedure *p, Entity *e, Ast *exp GB_ASSERT(e != nullptr); if (e->kind == Entity_Constant) { Type *t = default_type(type_of_expr(expr)); - lbValue v = lb_const_value(p->module, t, e->Constant.value); + lbValue v = lb_const_value(p->module, t, e->Constant.value, LB_CONST_CONTEXT_DEFAULT_NO_LOCAL, e->type); if (LLVMIsConstant(v.value)) { lbAddr g = lb_add_global_generated_from_procedure(p, t, v); return g; From ffdfbfe2c2d0e09087f166be79f3dbc2859844e6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 20:20:26 +0100 Subject: [PATCH 125/162] Begin to support constant array of unions --- src/check_expr.cpp | 28 ++++++++-------------------- src/llvm_backend_const.cpp | 18 ++++++++++-------- src/llvm_backend_general.cpp | 11 ++++++++++- src/llvm_backend_proc.cpp | 2 +- src/llvm_backend_type.cpp | 6 +++--- src/parser.hpp | 18 ++++++++++++++++++ src/types.cpp | 22 +++++++++++++++++++++- 7 files changed, 71 insertions(+), 34 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index a825ec7bf..2da8776eb 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -3505,24 +3505,6 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type return false; } -gb_internal bool is_type_union_constantable(Type *type) { - Type *bt = base_type(type); - GB_ASSERT(bt->kind == Type_Union); - - if (bt->Union.variants.count == 0) { - return true; - } else if (bt->Union.variants.count == 1) { - return is_type_constant_type(bt->Union.variants[0]); - } - - for (Type *v : bt->Union.variants) { - if (!is_type_constant_type(v)) { - return false; - } - } - return true; -} - gb_internal bool check_cast_internal(CheckerContext *c, Operand *x, Type *type) { bool is_const_expr = x->mode == Addressing_Constant; @@ -4880,7 +4862,10 @@ gb_internal void convert_to_typed(CheckerContext *c, Operand *operand, Type *tar break; } operand->type = new_type; - operand->mode = Addressing_Value; + if (operand->mode != Addressing_Constant || + !elem_type_can_be_constant(operand->type)) { + operand->mode = Addressing_Value; + } break; } else if (valid_count > 1) { ERROR_BLOCK(); @@ -9895,7 +9880,10 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * Operand o = {}; check_expr_or_type(c, &o, elem, field->type); - if (is_type_any(field->type) || is_type_union(field->type) || is_type_raw_union(field->type) || is_type_typeid(field->type)) { + if (is_type_any(field->type) || + is_type_raw_union(field->type) || + (is_type_union(field->type) && !is_type_union_constantable(field->type)) || + is_type_typeid(field->type)) { is_constant = false; } if (is_constant) { diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 415d6743b..8cdc889e0 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -96,10 +96,6 @@ gb_internal LLVMValueRef llvm_const_cast(LLVMValueRef val, LLVMTypeRef dst) { case LLVMPointerTypeKind: return LLVMConstPointerCast(val, dst); case LLVMStructTypeKind: - // GB_PANIC("%s -> %s", LLVMPrintValueToString(val), LLVMPrintTypeToString(dst)); - // NOTE(bill): It's not possible to do a bit cast on a struct, why was this code even here in the first place? - // It seems mostly to exist to get around the "anonymous -> named" struct assignments - // return LLVMConstBitCast(val, dst); return val; default: GB_PANIC("Unhandled const cast %s to %s", LLVMPrintTypeToString(src), LLVMPrintTypeToString(dst)); @@ -199,11 +195,17 @@ gb_internal LLVMValueRef llvm_const_named_struct_internal(LLVMTypeRef t, LLVMVal return LLVMConstNamedStruct(t, values, value_count); } -gb_internal LLVMValueRef llvm_const_array(LLVMTypeRef elem_type, LLVMValueRef *values, isize value_count_) { +gb_internal LLVMValueRef llvm_const_array(lbModule *m, LLVMTypeRef elem_type, LLVMValueRef *values, isize value_count_) { unsigned value_count = cast(unsigned)value_count_; for (unsigned i = 0; i < value_count; i++) { values[i] = llvm_const_cast(values[i], elem_type); } + for (unsigned i = 0; i < value_count; i++) { + if (elem_type != LLVMTypeOf(values[i])) { + return LLVMConstStructInContext(m->ctx, values, value_count, false); + } + } + return LLVMConstArray(elem_type, values, value_count); } @@ -461,7 +463,7 @@ gb_internal LLVMValueRef lb_build_constant_array_values(lbModule *m, Type *type, return lb_addr_load(p, v).value; } - return llvm_const_array(lb_type(m, elem_type), values, cast(unsigned int)count); + return llvm_const_array(m, lb_type(m, elem_type), values, cast(unsigned int)count); } gb_internal LLVMValueRef lb_big_int_to_llvm(lbModule *m, Type *original_type, BigInt const *a) { @@ -1016,7 +1018,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb } GB_ASSERT(offset == s.len); - res.value = llvm_const_array(et, elems, cast(unsigned)count); + res.value = llvm_const_array(m, et, elems, cast(unsigned)count); return res; } // NOTE(bill, 2021-10-07): Allow for array programming value constants @@ -1046,7 +1048,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb elems[i] = single_elem.value; } - res.value = llvm_const_array(lb_type(m, elem), elems, cast(unsigned)count); + res.value = llvm_const_array(m, lb_type(m, elem), elems, cast(unsigned)count); return res; } else if (is_type_matrix(type) && value.kind != ExactValue_Invalid && diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 6e513a075..8e5efcb52 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -3253,11 +3253,18 @@ gb_internal lbAddr lb_add_global_generated_with_name(lbModule *m, Type *type, lb GB_ASSERT(type != nullptr); type = default_type(type); + LLVMTypeRef actual_type = lb_type(m, type); + if (value.value != nullptr) { + LLVMTypeRef value_type = LLVMTypeOf(value.value); + GB_ASSERT(lb_sizeof(actual_type) == lb_sizeof(value_type)); + actual_type = value_type; + } + Scope *scope = nullptr; Entity *e = alloc_entity_variable(scope, make_token_ident(name), type); lbValue g = {}; g.type = alloc_type_pointer(type); - g.value = LLVMAddGlobal(m->mod, lb_type(m, type), alloc_cstring(temporary_allocator(), name)); + g.value = LLVMAddGlobal(m->mod, actual_type, alloc_cstring(temporary_allocator(), name)); if (value.value != nullptr) { GB_ASSERT_MSG(LLVMIsConstant(value.value), LLVMPrintValueToString(value.value)); LLVMSetInitializer(g.value, value.value); @@ -3265,6 +3272,8 @@ gb_internal lbAddr lb_add_global_generated_with_name(lbModule *m, Type *type, lb LLVMSetInitializer(g.value, LLVMConstNull(lb_type(m, type))); } + g.value = LLVMConstPointerCast(g.value, lb_type(m, g.type)); + lb_add_entity(m, e, g); lb_add_member(m, name, g); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index ee17ef771..19213e1a3 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2237,7 +2237,7 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu elements[i] = element; } - LLVMValueRef backing_array = llvm_const_array(lb_type(m, t_load_directory_file), elements, count); + LLVMValueRef backing_array = llvm_const_array(m, lb_type(m, t_load_directory_file), elements, count); Type *array_type = alloc_type_array(t_load_directory_file, count); lbAddr backing_array_addr = lb_add_global_generated_from_procedure(p, array_type, {backing_array, array_type}); diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index b2eec218f..7d412eb15 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -302,7 +302,7 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ (name##_values)[i] = LLVMConstNull(elem); \ } \ } \ - LLVMSetInitializer(name.addr.value, llvm_const_array(elem, name##_values, at->Array.count)); \ + LLVMSetInitializer(name.addr.value, llvm_const_array(m, elem, name##_values, at->Array.count)); \ }) type_info_allocate_values(lb_global_type_info_member_types); @@ -752,8 +752,8 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ value_values[i] = lb_const_value(m, t_i64, fields[i]->Constant.value).value; } - LLVMValueRef name_init = llvm_const_array(lb_type(m, t_string), name_values, cast(unsigned)fields.count); - LLVMValueRef value_init = llvm_const_array(lb_type(m, t_type_info_enum_value), value_values, cast(unsigned)fields.count); + LLVMValueRef name_init = llvm_const_array(m, lb_type(m, t_string), name_values, cast(unsigned)fields.count); + LLVMValueRef value_init = llvm_const_array(m, lb_type(m, t_type_info_enum_value), value_values, cast(unsigned)fields.count); LLVMSetInitializer(name_array.value, name_init); LLVMSetInitializer(value_array.value, value_init); LLVMSetGlobalConstant(name_array.value, true); diff --git a/src/parser.hpp b/src/parser.hpp index 56447df43..979b44618 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -27,6 +27,24 @@ enum AddressingMode : u8 { Addressing_SwizzleVariable = 14, // Swizzle indexed variable }; +gb_global String const addressing_mode_strings[] = { + str_lit("Invalid"), + str_lit("NoValue"), + str_lit("Value"), + str_lit("Context"), + str_lit("Variable"), + str_lit("Constant"), + str_lit("Type"), + str_lit("Builtin"), + str_lit("ProcGroup"), + str_lit("MapIndex"), + str_lit("OptionalOk"), + str_lit("OptionalOkPtr"), + str_lit("SoaVariable"), + str_lit("SwizzleValue"), + str_lit("SwizzleVariable"), +}; + struct TypeAndValue { Type * type; AddressingMode mode; diff --git a/src/types.cpp b/src/types.cpp index 0c6702103..46e41bb4b 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2507,15 +2507,35 @@ gb_internal bool type_has_nil(Type *t) { return false; } +gb_internal bool is_type_union_constantable(Type *type) { + Type *bt = base_type(type); + GB_ASSERT(bt->kind == Type_Union); + + if (bt->Union.variants.count == 0) { + return true; + } else if (bt->Union.variants.count == 1) { + return is_type_constant_type(bt->Union.variants[0]); + } + + for (Type *v : bt->Union.variants) { + if (!is_type_constant_type(v)) { + return false; + } + } + return true; +} gb_internal bool elem_type_can_be_constant(Type *t) { t = base_type(t); if (t == t_invalid) { return false; } - if (is_type_any(t) || is_type_union(t) || is_type_raw_union(t)) { + if (is_type_any(t) || is_type_raw_union(t)) { return false; } + if (is_type_union(t)) { + return is_type_union_constantable(t); + } return true; } From 17204bd1c23934c878c699e026aa8994e430a372 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 20:40:26 +0100 Subject: [PATCH 126/162] Global const unions with `@(rodata)` --- src/llvm_backend.cpp | 77 +++++++++++++++++++++----------------- src/llvm_backend.hpp | 2 +- src/llvm_backend_const.cpp | 47 +++++++++++++---------- 3 files changed, 71 insertions(+), 55 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 3af473c3a..8ac68d9bf 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3262,37 +3262,14 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { lbModule *m = &gen->default_module; String name = lb_get_entity_name(m, e); - lbValue g = {}; - g.value = LLVMAddGlobal(m->mod, lb_type(m, e->type), alloc_cstring(permanent_allocator(), name)); - g.type = alloc_type_pointer(e->type); - - lb_apply_thread_local_model(g.value, e->Variable.thread_local_model); - - if (is_foreign) { - LLVMSetLinkage(g.value, LLVMExternalLinkage); - LLVMSetDLLStorageClass(g.value, LLVMDLLImportStorageClass); - LLVMSetExternallyInitialized(g.value, true); - lb_add_foreign_library_path(m, e->Variable.foreign_library); - } else { - LLVMSetInitializer(g.value, LLVMConstNull(lb_type(m, e->type))); - } - if (is_export) { - LLVMSetLinkage(g.value, LLVMDLLExportLinkage); - LLVMSetDLLStorageClass(g.value, LLVMDLLExportStorageClass); - } else if (!is_foreign) { - LLVMSetLinkage(g.value, USE_SEPARATE_MODULES ? LLVMWeakAnyLinkage : LLVMInternalLinkage); - } - lb_set_linkage_from_entity_flags(m, g.value, e->flags); - LLVMSetAlignment(g.value, cast(u32)type_align_of(e->type)); - - if (e->Variable.link_section.len > 0) { - LLVMSetSection(g.value, alloc_cstring(permanent_allocator(), e->Variable.link_section)); - } - lbGlobalVariable var = {}; - var.var = g; var.decl = decl; + lbValue g = {}; + g.type = alloc_type_pointer(e->type); + g.value = LLVMAddGlobal(m->mod, lb_type(m, e->type), alloc_cstring(permanent_allocator(), name)); + + if (decl->init_expr != nullptr) { TypeAndValue tav = type_and_value_of_expr(decl->init_expr); if (!is_type_any(e->type) && !is_type_union(e->type)) { @@ -3305,6 +3282,11 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { ExactValue v = tav.value; lbValue init = lb_const_value(m, tav.type, v, cc); + + LLVMDeleteGlobal(g.value); + g.value = nullptr; + g.value = LLVMAddGlobal(m->mod, LLVMTypeOf(init.value), alloc_cstring(permanent_allocator(), name)); + LLVMSetInitializer(g.value, init.value); var.is_initialized = true; if (cc.is_rodata) { @@ -3323,16 +3305,33 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { LLVMSetGlobalConstant(g.value, true); } + + lb_apply_thread_local_model(g.value, e->Variable.thread_local_model); + + if (is_foreign) { + LLVMSetLinkage(g.value, LLVMExternalLinkage); + LLVMSetDLLStorageClass(g.value, LLVMDLLImportStorageClass); + LLVMSetExternallyInitialized(g.value, true); + lb_add_foreign_library_path(m, e->Variable.foreign_library); + } else if (!var.is_initialized) { + LLVMSetInitializer(g.value, LLVMConstNull(lb_type(m, e->type))); + } + if (is_export) { + LLVMSetLinkage(g.value, LLVMDLLExportLinkage); + LLVMSetDLLStorageClass(g.value, LLVMDLLExportStorageClass); + } else if (!is_foreign) { + LLVMSetLinkage(g.value, USE_SEPARATE_MODULES ? LLVMWeakAnyLinkage : LLVMInternalLinkage); + } + lb_set_linkage_from_entity_flags(m, g.value, e->flags); + LLVMSetAlignment(g.value, cast(u32)type_align_of(e->type)); + + if (e->Variable.link_section.len > 0) { + LLVMSetSection(g.value, alloc_cstring(permanent_allocator(), e->Variable.link_section)); + } if (e->flags & EntityFlag_Require) { lb_append_to_compiler_used(m, g.value); } - array_add(&global_variables, var); - - lb_add_entity(m, e, g); - lb_add_member(m, name, g); - - if (m->debug_builder) { String global_name = e->token.string; if (global_name.len != 0 && global_name != "_") { @@ -3361,6 +3360,16 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { LLVMGlobalSetMetadata(g.value, 0, global_variable_metadata); } } + + g.value = LLVMConstPointerCast(g.value, lb_type(m, alloc_type_pointer(e->type))); + + var.var = g; + array_add(&global_variables, var); + + lb_add_entity(m, e, g); + lb_add_member(m, name, g); + + } if (build_context.ODIN_DEBUG) { diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 76ec2fe1f..6870f6259 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -604,7 +604,7 @@ gb_internal lbValue lb_emit_logical_binary_expr(lbProcedure *p, TokenKind op, As gb_internal lbValue lb_build_cond(lbProcedure *p, Ast *cond, lbBlock *true_block, lbBlock *false_block); gb_internal LLVMValueRef llvm_const_named_struct(lbModule *m, Type *t, LLVMValueRef *values, isize value_count_); -gb_internal LLVMValueRef llvm_const_named_struct_internal(LLVMTypeRef t, LLVMValueRef *values, isize value_count_); +gb_internal LLVMValueRef llvm_const_named_struct_internal(lbModule *m, LLVMTypeRef t, LLVMValueRef *values, isize value_count_); gb_internal void lb_set_entity_from_other_modules_linkage_correctly(lbModule *other_module, Entity *e, String const &name); gb_internal lbValue lb_expr_untyped_const_to_typed(lbModule *m, Ast *expr, Type *t); diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 8cdc889e0..d3e2826ed 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -81,7 +81,7 @@ gb_internal String lb_get_const_string(lbModule *m, lbValue value) { } -gb_internal LLVMValueRef llvm_const_cast(LLVMValueRef val, LLVMTypeRef dst) { +gb_internal LLVMValueRef llvm_const_cast(LLVMValueRef val, LLVMTypeRef dst, bool *failure_) { LLVMTypeRef src = LLVMTypeOf(val); if (src == dst) { return val; @@ -97,10 +97,8 @@ gb_internal LLVMValueRef llvm_const_cast(LLVMValueRef val, LLVMTypeRef dst) { return LLVMConstPointerCast(val, dst); case LLVMStructTypeKind: return val; - default: - GB_PANIC("Unhandled const cast %s to %s", LLVMPrintTypeToString(src), LLVMPrintTypeToString(dst)); } - + if (failure_) *failure_ = true; return val; } @@ -125,13 +123,13 @@ gb_internal LLVMValueRef llvm_const_string_internal(lbModule *m, Type *t, LLVMVa LLVMConstNull(lb_type(m, t_i32)), len, }; - return llvm_const_named_struct_internal(lb_type(m, t), values, 3); + return llvm_const_named_struct_internal(m, lb_type(m, t), values, 3); } else { LLVMValueRef values[2] = { data, len, }; - return llvm_const_named_struct_internal(lb_type(m, t), values, 2); + return llvm_const_named_struct_internal(m, lb_type(m, t), values, 2); } } @@ -143,13 +141,13 @@ gb_internal LLVMValueRef llvm_const_string16_internal(lbModule *m, Type *t, LLVM LLVMConstNull(lb_type(m, t_i32)), len, }; - return llvm_const_named_struct_internal(lb_type(m, t), values, 3); + return llvm_const_named_struct_internal(m, lb_type(m, t), values, 3); } else { LLVMValueRef values[2] = { data, len, }; - return llvm_const_named_struct_internal(lb_type(m, t), values, 2); + return llvm_const_named_struct_internal(m, lb_type(m, t), values, 2); } } @@ -161,7 +159,7 @@ gb_internal LLVMValueRef llvm_const_named_struct(lbModule *m, Type *t, LLVMValue unsigned value_count = cast(unsigned)value_count_; unsigned elem_count = LLVMCountStructElementTypes(struct_type); if (elem_count == value_count) { - return llvm_const_named_struct_internal(struct_type, values, value_count_); + return llvm_const_named_struct_internal(m, struct_type, values, value_count_); } Type *bt = base_type(t); GB_ASSERT(bt->kind == Type_Struct || bt->kind == Type_Union); @@ -181,24 +179,33 @@ gb_internal LLVMValueRef llvm_const_named_struct(lbModule *m, Type *t, LLVMValue } } - return llvm_const_named_struct_internal(struct_type, values_with_padding, values_with_padding_count); + return llvm_const_named_struct_internal(m, struct_type, values_with_padding, values_with_padding_count); } -gb_internal LLVMValueRef llvm_const_named_struct_internal(LLVMTypeRef t, LLVMValueRef *values, isize value_count_) { +gb_internal LLVMValueRef llvm_const_named_struct_internal(lbModule *m, LLVMTypeRef t, LLVMValueRef *values, isize value_count_) { unsigned value_count = cast(unsigned)value_count_; unsigned elem_count = LLVMCountStructElementTypes(t); GB_ASSERT_MSG(value_count == elem_count, "%s %u %u", LLVMPrintTypeToString(t), value_count, elem_count); + bool failure = false; for (unsigned i = 0; i < elem_count; i++) { LLVMTypeRef elem_type = LLVMStructGetTypeAtIndex(t, i); - values[i] = llvm_const_cast(values[i], elem_type); + values[i] = llvm_const_cast(values[i], elem_type, &failure); + } + + if (failure) { + return LLVMConstStructInContext(m->ctx, values, value_count, true); } return LLVMConstNamedStruct(t, values, value_count); } gb_internal LLVMValueRef llvm_const_array(lbModule *m, LLVMTypeRef elem_type, LLVMValueRef *values, isize value_count_) { unsigned value_count = cast(unsigned)value_count_; + bool failure = false; for (unsigned i = 0; i < value_count; i++) { - values[i] = llvm_const_cast(values[i], elem_type); + values[i] = llvm_const_cast(values[i], elem_type, &failure); + } + if (failure) { + return LLVMConstStructInContext(m->ctx, values, value_count, false); } for (unsigned i = 0; i < value_count; i++) { if (elem_type != LLVMTypeOf(values[i])) { @@ -851,7 +858,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb if (is_type_union_maybe_pointer(type)) { LLVMValueRef values[1] = {cv.value}; - res.value = llvm_const_named_struct_internal(llvm_type, values, 1); + res.value = llvm_const_named_struct_internal(m, llvm_type, values, 1); res.type = original_type; return res; } else { @@ -869,7 +876,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb value_count = 3; padding = LLVMConstNull(LLVMStructGetTypeAtIndex(llvm_type, 2)); } - res.value = llvm_const_named_struct_internal(llvm_type, values, value_count); + res.value = llvm_const_named_struct_internal(m, llvm_type, values, value_count); res.type = original_type; return res; } @@ -1060,7 +1067,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb Type *elem = type->Matrix.elem; lbValue single_elem = lb_const_value(m, elem, value, cc); - single_elem.value = llvm_const_cast(single_elem.value, lb_type(m, elem)); + single_elem.value = llvm_const_cast(single_elem.value, lb_type(m, elem), /*failure_*/nullptr); i64 total_elem_count = matrix_type_total_internal_elems(type); LLVMValueRef *elems = gb_alloc_array(permanent_allocator(), LLVMValueRef, cast(isize)total_elem_count); @@ -1082,7 +1089,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb Type *elem = type->SimdVector.elem; lbValue single_elem = lb_const_value(m, elem, value, cc); - single_elem.value = llvm_const_cast(single_elem.value, lb_type(m, elem)); + single_elem.value = llvm_const_cast(single_elem.value, lb_type(m, elem), /*failure_*/nullptr); LLVMValueRef *elems = gb_alloc_array(permanent_allocator(), LLVMValueRef, count); for (i64 i = 0; i < count; i++) { @@ -1662,7 +1669,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb values[i] = LLVMConstNull(et); } for (isize i = 0; i < total_elem_count; i++) { - values[i] = llvm_const_cast(values[i], et); + values[i] = llvm_const_cast(values[i], et, /*failure_*/nullptr); } res.value = LLVMConstVector(values, cast(unsigned)total_elem_count); @@ -1829,7 +1836,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb } if (is_constant) { - res.value = llvm_const_named_struct_internal(struct_type, values, cast(unsigned)value_count); + res.value = llvm_const_named_struct_internal(m, struct_type, values, cast(unsigned)value_count); return res; } else { // TODO(bill): THIS IS HACK BUT IT WORKS FOR WHAT I NEED @@ -1843,7 +1850,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb new_values[i] = LLVMConstNull(LLVMTypeOf(old_value)); } } - LLVMValueRef constant_value = llvm_const_named_struct_internal(struct_type, new_values, cast(unsigned)value_count); + LLVMValueRef constant_value = llvm_const_named_struct_internal(m, struct_type, new_values, cast(unsigned)value_count); GB_ASSERT(is_local); lbProcedure *p = m->curr_procedure; From 547477abf60c4c643940b55ed729541fbca21cf7 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 20:47:32 +0100 Subject: [PATCH 127/162] Add `#+test` to replace `_test.odin` --- src/parser.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/parser.cpp b/src/parser.cpp index a32494c05..363eb0c55 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6561,6 +6561,10 @@ gb_internal bool parse_file_tag(const String &lc, const Token &tok, AstFile *f) } else if (string_starts_with(lc, str_lit("vet"))) { f->vet_flags = parse_vet_tag(tok, lc); f->vet_flags_set = true; + } else if (string_starts_with(lc, str_lit("test"))) { + if ((build_context.command_kind & Command_test) == 0) { + return false; + } } else if (string_starts_with(lc, str_lit("ignore"))) { return false; } else if (string_starts_with(lc, str_lit("private"))) { From d3b877031838c5a13c4adbfdd1d8fb77c35c9801 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 20:48:13 +0100 Subject: [PATCH 128/162] Add `#+test` to base32_test.odin --- core/encoding/base32/base32_test.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/encoding/base32/base32_test.odin b/core/encoding/base32/base32_test.odin index ea41ae36f..07d5c8080 100644 --- a/core/encoding/base32/base32_test.odin +++ b/core/encoding/base32/base32_test.odin @@ -1,3 +1,4 @@ +#+test package encoding_base32 import "core:testing" From f743110f63d7659d0990715311ea64fea056e249 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 21:00:36 +0100 Subject: [PATCH 129/162] Correct union type checking for constants --- src/check_expr.cpp | 208 ++++++++++++++++++----------------- src/llvm_backend_general.cpp | 2 +- 2 files changed, 108 insertions(+), 102 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2da8776eb..ebf61ab0c 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -821,9 +821,11 @@ gb_internal i64 check_distance_between_types(CheckerContext *c, Operand *operand } } - if (is_type_enum(dst) && are_types_identical(dst->Enum.base_type, operand->type)) { - if (c->in_enum_type) { - return 3; + if (c != nullptr) { + if (is_type_enum(dst) && are_types_identical(dst->Enum.base_type, operand->type)) { + if (c->in_enum_type) { + return 3; + } } } @@ -3590,11 +3592,7 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type, bool forb Type *final_type = type; if (is_const_expr && !is_type_constant_type(type)) { if (is_type_union(type)) { - if (is_type_union_constantable(type)) { - - } else { - convert_to_typed(c, x, type); - } + convert_to_typed(c, x, type); } final_type = default_type(x->type); } @@ -8033,6 +8031,106 @@ gb_internal bool check_call_parameter_mixture(Slice const &args, char con return Expr_Stmt; \ } +gb_internal ExprKind check_call_expr_as_type_cast(CheckerContext *c, Operand *operand, Ast *call, Slice const &args, Type *type_hint) { + GB_ASSERT(operand->mode == Addressing_Type); + Type *t = operand->type; + if (is_type_polymorphic_record(t)) { + CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("polymorphic type construction"); + + if (!is_type_named(t)) { + gbString s = expr_to_string(operand->expr); + error(call, "Illegal use of an unnamed polymorphic record, %s", s); + gb_string_free(s); + operand->mode = Addressing_Invalid; + operand->type = t_invalid;; + return Expr_Expr; + } + auto err = check_polymorphic_record_type(c, operand, call); + if (err == 0) { + Ast *ident = operand->expr; + while (ident->kind == Ast_SelectorExpr) { + Ast *s = ident->SelectorExpr.selector; + ident = s; + } + Type *ot = operand->type; + GB_ASSERT(ot->kind == Type_Named); + Entity *e = ot->Named.type_name; + add_entity_use(c, ident, e); + add_type_and_value(c, call, Addressing_Type, ot, empty_exact_value); + } else { + operand->mode = Addressing_Invalid; + operand->type = t_invalid; + } + } else { + CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("type conversion"); + + operand->mode = Addressing_Invalid; + isize arg_count = args.count; + switch (arg_count) { + case 0: + { + gbString str = type_to_string(t); + error(call, "Missing argument in conversion to '%s'", str); + gb_string_free(str); + } break; + default: + { + gbString str = type_to_string(t); + if (t->kind == Type_Basic) { + ERROR_BLOCK(); + switch (t->Basic.kind) { + case Basic_complex32: + case Basic_complex64: + case Basic_complex128: + error(call, "Too many arguments in conversion to '%s'", str); + error_line("\tSuggestion: %s(1+2i) or construct with 'complex'\n", str); + break; + case Basic_quaternion64: + case Basic_quaternion128: + case Basic_quaternion256: + error(call, "Too many arguments in conversion to '%s'", str); + error_line("\tSuggestion: %s(1+2i+3j+4k) or construct with 'quaternion'\n", str); + break; + default: + error(call, "Too many arguments in conversion to '%s'", str); + } + } else { + error(call, "Too many arguments in conversion to '%s'", str); + } + gb_string_free(str); + } break; + case 1: { + Ast *arg = args[0]; + if (arg->kind == Ast_FieldValue) { + error(call, "'field = value' cannot be used in a type conversion"); + arg = arg->FieldValue.value; + // NOTE(bill): Carry on the cast regardless + } + check_expr_with_type_hint(c, operand, arg, t); + if (operand->mode != Addressing_Invalid) { + if (is_type_polymorphic(t)) { + error(call, "A polymorphic type cannot be used in a type conversion"); + } else { + // NOTE(bill): Otherwise the compiler can override the polymorphic type + // as it assumes it is determining the type + check_cast(c, operand, t); + } + } + operand->type = t; + operand->expr = call; + + + if (operand->mode != Addressing_Invalid) { + update_untyped_expr_type(c, arg, t, false); + } + break; + } + } + } + return Expr_Expr; +} + + gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *proc, Slice const &args, ProcInlining inlining, Type *type_hint) { if (proc != nullptr && @@ -8089,99 +8187,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } if (operand->mode == Addressing_Type) { - Type *t = operand->type; - if (is_type_polymorphic_record(t)) { - CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("polymorphic type construction"); - - if (!is_type_named(t)) { - gbString s = expr_to_string(operand->expr); - error(call, "Illegal use of an unnamed polymorphic record, %s", s); - gb_string_free(s); - operand->mode = Addressing_Invalid; - operand->type = t_invalid;; - return Expr_Expr; - } - auto err = check_polymorphic_record_type(c, operand, call); - if (err == 0) { - Ast *ident = operand->expr; - while (ident->kind == Ast_SelectorExpr) { - Ast *s = ident->SelectorExpr.selector; - ident = s; - } - Type *ot = operand->type; - GB_ASSERT(ot->kind == Type_Named); - Entity *e = ot->Named.type_name; - add_entity_use(c, ident, e); - add_type_and_value(c, call, Addressing_Type, ot, empty_exact_value); - } else { - operand->mode = Addressing_Invalid; - operand->type = t_invalid; - } - } else { - CHECK_CALL_PARAMETER_MIXTURE_OR_RETURN("type conversion"); - - operand->mode = Addressing_Invalid; - isize arg_count = args.count; - switch (arg_count) { - case 0: - { - gbString str = type_to_string(t); - error(call, "Missing argument in conversion to '%s'", str); - gb_string_free(str); - } break; - default: - { - gbString str = type_to_string(t); - if (t->kind == Type_Basic) { - ERROR_BLOCK(); - switch (t->Basic.kind) { - case Basic_complex32: - case Basic_complex64: - case Basic_complex128: - error(call, "Too many arguments in conversion to '%s'", str); - error_line("\tSuggestion: %s(1+2i) or construct with 'complex'\n", str); - break; - case Basic_quaternion64: - case Basic_quaternion128: - case Basic_quaternion256: - error(call, "Too many arguments in conversion to '%s'", str); - error_line("\tSuggestion: %s(1+2i+3j+4k) or construct with 'quaternion'\n", str); - break; - default: - error(call, "Too many arguments in conversion to '%s'", str); - } - } else { - error(call, "Too many arguments in conversion to '%s'", str); - } - gb_string_free(str); - } break; - case 1: { - Ast *arg = args[0]; - if (arg->kind == Ast_FieldValue) { - error(call, "'field = value' cannot be used in a type conversion"); - arg = arg->FieldValue.value; - // NOTE(bill): Carry on the cast regardless - } - check_expr_with_type_hint(c, operand, arg, t); - if (operand->mode != Addressing_Invalid) { - if (is_type_polymorphic(t)) { - error(call, "A polymorphic type cannot be used in a type conversion"); - } else { - // NOTE(bill): Otherwise the compiler can override the polymorphic type - // as it assumes it is determining the type - check_cast(c, operand, t); - } - } - operand->type = t; - operand->expr = call; - if (operand->mode != Addressing_Invalid) { - update_untyped_expr_type(c, arg, t, false); - } - break; - } - } - } - return Expr_Expr; + return check_call_expr_as_type_cast(c, operand, call, args, type_hint); } if (operand->mode == Addressing_Builtin) { diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 8e5efcb52..b181788b9 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -3256,7 +3256,7 @@ gb_internal lbAddr lb_add_global_generated_with_name(lbModule *m, Type *type, lb LLVMTypeRef actual_type = lb_type(m, type); if (value.value != nullptr) { LLVMTypeRef value_type = LLVMTypeOf(value.value); - GB_ASSERT(lb_sizeof(actual_type) == lb_sizeof(value_type)); + GB_ASSERT_MSG(lb_sizeof(actual_type) == lb_sizeof(value_type), "%s vs %s", LLVMPrintTypeToString(actual_type), LLVMPrintTypeToString(value_type)); actual_type = value_type; } From 35a32d41e03ad10134d7705e4e502c1a23f39d47 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 21:08:47 +0100 Subject: [PATCH 130/162] Fix `Union{}` --- src/llvm_backend_const.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index d3e2826ed..9563e4800 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -885,6 +885,16 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb i64 block_size = bt->Union.variant_block_size; + if (are_types_identical(value_type, original_type)) { + if (value.kind == ExactValue_Compound) { + ast_node(cl, CompoundLit, value.value_compound); + GB_ASSERT(cl->elems.count == 0); + return lb_const_nil(m, original_type); + } + + GB_PANIC("%s vs %s", type_to_string(value_type), type_to_string(original_type)); + } + lbValue cv = lb_const_value(m, value_type, value, cc, value_type); Type *variant_type = cv.type; From 8be18d9a401f898320b57379488bad1367632628 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 21:47:56 +0100 Subject: [PATCH 131/162] Allow for constant `[]typeid` --- src/check_decl.cpp | 21 +++++++++++++++++++++ src/check_expr.cpp | 27 +++++++++++++++------------ src/types.cpp | 30 +++++++++++++++++++++++++----- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index bda1059fb..842f8653c 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1686,7 +1686,28 @@ gb_internal void check_global_variable_decl(CheckerContext *ctx, Entity *e, Ast 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_BLOCK(); error(o.expr, "Variables declared with @(rodata) must have constant initialization"); + Ast *expr = unparen_expr(o.expr); + if (is_type_struct(e->type) && expr && expr->kind == Ast_CompoundLit) { + ast_node(cl, CompoundLit, expr); + for (Ast *elem_ : cl->elems) { + Ast *elem = elem_; + if (elem->kind == Ast_FieldValue) { + elem = elem->FieldValue.value; + } + elem = unparen_expr(elem); + + Entity *e = entity_of_node(elem); + if (elem->tav.mode != Addressing_Constant && e == nullptr && elem->kind != Ast_ProcLit) { + Token tok = ast_token(elem); + TokenPos pos = tok.pos; + gbString s = type_to_string(type_of_expr(elem)); + error_line("%s Element is not constant, which is required for @(rodata), of type %s\n", token_pos_to_string(pos), s); + gb_string_free(s); + } + } + } } 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_expr.cpp b/src/check_expr.cpp index ebf61ab0c..3c37284eb 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -8655,7 +8655,7 @@ gb_internal bool check_range(CheckerContext *c, Ast *node, bool is_for_loop, Ope return true; } -gb_internal bool check_is_operand_compound_lit_constant(CheckerContext *c, Operand *o) { +gb_internal bool check_is_operand_compound_lit_constant(CheckerContext *c, Operand *o, Type *field_type) { if (is_operand_nil(*o)) { return true; } @@ -8670,6 +8670,9 @@ gb_internal bool check_is_operand_compound_lit_constant(CheckerContext *c, Opera return true; } } + if (field_type != nullptr && is_type_typeid(field_type) && o->mode == Addressing_Type) { + return true; + } return o->mode == Addressing_Constant; } @@ -9620,8 +9623,7 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, Slicevalue, field->type); - if (is_type_any(field->type) || is_type_union(field->type) || is_type_raw_union(field->type) || is_type_typeid(field->type)) { + if (elem_cannot_be_constant(field->type)) { is_constant = false; } if (is_constant) { - is_constant = check_is_operand_compound_lit_constant(c, &o); + is_constant = check_is_operand_compound_lit_constant(c, &o, field->type); } u8 prev_bit_field_bit_size = c->bit_field_bit_size; @@ -9886,14 +9888,11 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * Operand o = {}; check_expr_or_type(c, &o, elem, field->type); - if (is_type_any(field->type) || - is_type_raw_union(field->type) || - (is_type_union(field->type) && !is_type_union_constantable(field->type)) || - is_type_typeid(field->type)) { + if (elem_cannot_be_constant(field->type)) { is_constant = false; } if (is_constant) { - is_constant = check_is_operand_compound_lit_constant(c, &o); + is_constant = check_is_operand_compound_lit_constant(c, &o, field->type); } check_assignment(c, &o, field->type, str_lit("structure literal")); @@ -10070,7 +10069,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * check_expr_with_type_hint(c, &operand, fv->value, elem_type); check_assignment(c, &operand, elem_type, context_name); - is_constant = is_constant && operand.mode == Addressing_Constant; + if (is_constant) { + is_constant = check_is_operand_compound_lit_constant(c, &operand, elem_type); + } } } @@ -10097,7 +10098,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * check_expr_with_type_hint(c, &operand, e, elem_type); check_assignment(c, &operand, elem_type, context_name); - is_constant = is_constant && operand.mode == Addressing_Constant; + if (is_constant) { + is_constant = check_is_operand_compound_lit_constant(c, &operand, elem_type); + } } if (max < index) { diff --git a/src/types.cpp b/src/types.cpp index 46e41bb4b..814f99eff 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1377,14 +1377,20 @@ gb_internal bool is_type_ordered_numeric(Type *t) { gb_internal bool is_type_constant_type(Type *t) { t = core_type(t); if (t == nullptr) { return false; } - if (t->kind == Type_Basic) { + switch (t->kind) { + case Type_Basic: + if (t->Basic.kind == Basic_typeid) { + return true; + } return (t->Basic.flags & BasicFlag_ConstantType) != 0; - } - if (t->kind == Type_BitSet) { + case Type_BitSet: return true; - } - if (t->kind == Type_Proc) { + case Type_Proc: return true; + case Type_Array: + return is_type_constant_type(t->Array.elem); + case Type_EnumeratedArray: + return is_type_constant_type(t->EnumeratedArray.elem); } return false; } @@ -2539,6 +2545,20 @@ gb_internal bool elem_type_can_be_constant(Type *t) { return true; } +gb_internal bool elem_cannot_be_constant(Type *t) { + if (is_type_any(t)) { + return true; + } + if (is_type_union(t)) { + return !is_type_union_constantable(t); + } + if (is_type_raw_union(t)) { + return true; + } + return false; +} + + gb_internal bool is_type_lock_free(Type *t) { t = core_type(t); if (t == t_invalid) { From 1df9f1d01d2a4ecca117b9eb74b50ec6836bf92c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 22:02:25 +0100 Subject: [PATCH 132/162] Fix constant `union{proc()}` --- src/check_expr.cpp | 15 +++++++++++ src/llvm_backend_const.cpp | 51 ++++++++++++++++++++------------------ 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 3c37284eb..a59dbdc42 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -2340,6 +2340,20 @@ gb_internal bool check_representable_as_constant(CheckerContext *c, ExactValue i if (in_value.kind == ExactValue_Integer) { return true; } + } else if (is_type_typeid(type)) { + + if (in_value.kind == ExactValue_Compound) { + ast_node(cl, CompoundLit, in_value.value_compound); + if (cl->elems.count == 0) { + in_value = exact_value_typeid(nullptr); + } else { + return false; + } + } + if (in_value.kind == ExactValue_Typeid) { + if (out_value) *out_value = in_value; + return true; + } } return false; @@ -3510,6 +3524,7 @@ gb_internal bool check_is_castable_to(CheckerContext *c, Operand *operand, Type gb_internal bool check_cast_internal(CheckerContext *c, Operand *x, Type *type) { bool is_const_expr = x->mode == Addressing_Constant; + Type *bt = base_type(type); if (is_const_expr && is_type_constant_type(bt)) { if (core_type(bt)->kind == Type_Basic) { diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 9563e4800..66e2e50aa 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -540,7 +540,7 @@ gb_internal bool lb_is_nested_possibly_constant(Type *ft, Selection const &sel, } - if (is_type_raw_union(ft) || is_type_typeid(ft)) { + if (is_type_raw_union(ft)) { return false; } return lb_is_elem_const(elem, ft); @@ -819,28 +819,6 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb return lb_const_nil(m, original_type); } - if (value.kind == ExactValue_Procedure) { - lbValue res = {}; - Ast *expr = unparen_expr(value.value_procedure); - GB_ASSERT(expr != nullptr); - if (expr->kind == Ast_ProcLit) { - res = lb_generate_anonymous_proc_lit(m, str_lit("_proclit"), expr); - } else { - Entity *e = entity_from_expr(expr); - res = lb_find_procedure_value_from_entity(m, e); - } - if (res.value == nullptr) { - // This is an unspecialized polymorphic procedure, return nil or dummy value - return lb_const_nil(m, original_type); - } - GB_ASSERT(LLVMGetValueKind(res.value) == LLVMFunctionValueKind); - - if (LLVMGetIntrinsicID(res.value) == 0) { - // NOTE(bill): do not cast intrinsics as they are not really procedures that can be casted - res.value = LLVMConstPointerCast(res.value, lb_type(m, res.type)); - } - return res; - } bool is_local = cc.allow_local && m->curr_procedure != nullptr; @@ -922,7 +900,29 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb res.value = LLVMConstStructInContext(m->ctx, values, value_count, true); return res; } + } + if (value.kind == ExactValue_Procedure) { + lbValue res = {}; + Ast *expr = unparen_expr(value.value_procedure); + GB_ASSERT(expr != nullptr); + if (expr->kind == Ast_ProcLit) { + res = lb_generate_anonymous_proc_lit(m, str_lit("_proclit"), expr); + } else { + Entity *e = entity_from_expr(expr); + res = lb_find_procedure_value_from_entity(m, e); + } + if (res.value == nullptr) { + // This is an unspecialized polymorphic procedure, return nil or dummy value + return lb_const_nil(m, original_type); + } + GB_ASSERT(LLVMGetValueKind(res.value) == LLVMFunctionValueKind); + + if (LLVMGetIntrinsicID(res.value) == 0) { + // NOTE(bill): do not cast intrinsics as they are not really procedures that can be casted + res.value = LLVMConstPointerCast(res.value, lb_type(m, res.type)); + } + return res; } // GB_ASSERT_MSG(is_type_typed(type), "%s", type_to_string(type)); @@ -1720,7 +1720,10 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb i32 index = field_remapping[f->Variable.field_index]; if (elem_type_can_be_constant(f->type)) { if (sel.index.count == 1) { - values[index] = lb_const_value(m, f->type, tav.value, cc, tav.type).value; + lbValue value = lb_const_value(m, f->type, tav.value, cc, tav.type); + LLVMTypeRef value_type = LLVMTypeOf(value.value); + GB_ASSERT_MSG(lb_sizeof(value_type) == type_size_of(f->type), "%s vs %s", LLVMPrintTypeToString(value_type), type_to_string(f->type)); + values[index] = value.value; visited[index] = true; } else { if (!visited[index]) { From 17c9e1d76b3ff4a3cb2edb3dc0b6b070adaef91e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 22:11:46 +0100 Subject: [PATCH 133/162] Fix global initialization when non was set --- src/llvm_backend.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 8ac68d9bf..873f67cde 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3255,6 +3255,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { } GB_ASSERT(e->kind == Entity_Variable); + bool is_foreign = e->Variable.is_foreign; bool is_export = e->Variable.is_export; @@ -3269,7 +3270,6 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { g.type = alloc_type_pointer(e->type); g.value = LLVMAddGlobal(m->mod, lb_type(m, e->type), alloc_cstring(permanent_allocator(), name)); - if (decl->init_expr != nullptr) { TypeAndValue tav = type_and_value_of_expr(decl->init_expr); if (!is_type_any(e->type) && !is_type_union(e->type)) { @@ -3313,7 +3313,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { LLVMSetDLLStorageClass(g.value, LLVMDLLImportStorageClass); LLVMSetExternallyInitialized(g.value, true); lb_add_foreign_library_path(m, e->Variable.foreign_library); - } else if (!var.is_initialized) { + } else if (LLVMGetInitializer(g.value) == nullptr) { LLVMSetInitializer(g.value, LLVMConstNull(lb_type(m, e->type))); } if (is_export) { From cbab97fbd74834ef4b06126b8a6787479e177b24 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 22:50:36 +0100 Subject: [PATCH 134/162] Use `memcpy` for local constant slice arrays from a global constant --- src/llvm_backend_const.cpp | 202 +++++------------------------------ src/llvm_backend_expr.cpp | 18 ---- src/llvm_backend_general.cpp | 5 +- 3 files changed, 30 insertions(+), 195 deletions(-) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 66e2e50aa..dc555a7bd 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -641,161 +641,6 @@ gb_internal Slice lb_construct_const_union_flatten_values(lbModule return {}; } -gb_internal LLVMValueRef lb_construct_const_union(lbModule *m, LLVMValueRef variant_value, Type *variant_type, Type *union_type) { -#if 1 - return nullptr; -#else - Type *bt = base_type(union_type); - GB_ASSERT(bt->kind == Type_Union); - GB_ASSERT(lb_type(m, variant_type) == LLVMTypeOf(variant_value)); - - LLVMTypeRef llvm_type = lb_type(m, union_type); - - if (LLVMIsNull(variant_value)) { - return LLVMConstNull(llvm_type); - } - - if (bt->Union.variants.count == 0) { - GB_ASSERT(LLVMIsNull(variant_value)); - return variant_value; - } - - i64 block_size = bt->Union.variant_block_size; - i64 variant_size = type_size_of(variant_type); - - LLVMTypeRef llvm_variant_type = lb_type(m, variant_type); - - if (is_type_union_maybe_pointer(bt)) { - GB_ASSERT(lb_sizeof(LLVMTypeOf(variant_value)) == lb_sizeof(llvm_type)); - return LLVMConstBitCast(variant_value, llvm_type); - } - - if (bt->Union.variants.count == 1) { - unsigned long long the_tag = cast(unsigned long long)union_variant_index(union_type, variant_type); - LLVMTypeRef tag_type = lb_type(m, union_tag_type(bt)); - - LLVMValueRef values[3] = {}; - unsigned i = 0; - values[i++] = variant_value; - values[i++] = LLVMConstInt(tag_type, the_tag, false); - - i64 used_size = block_size + lb_sizeof(tag_type); - i64 padding = type_size_of(union_type) - used_size; - i64 align = type_align_of(union_type); - if (padding > 0) { - LLVMTypeRef padding_type = lb_type_padding_filler(m, padding, align); - values[i++] = LLVMConstNull(padding_type); - } - - return LLVMConstNamedStruct(llvm_type, values, i); - } else if (true) { - // TODO(bill): ignore this for the time being - return nullptr; - } - - LLVMTypeRef block_type = LLVMStructGetTypeAtIndex(llvm_type, 0); - - LLVMTypeRef tag_type = lb_type(m, union_tag_type(bt)); - - i64 used_size = block_size + lb_sizeof(tag_type); - i64 padding = type_size_of(union_type) - used_size; - i64 align = type_align_of(union_type); - LLVMTypeRef padding_type = nullptr; - if (padding > 0) { - padding_type = lb_type_padding_filler(m, padding, align); - } - - - unsigned i = 0; - LLVMValueRef values[3] = {}; - - LLVMValueRef block_value = variant_value; - - if (block_size == 0) { - block_value = LLVMConstNull(block_type); - } else if (lb_sizeof(llvm_variant_type) == 0) { - block_value = LLVMConstNull(block_type); - } else if (block_type != llvm_variant_type) { - - LLVMTypeKind block_kind = LLVMGetTypeKind(block_type); - LLVMTypeKind variant_kind = LLVMGetTypeKind(llvm_variant_type); - - - if (block_kind == LLVMArrayTypeKind) { - LLVMTypeRef elem = LLVMGetElementType(block_type); - unsigned count = LLVMGetArrayLength(block_type); - - Slice partial_elems = lb_construct_const_union_flatten_values(m, variant_value, variant_type, elem); - if (partial_elems.count == count) { - block_value = LLVMConstArray(elem, partial_elems.data, count); - goto assign_value_wrapped; - } - - Slice full_elems = temporary_slice_make(count); - slice_copy(&full_elems, partial_elems); - for (isize j = partial_elems.count; j < count; j++) { - full_elems[j] = LLVMConstNull(elem); - } - block_value = LLVMConstArray(elem, full_elems.data, count); - goto assign_value_wrapped; - - } else if (block_size != variant_size) { - if (block_kind == LLVMIntegerTypeKind && !is_type_different_to_arch_endianness(variant_type)) { - Slice partial_elems = lb_construct_const_union_flatten_values(m, variant_value, variant_type, block_type); - if (partial_elems.count == 1) { - block_value = partial_elems[0]; - goto assign_value_wrapped; - } - } - - return nullptr; - } - if (block_kind == LLVMIntegerTypeKind) { - GB_ASSERT(block_size == variant_size); - - switch (variant_kind) { - case LLVMHalfTypeKind: - case LLVMFloatTypeKind: - case LLVMDoubleTypeKind: - block_value = LLVMConstBitCast(block_value, block_type); - goto assign_value_wrapped; - case LLVMPointerTypeKind: - block_value = LLVMConstPtrToInt(block_value, block_type); - goto assign_value_wrapped; - } - } - - return nullptr; - } else { - // TODO(bill): ignore this for the time being - return nullptr; - } - -assign_value_wrapped:; - values[i++] = block_value; - - unsigned long long the_tag = cast(unsigned long long)union_variant_index(union_type, variant_type); - values[i++] = LLVMConstInt(tag_type, the_tag, false); - if (padding > 0) { - values[i++] = LLVMConstNull(padding_type); - } - return LLVMConstNamedStruct(llvm_type, values, i); -#endif -} - -gb_internal bool lb_try_construct_const_union(lbModule *m, lbValue *value, Type *variant_type, Type *union_type) { - if (lb_is_const(*value)) { - LLVMValueRef res = lb_construct_const_union(m, value->value, variant_type, union_type); - if (res != nullptr) { - *value = {res, union_type}; - return true; - } - // gb_printf_err("%s -> %s\n", LLVMPrintValueToString(value->value), LLVMPrintTypeToString(lb_type(m, union_type))); - } - return false; -} - - gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lbConstContext cc, Type *value_type) { if (cc.allow_local) { cc.is_rodata = false; @@ -888,6 +733,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb Type *tag_type = union_tag_type(bt); LLVMTypeRef llvm_tag_type = lb_type(m, tag_type); i64 tag_index = union_variant_index(bt, variant_type); + GB_ASSERT(tag_index >= 0); values[value_count++] = LLVMConstInt(llvm_tag_type, tag_index, false); i64 used_size = block_size + type_size_of(tag_type); i64 union_size = type_size_of(bt); @@ -946,27 +792,42 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb count = gb_max(cast(isize)cl->max_count, count); Type *elem = base_type(type)->Slice.elem; Type *t = alloc_type_array(elem, count); - lbValue backing_array = lb_const_value(m, t, value, cc); + lbValue backing_array = lb_const_value(m, t, value, cc, nullptr); LLVMValueRef array_data = nullptr; + u32 id = m->global_array_index.fetch_add(1); + gbString str = gb_string_make(temporary_allocator(), "csba$"); + str = gb_string_appendc(str, m->module_name); + str = gb_string_append_fmt(str, "$%x", id); + + String name = make_string(cast(u8 const *)str, gb_string_length(str)); + + Entity *e = alloc_entity_constant(nullptr, make_token_ident(name), t, value); + array_data = LLVMAddGlobal(m->mod, LLVMTypeOf(backing_array.value), str); + LLVMSetInitializer(array_data, backing_array.value); + + + if (is_local) { + LLVMSetGlobalConstant(array_data, true); + // NOTE(bill, 2020-06-08): This is a bit of a hack but a "constant" slice needs // its backing data on the stack lbProcedure *p = m->curr_procedure; + + // NOTE(bill, 2025-09-28): make the array data global BUT memcpy it + // to make a local copy LLVMTypeRef llvm_type = lb_type(m, t); - - array_data = llvm_alloca(p, llvm_type, 16); - - { - LLVMValueRef ptr = array_data; - ptr = LLVMBuildPointerCast(p->builder, ptr, LLVMPointerType(LLVMTypeOf(backing_array.value), 0), ""); - LLVMBuildStore(p->builder, backing_array.value, ptr); - } + LLVMValueRef local_copy = llvm_alloca(p, llvm_type, 16); + LLVMBuildMemCpy(p->builder, + local_copy, 16, + array_data, 1, + LLVMConstInt(lb_type(m, t_int), type_size_of(t), false)); { LLVMValueRef indices[2] = {llvm_zero(m), llvm_zero(m)}; - LLVMValueRef ptr = LLVMBuildInBoundsGEP2(p->builder, llvm_type, array_data, indices, 2, ""); + LLVMValueRef ptr = LLVMBuildInBoundsGEP2(p->builder, llvm_type, local_copy, indices, 2, ""); LLVMValueRef len = LLVMConstInt(lb_type(m, t_int), count, true); lbAddr slice = lb_add_local_generated(p, original_type, false); @@ -976,17 +837,6 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb return lb_addr_load(p, slice); } } else { - u32 id = m->global_array_index.fetch_add(1); - gbString str = gb_string_make(temporary_allocator(), "csba$"); - str = gb_string_appendc(str, m->module_name); - str = gb_string_append_fmt(str, "$%x", id); - - String name = make_string(cast(u8 const *)str, gb_string_length(str)); - - Entity *e = alloc_entity_constant(nullptr, make_token_ident(name), t, value); - array_data = LLVMAddGlobal(m->mod, LLVMTypeOf(backing_array.value), str); - LLVMSetInitializer(array_data, backing_array.value); - if (cc.link_section.len > 0) { LLVMSetSection(array_data, alloc_cstring(permanent_allocator(), cc.link_section)); } diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 0e8562194..187c34595 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -2495,13 +2495,6 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { Type *vt = dst->Union.variants[0]; if (internal_check_is_assignable_to(src_type, vt)) { value = lb_emit_conv(p, value, vt); - if (lb_is_const(value)) { - LLVMValueRef res = lb_construct_const_union(m, value.value, vt, t); - if (res != nullptr) { - return {res, t}; - } - } - lbAddr parent = lb_add_local_generated(p, t, true); lb_emit_store_union_variant(p, parent.addr, value, vt); return lb_addr_load(p, parent); @@ -2509,19 +2502,11 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { } for (Type *vt : dst->Union.variants) { if (src_type == t_llvm_bool && is_type_boolean(vt)) { - value = lb_emit_conv(p, value, vt); - if (lb_try_construct_const_union(m, &value, vt, t)) { - return value; - } - lbAddr parent = lb_add_local_generated(p, t, true); lb_emit_store_union_variant(p, parent.addr, value, vt); return lb_addr_load(p, parent); } if (are_types_identical(src_type, vt)) { - if (lb_try_construct_const_union(m, &value, vt, t)) { - return value; - } lbAddr parent = lb_add_local_generated(p, t, true); lb_emit_store_union_variant(p, parent.addr, value, vt); return lb_addr_load(p, parent); @@ -2559,9 +2544,6 @@ gb_internal lbValue lb_emit_conv(lbProcedure *p, lbValue value, Type *t) { if (valid_count == 1) { Type *vt = dst->Union.variants[first_success_index]; value = lb_emit_conv(p, value, vt); - if (lb_try_construct_const_union(m, &value, vt, t)) { - return value; - } lbAddr parent = lb_add_local_generated(p, t, true); lb_emit_store_union_variant(p, parent.addr, value, vt); return lb_addr_load(p, parent); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index b181788b9..123af51f5 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -1493,8 +1493,11 @@ gb_internal lbValue lb_emit_union_tag_ptr(lbProcedure *p, lbValue u) { unsigned element_count = LLVMCountStructElementTypes(uvt); GB_ASSERT_MSG(element_count >= 2, "element_count=%u (%s) != (%s)", element_count, type_to_string(ut), LLVMPrintTypeToString(uvt)); + LLVMValueRef ptr = u.value; + ptr = LLVMBuildPointerCast(p->builder, ptr, LLVMPointerType(uvt, 0), ""); + lbValue tag_ptr = {}; - tag_ptr.value = LLVMBuildStructGEP2(p->builder, uvt, u.value, 1, ""); + tag_ptr.value = LLVMBuildStructGEP2(p->builder, uvt, ptr, 1, ""); tag_ptr.type = alloc_type_pointer(tag_type); return tag_ptr; } From 5b88d2363d493c84b59217aca756dc417ebeffa2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 23:19:43 +0100 Subject: [PATCH 135/162] Correct failure check for const cast --- src/llvm_backend_const.cpp | 57 +++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index dc555a7bd..fd3ee9da3 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -96,6 +96,9 @@ gb_internal LLVMValueRef llvm_const_cast(LLVMValueRef val, LLVMTypeRef dst, bool case LLVMPointerTypeKind: return LLVMConstPointerCast(val, dst); case LLVMStructTypeKind: + if (LLVMTypeOf(val) != dst) { + if (failure_) *failure_ = true; + } return val; } if (failure_) *failure_ = true; @@ -796,38 +799,30 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb LLVMValueRef array_data = nullptr; - u32 id = m->global_array_index.fetch_add(1); - gbString str = gb_string_make(temporary_allocator(), "csba$"); - str = gb_string_appendc(str, m->module_name); - str = gb_string_append_fmt(str, "$%x", id); - - String name = make_string(cast(u8 const *)str, gb_string_length(str)); - - Entity *e = alloc_entity_constant(nullptr, make_token_ident(name), t, value); - array_data = LLVMAddGlobal(m->mod, LLVMTypeOf(backing_array.value), str); - LLVMSetInitializer(array_data, backing_array.value); - - if (is_local) { - LLVMSetGlobalConstant(array_data, true); - // NOTE(bill, 2020-06-08): This is a bit of a hack but a "constant" slice needs // its backing data on the stack lbProcedure *p = m->curr_procedure; - - // NOTE(bill, 2025-09-28): make the array data global BUT memcpy it - // to make a local copy LLVMTypeRef llvm_type = lb_type(m, t); - LLVMValueRef local_copy = llvm_alloca(p, llvm_type, 16); + + unsigned alignment = cast(unsigned)gb_max(type_align_of(t), 16); + + LLVMValueRef local_copy = llvm_alloca(p, LLVMTypeOf(backing_array.value), alignment); + LLVMBuildStore(p->builder, backing_array.value, local_copy); + + array_data = llvm_alloca(p, llvm_type, alignment); + LLVMBuildMemCpy(p->builder, - local_copy, 16, - array_data, 1, - LLVMConstInt(lb_type(m, t_int), type_size_of(t), false)); + array_data, alignment, + local_copy, alignment, + LLVMConstInt(lb_type(m, t_int), type_size_of(t), false) + ); + { LLVMValueRef indices[2] = {llvm_zero(m), llvm_zero(m)}; - LLVMValueRef ptr = LLVMBuildInBoundsGEP2(p->builder, llvm_type, local_copy, indices, 2, ""); + LLVMValueRef ptr = LLVMBuildInBoundsGEP2(p->builder, llvm_type, array_data, indices, 2, ""); LLVMValueRef len = LLVMConstInt(lb_type(m, t_int), count, true); lbAddr slice = lb_add_local_generated(p, original_type, false); @@ -837,6 +832,17 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb return lb_addr_load(p, slice); } } else { + u32 id = m->global_array_index.fetch_add(1); + gbString str = gb_string_make(temporary_allocator(), "csba$"); + str = gb_string_appendc(str, m->module_name); + str = gb_string_append_fmt(str, "$%x", id); + + String name = make_string(cast(u8 const *)str, gb_string_length(str)); + + Entity *e = alloc_entity_constant(nullptr, make_token_ident(name), t, value); + array_data = LLVMAddGlobal(m->mod, LLVMTypeOf(backing_array.value), str); + LLVMSetInitializer(array_data, backing_array.value); + if (cc.link_section.len > 0) { LLVMSetSection(array_data, alloc_cstring(permanent_allocator(), cc.link_section)); } @@ -1673,7 +1679,10 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb i32 index = field_remapping[f->Variable.field_index]; if (elem_type_can_be_constant(f->type)) { - values[index] = lb_const_value(m, f->type, val, cc, tav.type).value; + lbValue value = lb_const_value(m, f->type, tav.value, cc, tav.type); + LLVMTypeRef value_type = LLVMTypeOf(value.value); + GB_ASSERT_MSG(lb_sizeof(value_type) == type_size_of(f->type), "%s vs %s", LLVMPrintTypeToString(value_type), type_to_string(f->type)); + values[index] = value.value; visited[index] = true; } } @@ -1700,6 +1709,8 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb if (is_constant) { res.value = llvm_const_named_struct_internal(m, struct_type, values, cast(unsigned)value_count); + LLVMTypeRef res_type = LLVMTypeOf(res.value); + GB_ASSERT(lb_sizeof(res_type) == lb_sizeof(struct_type)); return res; } else { // TODO(bill): THIS IS HACK BUT IT WORKS FOR WHAT I NEED From 6db8943efabf0f7569a5fcea069e84e77d7ad3ce Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 23:25:27 +0100 Subject: [PATCH 136/162] Check for empty compound literal early for constants --- src/llvm_backend_const.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index fd3ee9da3..f7a3a5f7a 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -667,6 +667,12 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb return lb_const_nil(m, original_type); } + if (value.kind == ExactValue_Compound) { + ast_node(cl, CompoundLit, value.value_compound); + if (cl->elems.count == 0) { + return lb_const_nil(m, original_type); + } + } bool is_local = cc.allow_local && m->curr_procedure != nullptr; @@ -707,17 +713,11 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb return res; } } else { - GB_ASSERT(value_type != nullptr); + GB_ASSERT_MSG(value_type != nullptr, "%s :: %s", type_to_string(original_type), exact_value_to_string(value)); i64 block_size = bt->Union.variant_block_size; if (are_types_identical(value_type, original_type)) { - if (value.kind == ExactValue_Compound) { - ast_node(cl, CompoundLit, value.value_compound); - GB_ASSERT(cl->elems.count == 0); - return lb_const_nil(m, original_type); - } - GB_PANIC("%s vs %s", type_to_string(value_type), type_to_string(original_type)); } From dd15a5bc8e546177f5a7ac89df464c3ed1e9c305 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 23:32:37 +0100 Subject: [PATCH 137/162] Do not need an extra local copy for the slices --- src/llvm_backend_const.cpp | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index f7a3a5f7a..cde610cc8 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -785,7 +785,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb GB_ASSERT(is_type_slice(type)); res.value = lb_find_or_add_entity_string16_slice_with_type(m, value.value_string16, original_type).value; return res; - }else { + } else { ast_node(cl, CompoundLit, value.value_compound); isize count = cl->elems.count; @@ -808,16 +808,25 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb unsigned alignment = cast(unsigned)gb_max(type_align_of(t), 16); - LLVMValueRef local_copy = llvm_alloca(p, LLVMTypeOf(backing_array.value), alignment); - LLVMBuildStore(p->builder, backing_array.value, local_copy); - array_data = llvm_alloca(p, llvm_type, alignment); + bool do_local_copy = false; + if (do_local_copy) { + array_data = llvm_alloca(p, llvm_type, alignment); - LLVMBuildMemCpy(p->builder, - array_data, alignment, - local_copy, alignment, - LLVMConstInt(lb_type(m, t_int), type_size_of(t), false) - ); + LLVMValueRef local_copy = llvm_alloca(p, LLVMTypeOf(backing_array.value), alignment); + LLVMBuildStore(p->builder, backing_array.value, local_copy); + + LLVMBuildMemCpy(p->builder, + array_data, alignment, + local_copy, alignment, + LLVMConstInt(lb_type(m, t_int), type_size_of(t), false) + ); + } else { + array_data = llvm_alloca(p, LLVMTypeOf(backing_array.value), alignment); + LLVMBuildStore(p->builder, backing_array.value, array_data); + + array_data = LLVMBuildPointerCast(p->builder, array_data, LLVMPointerType(llvm_type, 0), ""); + } { From 4877214f34abbccb8d7b11d773371fa935fe73d3 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 28 Sep 2025 23:53:07 +0100 Subject: [PATCH 138/162] Add more `check_is_operand_compound_lit_constant` uses --- src/check_expr.cpp | 20 ++++++++++++++++---- src/llvm_backend.cpp | 10 ++++++++-- src/types.cpp | 5 ++++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index a59dbdc42..10fec1890 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -8686,8 +8686,12 @@ gb_internal bool check_is_operand_compound_lit_constant(CheckerContext *c, Opera } } if (field_type != nullptr && is_type_typeid(field_type) && o->mode == Addressing_Type) { + add_type_info_type(c, o->type); return true; } + if (is_type_any(field_type)) { + return false; + } return o->mode == Addressing_Constant; } @@ -10052,7 +10056,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * check_expr_with_type_hint(c, &operand, fv->value, elem_type); check_assignment(c, &operand, elem_type, context_name); - is_constant = is_constant && operand.mode == Addressing_Constant; + if (is_constant) { + is_constant = check_is_operand_compound_lit_constant(c, &operand, elem_type); + } } else { Operand op_index = {}; check_expr(c, &op_index, fv->field); @@ -10289,7 +10295,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * check_expr_with_type_hint(c, &operand, fv->value, elem_type); check_assignment(c, &operand, elem_type, context_name); - is_constant = is_constant && operand.mode == Addressing_Constant; + if (is_constant) { + is_constant = check_is_operand_compound_lit_constant(c, &operand, elem_type); + } TokenKind upper_op = Token_LtEq; if (op.kind == Token_RangeHalf) { @@ -10330,7 +10338,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * check_expr_with_type_hint(c, &operand, fv->value, elem_type); check_assignment(c, &operand, elem_type, context_name); - is_constant = is_constant && operand.mode == Addressing_Constant; + if (is_constant) { + is_constant = check_is_operand_compound_lit_constant(c, &operand, elem_type); + } add_to_seen_map(c, &seen, op_index); } @@ -10360,7 +10370,9 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * check_expr_with_type_hint(c, &operand, e, elem_type); check_assignment(c, &operand, elem_type, context_name); - is_constant = is_constant && operand.mode == Addressing_Constant; + if (is_constant) { + is_constant = check_is_operand_compound_lit_constant(c, &operand, elem_type); + } } if (max < index) { diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 873f67cde..6a15e11a6 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1947,7 +1947,7 @@ gb_internal bool lb_init_global_var(lbModule *m, lbProcedure *p, Entity *e, Ast GB_PANIC("Invalid init value, got %s", expr_to_string(init_expr)); } - if (is_type_any(e->type) || is_type_union(e->type)) { + if (is_type_any(e->type)) { var.init = init; } else if (lb_is_const_or_global(init)) { if (!var.is_initialized) { @@ -3272,7 +3272,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { if (decl->init_expr != nullptr) { TypeAndValue tav = type_and_value_of_expr(decl->init_expr); - if (!is_type_any(e->type) && !is_type_union(e->type)) { + if (!is_type_any(e->type)) { if (tav.mode != Addressing_Invalid) { if (tav.value.kind != ExactValue_Invalid) { auto cc = LB_CONST_CONTEXT_DEFAULT; @@ -3287,6 +3287,12 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { g.value = nullptr; g.value = LLVMAddGlobal(m->mod, LLVMTypeOf(init.value), alloc_cstring(permanent_allocator(), name)); + if (e->token.string == "node_camera_info") { + gb_printf_err("HERE!\n"); + gb_printf_err("%s\n", LLVMPrintValueToString(init.value)); + } + + LLVMSetInitializer(g.value, init.value); var.is_initialized = true; if (cc.is_rodata) { diff --git a/src/types.cpp b/src/types.cpp index 814f99eff..62e47259d 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2536,7 +2536,10 @@ gb_internal bool elem_type_can_be_constant(Type *t) { if (t == t_invalid) { return false; } - if (is_type_any(t) || is_type_raw_union(t)) { + if (is_type_any(t)) { + return false; + } + if (is_type_raw_union(t)) { return false; } if (is_type_union(t)) { From 4f442c60453df9e421dad8314fb76231d8bed344 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 09:51:23 +0100 Subject: [PATCH 139/162] Rearrange const union initialization so that it is priority --- src/llvm_backend.cpp | 6 -- src/llvm_backend_const.cpp | 145 +++++++++---------------------------- 2 files changed, 35 insertions(+), 116 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 6a15e11a6..ec9630a47 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3287,12 +3287,6 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { g.value = nullptr; g.value = LLVMAddGlobal(m->mod, LLVMTypeOf(init.value), alloc_cstring(permanent_allocator(), name)); - if (e->token.string == "node_camera_info") { - gb_printf_err("HERE!\n"); - gb_printf_err("%s\n", LLVMPrintValueToString(init.value)); - } - - LLVMSetInitializer(g.value, init.value); var.is_initialized = true; if (cc.is_rodata) { diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index cde610cc8..d27b2012b 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -549,101 +549,6 @@ gb_internal bool lb_is_nested_possibly_constant(Type *ft, Selection const &sel, return lb_is_elem_const(elem, ft); } -gb_internal Slice lb_construct_const_union_flatten_values(lbModule *m, LLVMValueRef variant_value, Type *variant_type, LLVMTypeRef elem) { - LLVMTypeRef llvm_variant_type = lb_type(m, variant_type); - LLVMTypeKind variant_kind = LLVMGetTypeKind(llvm_variant_type); - LLVMTypeKind elem_kind = LLVMGetTypeKind(elem); - - if (is_type_struct(variant_type)) { - Type *st = base_type(variant_type); - GB_ASSERT(st->kind == Type_Struct); - if (st->Struct.fields.count == 1) { - LLVMValueRef f = llvm_const_extract_value(m, variant_value, 0); - return lb_construct_const_union_flatten_values(m, f, st->Struct.fields[0]->type, elem); - } - } else if (is_llvm_type_slice_like(llvm_variant_type)) { - if (lb_sizeof(elem) == build_context.ptr_size) { - LLVMValueRef *elems = temporary_alloc_array(2); - elems[0] = llvm_const_extract_value(m, variant_value, 0); - elems[0] = LLVMConstPtrToInt(elems[0], elem); - - elems[1] = llvm_const_extract_value(m, variant_value, 1); - - return {elems, 2}; - } - } else if (is_type_array_like(variant_type)) { - Type *array_elem = base_array_type(variant_type); - isize array_count = get_array_type_count(variant_type); - Slice array = temporary_slice_make(array_count); - for (isize i = 0; i < array_count; i++) { - LLVMValueRef v = llvm_const_extract_value(m, variant_value, 0); - auto res = lb_construct_const_union_flatten_values(m, v, array_elem, elem); - if (res.count != 1) { - return {}; - } - array[i] = res[0]; - } - return array; - } else if (variant_kind == LLVMIntegerTypeKind) { - if (elem == llvm_variant_type) { - LLVMValueRef *elems = temporary_alloc_array(1); - elems[0] = variant_value; - return {elems, 1}; - } else if (!is_type_different_to_arch_endianness(variant_type)) { - i64 elem_size = lb_sizeof(elem); - i64 variant_size = lb_sizeof(llvm_variant_type); - if (elem_size > variant_size) { - u64 val = LLVMConstIntGetZExtValue(variant_value); - - LLVMValueRef *elems = temporary_alloc_array(1); - elems[0] = LLVMConstInt(elem, val, false); - return {elems, 1}; - } - } - } else if (!is_type_different_to_arch_endianness(variant_type) && - elem_kind == LLVMIntegerTypeKind) { - switch (variant_kind) { - case LLVMHalfTypeKind: - { - LLVMBool loses = false; - f64 res = LLVMConstRealGetDouble(variant_value, &loses); - u16 val = f32_to_f16(cast(f32)res); - - LLVMValueRef *elems = temporary_alloc_array(1); - elems[0] = LLVMConstInt(elem, val, false); - return {elems, 1}; - } - break; - case LLVMFloatTypeKind: - { - LLVMBool loses = false; - f64 res = LLVMConstRealGetDouble(variant_value, &loses); - union { f32 f; u32 i; } val = {}; - val.f = cast(f32)res; - - LLVMValueRef *elems = temporary_alloc_array(1); - elems[0] = LLVMConstInt(elem, val.i, false); - return {elems, 1}; - } - break; - case LLVMDoubleTypeKind: - { - LLVMBool loses = false; - f64 res = LLVMConstRealGetDouble(variant_value, &loses); - union { f64 f; u64 i; } val = {}; - val.f = res; - - LLVMValueRef *elems = temporary_alloc_array(1); - elems[0] = LLVMConstInt(elem, val.i, false); - return {elems, 1}; - } - break; - } - } - - return {}; -} - gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lbConstContext cc, Type *value_type) { if (cc.allow_local) { cc.is_rodata = false; @@ -659,21 +564,6 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb type = core_type(type); value = convert_exact_value_for_type(value, type); - if (value.kind == ExactValue_Typeid) { - return lb_typeid(m, value.value_typeid); - } - - if (value.kind == ExactValue_Invalid) { - return lb_const_nil(m, original_type); - } - - if (value.kind == ExactValue_Compound) { - ast_node(cl, CompoundLit, value.value_compound); - if (cl->elems.count == 0) { - return lb_const_nil(m, original_type); - } - } - bool is_local = cc.allow_local && m->curr_procedure != nullptr; if (is_type_union(type) && is_type_union_constantable(type)) { @@ -718,6 +608,15 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb i64 block_size = bt->Union.variant_block_size; if (are_types_identical(value_type, original_type)) { + if (value.kind == ExactValue_Compound) { + ast_node(cl, CompoundLit, value.value_compound); + if (cl->elems.count == 0) { + return lb_const_nil(m, original_type); + } + } else if (value.kind == ExactValue_Invalid) { + return lb_const_nil(m, original_type); + } + GB_PANIC("%s vs %s", type_to_string(value_type), type_to_string(original_type)); } @@ -774,6 +673,24 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb return res; } + // NOTE(bill): This has to be done AFTER the union stuff + if (value.kind == ExactValue_Invalid) { + return lb_const_nil(m, original_type); + } + + + if (value.kind == ExactValue_Typeid) { + return lb_typeid(m, value.value_typeid); + } + + if (value.kind == ExactValue_Compound) { + ast_node(cl, CompoundLit, value.value_compound); + if (cl->elems.count == 0) { + return lb_const_nil(m, original_type); + } + } + + // GB_ASSERT_MSG(is_type_typed(type), "%s", type_to_string(type)); if (is_type_slice(type)) { @@ -1586,6 +1503,14 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb if (elem_type_can_be_constant(f->type)) { if (sel.index.count == 1) { lbValue value = lb_const_value(m, f->type, tav.value, cc, tav.type); + if (is_type_union(f->type)) { + if (f->token.string == "default_value") { + if (LLVMIsNull(value.value)) { + gb_printf_err("HERE: %s %s\n", type_to_string(f->type), LLVMPrintValueToString(value.value)); + GB_PANIC("GAH"); + } + } + } LLVMTypeRef value_type = LLVMTypeOf(value.value); GB_ASSERT_MSG(lb_sizeof(value_type) == type_size_of(f->type), "%s vs %s", LLVMPrintTypeToString(value_type), type_to_string(f->type)); values[index] = value.value; From 1f2cedcf78907c7cb2d743cc476f11981a06e32b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 09:53:04 +0100 Subject: [PATCH 140/162] Remove debug code --- src/llvm_backend_const.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index d27b2012b..ebafa488e 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -1503,14 +1503,6 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb if (elem_type_can_be_constant(f->type)) { if (sel.index.count == 1) { lbValue value = lb_const_value(m, f->type, tav.value, cc, tav.type); - if (is_type_union(f->type)) { - if (f->token.string == "default_value") { - if (LLVMIsNull(value.value)) { - gb_printf_err("HERE: %s %s\n", type_to_string(f->type), LLVMPrintValueToString(value.value)); - GB_PANIC("GAH"); - } - } - } LLVMTypeRef value_type = LLVMTypeOf(value.value); GB_ASSERT_MSG(lb_sizeof(value_type) == type_size_of(f->type), "%s vs %s", LLVMPrintTypeToString(value_type), type_to_string(f->type)); values[index] = value.value; From 10ba956d6a57cb5b334b4311cda96c6c7f8737db Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 10:28:16 +0100 Subject: [PATCH 141/162] Rudimentary support for some constant `struct #raw_union` --- src/check_expr.cpp | 2 +- src/llvm_backend_const.cpp | 33 +++++++++++++++++++++++++++++++++ src/types.cpp | 18 ++++++++++++++++-- 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 10fec1890..02cd66136 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -9847,7 +9847,7 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * if (t->Struct.is_raw_union) { if (cl->elems.count > 0) { // NOTE: unions cannot be constant - is_constant = false; + is_constant = elem_type_can_be_constant(t); if (cl->elems[0]->kind != Ast_FieldValue) { gbString type_str = type_to_string(type); diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index ebafa488e..782c75cd2 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -1475,6 +1475,39 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb } if (is_type_raw_union(type)) { + if (is_type_raw_union_constantable(type)) { + GB_ASSERT(cl->elems.count == 1); + GB_ASSERT(cl->elems[0]->kind == Ast_FieldValue); + ast_node(fv, FieldValue, cl->elems[0]); + Entity *f = entity_of_node(fv->field); + + TypeAndValue tav = fv->value->tav; + if (tav.value.kind != ExactValue_Invalid) { + lbValue value = lb_const_value(m, f->type, tav.value, cc, f->type); + + LLVMValueRef values[2]; + unsigned value_count = 0; + + values[value_count++] = value.value; + + i64 union_alignment = type_align_of(type); + i64 value_alignment = type_align_of(f->type); + i64 alignment = gb_max(gb_min(value_alignment, union_alignment), 1); + + i64 union_size = type_size_of(type); + i64 value_size = lb_sizeof(LLVMTypeOf(value.value)); + i64 padding = union_size-value_size; + if (padding > 0) { + LLVMTypeRef padding_type = lb_type_padding_filler(m, padding, alignment); + values[value_count++] = LLVMConstNull(padding_type); + } + + LLVMValueRef res = LLVMConstStructInContext(m->ctx, values, value_count, true); + + return {res, original_type}; + } + + } return lb_const_nil(m, original_type); } diff --git a/src/types.cpp b/src/types.cpp index 62e47259d..1fbcd429b 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2531,6 +2531,20 @@ gb_internal bool is_type_union_constantable(Type *type) { return true; } +gb_internal bool is_type_raw_union_constantable(Type *type) { + Type *bt = base_type(type); + GB_ASSERT(bt->kind == Type_Struct); + GB_ASSERT(bt->Struct.is_raw_union); + + for (Entity *f : bt->Struct.fields) { + if (!is_type_constant_type(f->type)) { + return false; + } + } + return true; +} + + gb_internal bool elem_type_can_be_constant(Type *t) { t = base_type(t); if (t == t_invalid) { @@ -2540,7 +2554,7 @@ gb_internal bool elem_type_can_be_constant(Type *t) { return false; } if (is_type_raw_union(t)) { - return false; + return is_type_raw_union_constantable(t); } if (is_type_union(t)) { return is_type_union_constantable(t); @@ -2556,7 +2570,7 @@ gb_internal bool elem_cannot_be_constant(Type *t) { return !is_type_union_constantable(t); } if (is_type_raw_union(t)) { - return true; + return !is_type_raw_union_constantable(t); } return false; } From 0d946268ee38d007791e3158eed07b856817fab9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 10:41:35 +0100 Subject: [PATCH 142/162] Disallow constant access `x.y` on `struct #raw_union` --- src/check_expr.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 02cd66136..a59f145c7 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -5120,7 +5120,11 @@ gb_internal ExactValue get_constant_field_single(CheckerContext *c, ExactValue v } if (cl->elems[0]->kind == Ast_FieldValue) { - if (is_type_struct(node->tav.type)) { + if (is_type_raw_union(node->tav.type)) { + if (success_) *success_ = false; + if (finish_) *finish_ = true; + return empty_exact_value; + } else if (is_type_struct(node->tav.type)) { bool found = false; for (Ast *elem : cl->elems) { if (elem->kind != Ast_FieldValue) { @@ -5834,7 +5838,7 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod switch (entity->kind) { case Entity_Constant: - operand->value = entity->Constant.value; + operand->value = entity->Constant.value; operand->mode = Addressing_Constant; if (operand->value.kind == ExactValue_Procedure) { Entity *proc = strip_entity_wrapping(operand->value.value_procedure); From e511f07d76d738533a6bc4dcf495dca15afc8b13 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 10:45:24 +0100 Subject: [PATCH 143/162] Short circuit for `Union{}` --- src/llvm_backend_const.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 782c75cd2..87e7962d4 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -603,6 +603,17 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb return res; } } else { + if (value_type == nullptr) { + if (value.kind == ExactValue_Compound) { + ast_node(cl, CompoundLit, value.value_compound); + if (cl->elems.count == 0) { + return lb_const_nil(m, original_type); + } + } else if (value.kind == ExactValue_Invalid) { + return lb_const_nil(m, original_type); + } + } + GB_ASSERT_MSG(value_type != nullptr, "%s :: %s", type_to_string(original_type), exact_value_to_string(value)); i64 block_size = bt->Union.variant_block_size; From 89645921e2653b533131b9a292ffd3fb0195e8b5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 11:00:08 +0100 Subject: [PATCH 144/162] Only add packing if the padding is non-zero for a #raw_union constant --- src/llvm_backend_const.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 87e7962d4..193bffe08 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -1513,7 +1513,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb values[value_count++] = LLVMConstNull(padding_type); } - LLVMValueRef res = LLVMConstStructInContext(m->ctx, values, value_count, true); + LLVMValueRef res = LLVMConstStructInContext(m->ctx, values, value_count, /*packed*/padding > 0); return {res, original_type}; } From b711e9296868accd2a1ca889db60dc54ecd46c75 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 12:27:28 +0100 Subject: [PATCH 145/162] Add bit cast --- src/llvm_backend_proc.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 19213e1a3..926401657 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -921,13 +921,20 @@ gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue arg_type != param_type) { LLVMTypeKind arg_kind = LLVMGetTypeKind(arg_type); LLVMTypeKind param_kind = LLVMGetTypeKind(param_type); - if (arg_kind == param_kind && - arg_kind == LLVMPointerTypeKind) { - // NOTE(bill): LLVM's newer `ptr` only type system seems to fail at times - // I don't know why... - args[i] = LLVMBuildPointerCast(p->builder, args[i], param_type, ""); - arg_type = param_type; - continue; + if (arg_kind == param_kind) { + if (arg_kind == LLVMPointerTypeKind) { + // NOTE(bill): LLVM's newer `ptr` only type system seems to fail at times + // I don't know why... + args[i] = LLVMBuildPointerCast(p->builder, args[i], param_type, ""); + arg_type = param_type; + continue; + } else if (arg_kind == LLVMStructTypeKind) { + if (lb_sizeof(arg_type) == lb_sizeof(param_type)) { + args[i] = LLVMBuildBitCast(p->builder, args[i], param_type, ""); + arg_type = param_type; + continue; + } + } } } From 240b2f1819294cc59b48f88ef3344bf77265fec6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 12:54:52 +0100 Subject: [PATCH 146/162] Disable `#raw_union` constants for the time being --- src/llvm_backend_proc.cpp | 6 ------ src/types.cpp | 3 ++- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 926401657..7680c5e76 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -928,12 +928,6 @@ gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue args[i] = LLVMBuildPointerCast(p->builder, args[i], param_type, ""); arg_type = param_type; continue; - } else if (arg_kind == LLVMStructTypeKind) { - if (lb_sizeof(arg_type) == lb_sizeof(param_type)) { - args[i] = LLVMBuildBitCast(p->builder, args[i], param_type, ""); - arg_type = param_type; - continue; - } } } } diff --git a/src/types.cpp b/src/types.cpp index 1fbcd429b..effa8ef64 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2541,7 +2541,8 @@ gb_internal bool is_type_raw_union_constantable(Type *type) { return false; } } - return true; + // return true; + return false; // Disable raw union constants for the time being } From 53f4fc1cbbd58396241264785dc1c8a75798f062 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 14:03:32 +0100 Subject: [PATCH 147/162] Add `-para-poly-diagnostics` --- src/build_settings.cpp | 2 + src/check_expr.cpp | 1 + src/checker.hpp | 2 + src/llvm_backend.cpp | 4 + src/llvm_backend_utility.cpp | 180 +++++++++++++++++++++++++++++++++++ src/main.cpp | 7 ++ 6 files changed, 196 insertions(+) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index a8d74d1da..abf8e6809 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -548,6 +548,8 @@ struct BuildContext { bool ignore_microsoft_magic; bool linker_map_file; + bool para_poly_diagnostics; + bool use_single_module; bool use_separate_modules; bool module_per_file; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index a59f145c7..2a22e5c48 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -587,6 +587,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E d->proc_lit = proc_lit; d->proc_checked_state = ProcCheckedState_Unchecked; d->defer_use_checked = false; + d->para_poly_original = old_decl->entity; Entity *entity = alloc_entity_procedure(nullptr, token, final_proc_type, tags); entity->state.store(EntityState_Resolved); diff --git a/src/checker.hpp b/src/checker.hpp index 9693c3e38..bda7b2746 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -218,6 +218,8 @@ struct DeclInfo { Ast * proc_lit; // Ast_ProcLit Type * gen_proc_type; // Precalculated + Entity * para_poly_original; + bool is_using; bool where_clauses_evaluated; bool foreign_require_results; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index ec9630a47..b47e2788f 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3556,6 +3556,10 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { TIME_SECTION("LLVM Correct Entity Linkage"); lb_correct_entity_linkage(gen); + if (build_context.para_poly_diagnostics) { + lb_do_para_poly_diagnostics(gen); + } + llvm_error = nullptr; defer (LLVMDisposeMessage(llvm_error)); diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index f7807364a..fd3214f24 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2787,3 +2787,183 @@ gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(Ast *expr) { ExactValue value = type_and_value_of_expr(expr).value; return llvm_atomic_ordering_from_odin(value); } + + + +struct lbParaPolyEntry { + Entity *entity; + String canonical_name; + isize count; + isize total_code_size; +}; + +gb_internal isize lb_total_code_size(lbProcedure *p) { + isize instruction_count = 0; + + LLVMBasicBlockRef first = LLVMGetFirstBasicBlock(p->value); + + for (LLVMBasicBlockRef block = first; block != nullptr; block = LLVMGetNextBasicBlock(block)) { + for (LLVMValueRef instr = LLVMGetFirstInstruction(block); instr != nullptr; instr = LLVMGetNextInstruction(instr)) { + instruction_count += 1; + } + } + return instruction_count; + +} + +gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { + PtrMap procs = {}; + map_init(&procs); + defer (map_destroy(&procs)); + + for (auto &entry : gen->modules) { + lbModule *m = entry.value; + for (lbProcedure *p : m->generated_procedures) { + Entity *e = p->entity; + if (e == nullptr) { + continue; + } + if (p->builder == nullptr) { + continue; + } + + DeclInfo *d = e->decl_info; + Entity *para_poly_parent = d->para_poly_original; + if (para_poly_parent == nullptr) { + continue; + } + + lbParaPolyEntry *entry = map_get(&procs, para_poly_parent); + if (entry == nullptr) { + lbParaPolyEntry entry = {}; + entry.entity = para_poly_parent; + entry.count = 0; + + + gbString w = string_canonical_entity_name(permanent_allocator(), entry.entity); + String name = make_string_c(w); + + for (isize i = 0; i < name.len; i++) { + String s = substring(name, i, name.len); + if (string_starts_with(s, str_lit(":proc"))) { + name = substring(name, 0, i); + break; + } + } + + entry.canonical_name = name; + + map_set(&procs, para_poly_parent, entry); + } + entry = map_get(&procs, para_poly_parent); + GB_ASSERT(entry != nullptr); + entry->count += 1; + entry->total_code_size += lb_total_code_size(p); + } + } + + + auto entries = array_make(heap_allocator(), 0, procs.count); + defer (array_free(&entries)); + + for (auto &entry : procs) { + array_add(&entries, entry.value); + } + + array_sort(entries, [](void const *a, void const *b) -> int { + lbParaPolyEntry *x = cast(lbParaPolyEntry *)a; + lbParaPolyEntry *y = cast(lbParaPolyEntry *)b; + if (x->total_code_size > y->total_code_size) { + return -1; + } + if (x->total_code_size < y->total_code_size) { + return +1; + } + return string_compare(x->canonical_name, y->canonical_name); + }); + + + gb_printf("Parametric Polymorphic Procedure Diagnostics\n"); + gb_printf("------------------------------------------------------------------------------------------\n"); + + gb_printf("Sorted by Total Instruction Count Descending (Top 100)\n\n"); + gb_printf("Total Instruction Count | Instantiation Count | Average Instruction Count | Procedure Name\n"); + + isize max_count = 100; + for (auto &entry : entries) { + isize code_size = entry.total_code_size; + isize count = entry.count; + String name = entry.canonical_name; + + f64 average = cast(f64)code_size / cast(f64)gb_max(count, 1); + + gb_printf("%23td | %19d | %25.2f | %.*s\n", code_size, count, average, LIT(name)); + if (max_count-- <= 0) { + break; + } + } + + gb_printf("------------------------------------------------------------------------------------------\n"); + + array_sort(entries, [](void const *a, void const *b) -> int { + lbParaPolyEntry *x = cast(lbParaPolyEntry *)a; + lbParaPolyEntry *y = cast(lbParaPolyEntry *)b; + if (x->count > y->count) { + return -1; + } + if (x->count < y->count) { + return +1; + } + + return string_compare(x->canonical_name, y->canonical_name); + }); + + gb_printf("Sorted by Total Instantiation Count Descending (Top 100)\n\n"); + gb_printf("Instantiation Count | Total Instruction Count | Average Instruction Count | Procedure Name\n"); + + max_count = 100; + for (auto &entry : entries) { + isize code_size = entry.total_code_size; + isize count = entry.count; + String name = entry.canonical_name; + + + f64 average = cast(f64)code_size / cast(f64)gb_max(count, 1); + + gb_printf("%19d | %23td | %25.2f | %.*s\n", count, code_size, average, LIT(name)); + if (max_count-- <= 0) { + break; + } + } + + gb_printf("------------------------------------------------------------------------------------------\n"); + + + array_sort(entries, [](void const *a, void const *b) -> int { + lbParaPolyEntry *x = cast(lbParaPolyEntry *)a; + lbParaPolyEntry *y = cast(lbParaPolyEntry *)b; + if (x->count < y->count) { + return -1; + } + if (x->count > y->count) { + return +1; + } + + return string_compare(x->canonical_name, y->canonical_name); + }); + + gb_printf("Single Instanced Parametric Polymorphic Procedures\n\n"); + gb_printf("Instruction Count | Procedure Name\n"); + for (auto &entry : entries) { + isize code_size = entry.total_code_size; + isize count = entry.count; + String name = entry.canonical_name; + if (count != 1) { + break; + } + + gb_printf("%17td | %.*s\n", code_size, LIT(name)); + } + + gb_printf("------------------------------------------------------------------------------------------\n"); +} diff --git a/src/main.cpp b/src/main.cpp index acc4773c0..707b85232 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -394,6 +394,8 @@ enum BuildFlagKind { BuildFlag_IntegerDivisionByZero, + BuildFlag_ParaPolyDiagnostics, + // internal use only BuildFlag_InternalFastISel, BuildFlag_InternalIgnoreLazy, @@ -619,6 +621,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_IntegerDivisionByZero, str_lit("integer-division-by-zero"), BuildFlagParam_String, Command__does_check); + add_flag(&build_flags, BuildFlag_ParaPolyDiagnostics, str_lit("para-poly-diagnostics"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_InternalFastISel, str_lit("internal-fast-isel"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalIgnoreLazy, str_lit("internal-ignore-lazy"), BuildFlagParam_None, Command_all); @@ -1562,6 +1565,10 @@ gb_internal bool parse_build_flags(Array args) { } break; + case BuildFlag_ParaPolyDiagnostics: + build_context.para_poly_diagnostics = true; + break; + case BuildFlag_InternalFastISel: build_context.fast_isel = true; break; From 1fc1d8e451d5346d35c4a6618a24414e1a290edd Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 14:32:21 +0100 Subject: [PATCH 148/162] Change sort for single instanced procedures --- src/llvm_backend_utility.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index fd3214f24..7fe6b1458 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2949,6 +2949,13 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { return +1; } + if (x->total_code_size > y->total_code_size) { + return -1; + } + if (x->total_code_size < y->total_code_size) { + return +1; + } + return string_compare(x->canonical_name, y->canonical_name); }); From 9b4c0ea4920ea70b3e9206979aa7fd36608c4837 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 15:12:55 +0100 Subject: [PATCH 149/162] Type erase the internals of `runtime.copy_*` --- base/runtime/core_builtin.odin | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 0f72e1336..d63d9e72a 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -70,31 +70,33 @@ init_global_temporary_allocator :: proc(size: int, backup_allocator := context.a } +@(require_results) +copy_slice_raw :: proc "contextless" (dst, src: rawptr, dst_len, src_len, elem_size: int) -> int { + n := min(dst_len, src_len) + if n > 0 { + intrinsics.mem_copy(dst, src, n*elem_size) + } + return n +} + // `copy_slice` is a built-in procedure that copies elements from a source slice `src` to a destination slice `dst`. // The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum // of len(src) and len(dst). // // Prefer the procedure group `copy`. @builtin -copy_slice :: proc "contextless" (dst, src: $T/[]$E) -> int { - n := min(len(dst), len(src)) - if n > 0 { - intrinsics.mem_copy(raw_data(dst), raw_data(src), n*size_of(E)) - } - return n +copy_slice :: #force_inline proc "contextless" (dst, src: $T/[]$E) -> int { + return copy_slice_raw(raw_data(dst), raw_data(src), len(dst), len(src), size_of(E)) } + // `copy_from_string` is a built-in procedure that copies elements from a source string `src` to a destination slice `dst`. // The source and destination may overlap. Copy returns the number of elements copied, which will be the minimum // of len(src) and len(dst). // // Prefer the procedure group `copy`. @builtin -copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int { - n := min(len(dst), len(src)) - if n > 0 { - intrinsics.mem_copy(raw_data(dst), raw_data(src), n) - } - return n +copy_from_string :: #force_inline proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int { + return copy_slice_raw(raw_data(dst), raw_data(src), len(dst), len(src), 1) } // `copy_from_string16` is a built-in procedure that copies elements from a source string `src` to a destination slice `dst`. @@ -103,12 +105,8 @@ copy_from_string :: proc "contextless" (dst: $T/[]$E/u8, src: $S/string) -> int // // Prefer the procedure group `copy`. @builtin -copy_from_string16 :: proc "contextless" (dst: $T/[]$E/u16, src: $S/string16) -> int { - n := min(len(dst), len(src)) - if n > 0 { - intrinsics.mem_copy(raw_data(dst), raw_data(src), n*size_of(u16)) - } - return n +copy_from_string16 :: #force_inline proc "contextless" (dst: $T/[]$E/u16, src: $S/string16) -> int { + return copy_slice_raw(raw_data(dst), raw_data(src), len(dst), len(src), 2) } // `copy` is a built-in procedure that copies elements from a source slice/string `src` to a destination slice `dst`. From 11712627cbb95e3ae15d6a8e2c5863e192caa68a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 16:14:55 +0100 Subject: [PATCH 150/162] Add module stuff to `-para-poly-diagnostics` --- src/llvm_backend.cpp | 37 ++++++++---- src/llvm_backend_utility.cpp | 112 +++++++++++++++++++++++++++++++---- 2 files changed, 126 insertions(+), 23 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index b47e2788f..bbfd91d30 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3245,10 +3245,6 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { continue; } - // if (!ptr_set_exists(min_dep_set, e)) { - // continue; - // } - DeclInfo *decl = decl_info_of_entity(e); if (decl == nullptr) { continue; @@ -3259,8 +3255,16 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { bool is_foreign = e->Variable.is_foreign; bool is_export = e->Variable.is_export; + lbModule *default_module = &gen->default_module; + + lbModule *m = default_module; + lbModule *e_module = lb_module_of_entity(gen, e, default_module); + + bool const split_globals_across_modules = false; + if (split_globals_across_modules) { + m = e_module; + } - lbModule *m = &gen->default_module; String name = lb_get_entity_name(m, e); lbGlobalVariable var = {}; @@ -3361,15 +3365,26 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { } } - g.value = LLVMConstPointerCast(g.value, lb_type(m, alloc_type_pointer(e->type))); + if (default_module == m) { + g.value = LLVMConstPointerCast(g.value, lb_type(m, alloc_type_pointer(e->type))); - var.var = g; - array_add(&global_variables, var); + var.var = g; + array_add(&global_variables, var); + } else { + lbValue local_g = {}; + local_g.type = alloc_type_pointer(e->type); + local_g.value = LLVMAddGlobal(default_module->mod, lb_type(default_module, e->type), alloc_cstring(permanent_allocator(), name)); + LLVMSetLinkage(local_g.value, LLVMExternalLinkage); + + var.var = local_g; + array_add(&global_variables, var); + + lb_add_entity(default_module, e, local_g); + lb_add_member(default_module, name, local_g); + } lb_add_entity(m, e, g); lb_add_member(m, name, g); - - } if (build_context.ODIN_DEBUG) { @@ -3557,7 +3572,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { lb_correct_entity_linkage(gen); if (build_context.para_poly_diagnostics) { - lb_do_para_poly_diagnostics(gen); + lb_do_code_gen_diagnostics(gen); } llvm_error = nullptr; diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 7fe6b1458..244ad6d73 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2790,7 +2790,7 @@ gb_internal LLVMAtomicOrdering llvm_atomic_ordering_from_odin(Ast *expr) { -struct lbParaPolyEntry { +struct lbDiagParaPolyEntry { Entity *entity; String canonical_name; isize count; @@ -2812,7 +2812,7 @@ gb_internal isize lb_total_code_size(lbProcedure *p) { } gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { - PtrMap procs = {}; + PtrMap procs = {}; map_init(&procs); defer (map_destroy(&procs)); @@ -2833,9 +2833,9 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { continue; } - lbParaPolyEntry *entry = map_get(&procs, para_poly_parent); + lbDiagParaPolyEntry *entry = map_get(&procs, para_poly_parent); if (entry == nullptr) { - lbParaPolyEntry entry = {}; + lbDiagParaPolyEntry entry = {}; entry.entity = para_poly_parent; entry.count = 0; @@ -2863,7 +2863,7 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { } - auto entries = array_make(heap_allocator(), 0, procs.count); + auto entries = array_make(heap_allocator(), 0, procs.count); defer (array_free(&entries)); for (auto &entry : procs) { @@ -2871,8 +2871,8 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { } array_sort(entries, [](void const *a, void const *b) -> int { - lbParaPolyEntry *x = cast(lbParaPolyEntry *)a; - lbParaPolyEntry *y = cast(lbParaPolyEntry *)b; + lbDiagParaPolyEntry *x = cast(lbDiagParaPolyEntry *)a; + lbDiagParaPolyEntry *y = cast(lbDiagParaPolyEntry *)b; if (x->total_code_size > y->total_code_size) { return -1; } @@ -2906,8 +2906,8 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { gb_printf("------------------------------------------------------------------------------------------\n"); array_sort(entries, [](void const *a, void const *b) -> int { - lbParaPolyEntry *x = cast(lbParaPolyEntry *)a; - lbParaPolyEntry *y = cast(lbParaPolyEntry *)b; + lbDiagParaPolyEntry *x = cast(lbDiagParaPolyEntry *)a; + lbDiagParaPolyEntry *y = cast(lbDiagParaPolyEntry *)b; if (x->count > y->count) { return -1; } @@ -2940,8 +2940,8 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { array_sort(entries, [](void const *a, void const *b) -> int { - lbParaPolyEntry *x = cast(lbParaPolyEntry *)a; - lbParaPolyEntry *y = cast(lbParaPolyEntry *)b; + lbDiagParaPolyEntry *x = cast(lbDiagParaPolyEntry *)a; + lbDiagParaPolyEntry *y = cast(lbDiagParaPolyEntry *)b; if (x->count < y->count) { return -1; } @@ -2972,5 +2972,93 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { gb_printf("%17td | %.*s\n", code_size, LIT(name)); } - gb_printf("------------------------------------------------------------------------------------------\n"); } + +struct lbDiagModuleEntry { + lbModule *m; + String name; + isize global_internal_count; + isize global_external_count; + isize proc_internal_count; + isize proc_external_count; + isize total_instruction_count; +}; + +gb_internal void lb_do_module_diagnostics(lbGenerator *gen) { + Array modules = {}; + array_init(&modules, heap_allocator()); + defer (array_free(&modules)); + + for (auto &entry : gen->modules) { + lbModule *m = entry.value; + + { + lbDiagModuleEntry entry = {}; + entry.m = m; + entry.name = make_string_c(m->module_name); + array_add(&modules, entry); + } + lbDiagModuleEntry &entry = modules[modules.count-1]; + + for (LLVMValueRef p = LLVMGetFirstFunction(m->mod); p != nullptr; p = LLVMGetNextFunction(p)) { + LLVMBasicBlockRef block = LLVMGetFirstBasicBlock(p); + if (block == nullptr) { + entry.proc_external_count += 1; + } else { + entry.proc_internal_count += 1; + + for (; block != nullptr; block = LLVMGetNextBasicBlock(block)) { + for (LLVMValueRef i = LLVMGetFirstInstruction(block); i != nullptr; i = LLVMGetNextInstruction(i)) { + entry.total_instruction_count += 1; + } + } + } + } + + for (LLVMValueRef g = LLVMGetFirstGlobal(m->mod); g != nullptr; g = LLVMGetNextGlobal(g)) { + LLVMLinkage linkage = LLVMGetLinkage(g); + if (linkage == LLVMExternalLinkage) { + entry.global_external_count += 1; + } else { + entry.global_internal_count += 1; + } + } + } + + array_sort(modules, [](void const *a, void const *b) -> int { + lbDiagModuleEntry *x = cast(lbDiagModuleEntry *)a; + lbDiagModuleEntry *y = cast(lbDiagModuleEntry *)b; + + if (x->total_instruction_count > y->total_instruction_count) { + return -1; + } + if (x->total_instruction_count < y->total_instruction_count) { + return +1; + } + + return string_compare(x->name, y->name); + }); + + gb_printf("Module Diagnostics\n\n"); + gb_printf("Total Instructions | Global Internals | Global Externals | Proc Internals | Proc Externals | Module Name\n"); + for (auto &entry : modules) { + gb_printf("%18td | %16td | %16td | %14td | %14d | %s \n", + entry.total_instruction_count, + entry.global_internal_count, + entry.global_external_count, + entry.proc_internal_count, + entry.proc_external_count, + entry.m->module_name); + } + + +} + +gb_internal void lb_do_code_gen_diagnostics(lbGenerator *gen) { + lb_do_para_poly_diagnostics(gen); + gb_printf("------------------------------------------------------------------------------------------\n"); + gb_printf("------------------------------------------------------------------------------------------\n\n"); + lb_do_module_diagnostics(gen); + gb_printf("------------------------------------------------------------------------------------------\n"); + gb_printf("------------------------------------------------------------------------------------------\n\n"); +} \ No newline at end of file From 9e8be055c1152bd42f533f544308355de87a361e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 16:16:19 +0100 Subject: [PATCH 151/162] Rename to `-build-diagnostics` --- src/build_settings.cpp | 2 +- src/llvm_backend.cpp | 4 ++-- src/llvm_backend_utility.cpp | 2 +- src/main.cpp | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index abf8e6809..53953600e 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -548,7 +548,7 @@ struct BuildContext { bool ignore_microsoft_magic; bool linker_map_file; - bool para_poly_diagnostics; + bool build_diagnostics; bool use_single_module; bool use_separate_modules; diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index bbfd91d30..ab0811085 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3571,8 +3571,8 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { TIME_SECTION("LLVM Correct Entity Linkage"); lb_correct_entity_linkage(gen); - if (build_context.para_poly_diagnostics) { - lb_do_code_gen_diagnostics(gen); + if (build_context.build_diagnostics) { + lb_do_build_diagnostics(gen); } llvm_error = nullptr; diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 244ad6d73..fa8f85d0f 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -3054,7 +3054,7 @@ gb_internal void lb_do_module_diagnostics(lbGenerator *gen) { } -gb_internal void lb_do_code_gen_diagnostics(lbGenerator *gen) { +gb_internal void lb_do_build_diagnostics(lbGenerator *gen) { lb_do_para_poly_diagnostics(gen); gb_printf("------------------------------------------------------------------------------------------\n"); gb_printf("------------------------------------------------------------------------------------------\n\n"); diff --git a/src/main.cpp b/src/main.cpp index 707b85232..6ad20cf26 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -394,7 +394,7 @@ enum BuildFlagKind { BuildFlag_IntegerDivisionByZero, - BuildFlag_ParaPolyDiagnostics, + BuildFlag_BuildDiagnostics, // internal use only BuildFlag_InternalFastISel, @@ -621,7 +621,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_IntegerDivisionByZero, str_lit("integer-division-by-zero"), BuildFlagParam_String, Command__does_check); - add_flag(&build_flags, BuildFlag_ParaPolyDiagnostics, str_lit("para-poly-diagnostics"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_BuildDiagnostics, str_lit("build-diagnostics"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_InternalFastISel, str_lit("internal-fast-isel"), BuildFlagParam_None, Command_all); add_flag(&build_flags, BuildFlag_InternalIgnoreLazy, str_lit("internal-ignore-lazy"), BuildFlagParam_None, Command_all); @@ -1565,8 +1565,8 @@ gb_internal bool parse_build_flags(Array args) { } break; - case BuildFlag_ParaPolyDiagnostics: - build_context.para_poly_diagnostics = true; + case BuildFlag_BuildDiagnostics: + build_context.build_diagnostics = true; break; case BuildFlag_InternalFastISel: From 34d040cef18635b95f5bc13bef793c35ff0647ed Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 29 Sep 2025 16:51:38 +0100 Subject: [PATCH 152/162] Correct format strings --- src/llvm_backend_general.cpp | 29 ++++++++++++++++++++++++++--- src/llvm_backend_utility.cpp | 26 ++++++++++++++++++++------ 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 123af51f5..39cf70a6a 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -186,9 +186,32 @@ gb_internal bool lb_init_generator(lbGenerator *gen, Checker *c) { lb_init_module(pm, do_threading); } - if (pkg->kind == Package_Runtime) { - // allow this to be per file - } else if (!module_per_file) { + bool allow_for_per_file = pkg->kind == Package_Runtime || module_per_file; + + #if 0 + if (!allow_for_per_file) { + if (pkg->files.count >= 20) { + isize proc_count = 0; + for (Entity *e : gen->info->entities) { + if (e->kind != Entity_Procedure) { + continue; + } + if (e->Procedure.is_foreign) { + continue; + } + if (e->pkg == pkg) { + proc_count += 1; + } + } + + if (proc_count >= 300) { + allow_for_per_file = true; + } + } + } + #endif + + if (!allow_for_per_file) { continue; } // NOTE(bill): Probably per file is not a good idea, so leave this for later diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index fa8f85d0f..155c55675 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2897,7 +2897,7 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { f64 average = cast(f64)code_size / cast(f64)gb_max(count, 1); - gb_printf("%23td | %19d | %25.2f | %.*s\n", code_size, count, average, LIT(name)); + gb_printf("%23td | %19td | %25.2f | %.*s\n", code_size, count, average, LIT(name)); if (max_count-- <= 0) { break; } @@ -2930,7 +2930,7 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { f64 average = cast(f64)code_size / cast(f64)gb_max(count, 1); - gb_printf("%19d | %23td | %25.2f | %.*s\n", count, code_size, average, LIT(name)); + gb_printf("%19td | %23td | %25.2f | %.*s\n", count, code_size, average, LIT(name)); if (max_count-- <= 0) { break; } @@ -2989,8 +2989,8 @@ gb_internal void lb_do_module_diagnostics(lbGenerator *gen) { array_init(&modules, heap_allocator()); defer (array_free(&modules)); - for (auto &entry : gen->modules) { - lbModule *m = entry.value; + for (auto &em : gen->modules) { + lbModule *m = em.value; { lbDiagModuleEntry entry = {}; @@ -3040,14 +3040,28 @@ gb_internal void lb_do_module_diagnostics(lbGenerator *gen) { }); gb_printf("Module Diagnostics\n\n"); - gb_printf("Total Instructions | Global Internals | Global Externals | Proc Internals | Proc Externals | Module Name\n"); + gb_printf("Total Instructions | Global Internals | Global Externals | Proc Internals | Proc Externals | Files | Instructions/File | Instructions/Proc | Module Name\n"); + gb_printf("-------------------+------------------+------------------+----------------+----------------+-------+-------------------+-------------------+------------\n"); for (auto &entry : modules) { - gb_printf("%18td | %16td | %16td | %14td | %14d | %s \n", + isize file_count = 1; + if (entry.m->file != nullptr) { + file_count = 1; + } else if (entry.m->pkg) { + file_count = entry.m->pkg->files.count; + } + + f64 instructions_per_file = cast(f64)entry.total_instruction_count / gb_max(1.0, cast(f64)file_count); + f64 instructions_per_proc = cast(f64)entry.total_instruction_count / gb_max(1.0, cast(f64)entry.proc_internal_count); + + gb_printf("%18td | %16td | %16td | %14td | %14td | %5td | %17.1f | %17.1f | %s \n", entry.total_instruction_count, entry.global_internal_count, entry.global_external_count, entry.proc_internal_count, entry.proc_external_count, + file_count, + instructions_per_file, + instructions_per_proc, entry.m->module_name); } From 24daa4427c75ea0f2ea0c7706b20849ac297c6fc Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Mon, 29 Sep 2025 19:54:53 -0400 Subject: [PATCH 153/162] Fix various foreign signatures --- core/c/libc/stdio.odin | 2 +- core/sys/darwin/CoreFoundation/CFString.odin | 6 ++++-- core/sys/posix/arpa_inet.odin | 1 - core/sys/posix/pthread.odin | 4 ++-- core/sys/posix/sys_mman.odin | 7 ++++++- src/check_decl.cpp | 6 ++++++ 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index 854a98637..aa92e4a6b 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -275,7 +275,7 @@ foreign libc { // 7.21.7 Character input/output functions fgetc :: proc(stream: ^FILE) -> int --- fgets :: proc(s: [^]char, n: int, stream: ^FILE) -> [^]char --- - fputc :: proc(s: cstring, stream: ^FILE) -> int --- + fputc :: proc(s: c.int, stream: ^FILE) -> int --- getc :: proc(stream: ^FILE) -> int --- getchar :: proc() -> int --- putc :: proc(c: int, stream: ^FILE) -> int --- diff --git a/core/sys/darwin/CoreFoundation/CFString.odin b/core/sys/darwin/CoreFoundation/CFString.odin index 24485a494..d245ba793 100644 --- a/core/sys/darwin/CoreFoundation/CFString.odin +++ b/core/sys/darwin/CoreFoundation/CFString.odin @@ -1,10 +1,12 @@ package CoreFoundation +import "core:c" + foreign import CoreFoundation "system:CoreFoundation.framework" String :: distinct TypeRef // same as CFStringRef -StringEncoding :: distinct u32 +StringEncoding :: distinct c.long StringBuiltInEncodings :: enum StringEncoding { MacRoman = 0, @@ -171,7 +173,7 @@ foreign CoreFoundation { // Fetches a range of the characters from a string into a byte buffer after converting the characters to a specified encoding. StringGetBytes :: proc(thestring: String, range: Range, encoding: StringEncoding, lossByte: u8, isExternalRepresentation: b8, buffer: [^]byte, maxBufLen: Index, usedBufLen: ^Index) -> Index --- - StringIsEncodingAvailable :: proc(encoding: StringEncoding) -> bool --- + StringIsEncodingAvailable :: proc(encoding: StringEncoding) -> b8 --- @(link_name = "__CFStringMakeConstantString") StringMakeConstantString :: proc "c" (#const c: cstring) -> String --- diff --git a/core/sys/posix/arpa_inet.odin b/core/sys/posix/arpa_inet.odin index 6edb9e535..70b12678c 100644 --- a/core/sys/posix/arpa_inet.odin +++ b/core/sys/posix/arpa_inet.odin @@ -50,7 +50,6 @@ foreign lib { af: AF, // INET or INET6 src: cstring, dst: rawptr, // either ^in_addr or ^in_addr6 - size: socklen_t, // size_of(dst^) ) -> pton_result --- } diff --git a/core/sys/posix/pthread.odin b/core/sys/posix/pthread.odin index c7255baa3..733725e2c 100644 --- a/core/sys/posix/pthread.odin +++ b/core/sys/posix/pthread.odin @@ -124,7 +124,7 @@ foreign lib { [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_attr_getscope.html ]] */ - pthread_attr_setscope :: proc(attr: ^pthread_attr_t, contentionscope: ^Thread_Scope) -> Errno --- + pthread_attr_setscope :: proc(attr: ^pthread_attr_t, contentionscope: Thread_Scope) -> Errno --- /* Get the area of storage to be used for the created thread's stack. @@ -400,7 +400,7 @@ when ODIN_OS == .Darwin { PTHREAD_SCOPE_PROCESS :: 2 PTHREAD_SCOPE_SYSTEM :: 1 - pthread_t :: distinct u64 + pthread_t :: distinct rawptr pthread_attr_t :: struct { __sig: c.long, diff --git a/core/sys/posix/sys_mman.odin b/core/sys/posix/sys_mman.odin index 2d51083dc..1bf3615dc 100644 --- a/core/sys/posix/sys_mman.odin +++ b/core/sys/posix/sys_mman.odin @@ -92,7 +92,12 @@ foreign lib { [[ More; https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html ]] */ - shm_open :: proc(name: cstring, oflag: O_Flags, mode: mode_t) -> FD --- + when ODIN_OS == .Darwin { + // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/shm_open.2.html + shm_open :: proc(name: cstring, oflag: O_Flags, #c_vararg args: ..any) -> FD --- + } else { + shm_open :: proc(name: cstring, oflag: O_Flags, mode: mode_t) -> FD --- + } /* Removes a shared memory object. diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 842f8653c..f7df73953 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -851,6 +851,12 @@ gb_internal bool signature_parameter_similar_enough(Type *x, Type *y) { } } + Type *x_base = base_type(x); + Type *y_base = base_type(y); + if (x_base->kind == y_base->kind && x_base->kind == Type_Struct) { + return type_size_of(x_base) == type_size_of(y_base) && type_align_of(x_base) == type_align_of(y_base); + } + return are_types_identical(x, y); } From c23d30f050ca79bf9bf8f642ae5def3c032c9aa0 Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Mon, 29 Sep 2025 19:56:10 -0400 Subject: [PATCH 154/162] Fix printf format --- src/llvm_backend_utility.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 7fe6b1458..dbd515315 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2897,7 +2897,7 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { f64 average = cast(f64)code_size / cast(f64)gb_max(count, 1); - gb_printf("%23td | %19d | %25.2f | %.*s\n", code_size, count, average, LIT(name)); + gb_printf("%23td | %19td | %25.2f | %.*s\n", code_size, count, average, LIT(name)); if (max_count-- <= 0) { break; } @@ -2930,7 +2930,7 @@ gb_internal void lb_do_para_poly_diagnostics(lbGenerator *gen) { f64 average = cast(f64)code_size / cast(f64)gb_max(count, 1); - gb_printf("%19d | %23td | %25.2f | %.*s\n", count, code_size, average, LIT(name)); + gb_printf("%19td | %23td | %25.2f | %.*s\n", count, code_size, average, LIT(name)); if (max_count-- <= 0) { break; } From 0fdac0bd8cdd9186e098d0fe4f8873b588a8d00e Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Mon, 29 Sep 2025 20:29:34 -0400 Subject: [PATCH 155/162] Fix test inet_pton call in test_arpa_inet --- tests/core/sys/posix/posix.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/sys/posix/posix.odin b/tests/core/sys/posix/posix.odin index 4564e23d2..68a6a87cd 100644 --- a/tests/core/sys/posix/posix.odin +++ b/tests/core/sys/posix/posix.odin @@ -21,7 +21,7 @@ test_arpa_inet :: proc(t: ^testing.T) { dst: [posix.INET6_ADDRSTRLEN]byte } - res := posix.inet_pton(af, src, &addr, size_of(addr)) + res := posix.inet_pton(af, src, &addr) testing.expect_value(t, res, expect, loc) if expect == .SUCCESS { From 51a8660d521040e3c15842fa6e5a74c78b2cf7b5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 30 Sep 2025 10:24:20 +0100 Subject: [PATCH 156/162] Disallow dynamic-literals withint procedure scopes where `context` is not defined --- src/check_expr.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 2a22e5c48..c1861652a 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -9719,7 +9719,10 @@ gb_internal bool is_expr_inferred_fixed_array(Ast *type_expr) { } gb_internal bool check_for_dynamic_literals(CheckerContext *c, Ast *node, AstCompoundLit *cl) { - if (cl->elems.count > 0 && (check_feature_flags(c, node) & OptInFeatureFlag_DynamicLiterals) == 0 && !build_context.dynamic_literals) { + if (cl->elems.count == 0) { + return false; + } + if ((check_feature_flags(c, node) & OptInFeatureFlag_DynamicLiterals) == 0 && !build_context.dynamic_literals) { ERROR_BLOCK(); error(node, "Compound literals of dynamic types are disabled by default"); error_line("\tSuggestion: If you want to enable them for this specific file, add '#+feature dynamic-literals' at the top of the file\n"); @@ -9730,9 +9733,13 @@ gb_internal bool check_for_dynamic_literals(CheckerContext *c, Ast *node, AstCom error_line("\tWarning: As '-default-to-panic-allocator' has been set, the dynamic compound literal may not be initialized as expected\n"); } return false; + } else if (c->curr_proc_decl != nullptr && c->curr_proc_calling_convention != ProcCC_Odin) { + if (c->scope != nullptr && (c->scope->flags & ScopeFlag_ContextDefined) == 0) { + error(node, "Compound literals of dynamic types require a 'context' to defined"); + } } - return cl->elems.count > 0; + return true; } gb_internal IntegerDivisionByZeroKind check_for_integer_division_by_zero(CheckerContext *c, Ast *node) { From 668df4a571cdc376a6a13992364f6debf09f0476 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 30 Sep 2025 10:47:31 +0100 Subject: [PATCH 157/162] Improve `signature_parameter_similar_enough` for structs --- src/check_decl.cpp | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index f7df73953..779dd6c12 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -853,10 +853,40 @@ gb_internal bool signature_parameter_similar_enough(Type *x, Type *y) { Type *x_base = base_type(x); Type *y_base = base_type(y); - if (x_base->kind == y_base->kind && x_base->kind == Type_Struct) { - return type_size_of(x_base) == type_size_of(y_base) && type_align_of(x_base) == type_align_of(y_base); + if (x_base->kind == y_base->kind && + x_base->kind == Type_Struct) { + i64 xs = type_size_of(x_base); + i64 ys = type_size_of(y_base); + + i64 xa = type_align_of(x_base); + i64 ya = type_align_of(y_base); + + + if (x_base->Struct.is_raw_union == y_base->Struct.is_raw_union && + xs == ys && xa == ya) { + if (xs > 16) { + // @@ABI NOTE(bill): Just allow anything over 16-bytes to be allowed, because on all current ABIs + // it will be passed by point + // NOTE(bill): this must be changed when ABI changes + return true; + } + if (x->Struct.fields.count == y->Struct.fields.count) { + for (isize i = 0; i < x->Struct.fields.count; i++) { + Entity *a = x->Struct.fields[i]; + Entity *b = y->Struct.fields[i]; + bool similar = signature_parameter_similar_enough(a->type, b->type); + if (!similar) { + // NOTE(bill): If the fields are not similar enough, then stop. + goto end; + } + } + } + // HACK NOTE(bill): Allow this for the time begin until it actually becomes a practical problem + return true; + } } +end:; return are_types_identical(x, y); } From 4945168e6db97516e9af65a17aec83e3297f0f90 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 30 Sep 2025 10:48:05 +0100 Subject: [PATCH 158/162] Short circuit for `#raw_union` in `signature_parameter_similar_enough` --- src/check_decl.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 779dd6c12..528ff8abe 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -870,6 +870,9 @@ gb_internal bool signature_parameter_similar_enough(Type *x, Type *y) { // NOTE(bill): this must be changed when ABI changes return true; } + if (x_base->Struct.is_raw_union) { + return true; + } if (x->Struct.fields.count == y->Struct.fields.count) { for (isize i = 0; i < x->Struct.fields.count; i++) { Entity *a = x->Struct.fields[i]; From a769e341cbb56e1ed018a3581f22c52945f7190a Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Tue, 30 Sep 2025 11:49:14 -0400 Subject: [PATCH 159/162] Preempt field checking on `signature_parameter_similar_enough` with a type ptr equality check --- src/check_decl.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 528ff8abe..b61c23fa7 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -853,12 +853,17 @@ gb_internal bool signature_parameter_similar_enough(Type *x, Type *y) { Type *x_base = base_type(x); Type *y_base = base_type(y); + + if (x_base == y_base) { + return true; + } + if (x_base->kind == y_base->kind && x_base->kind == Type_Struct) { - i64 xs = type_size_of(x_base); + i64 xs = type_size_of(x_base); i64 ys = type_size_of(y_base); - i64 xa = type_align_of(x_base); + i64 xa = type_align_of(x_base); i64 ya = type_align_of(y_base); From 7eb26d8eac012c3a5162bb50b81b50f907a40ff9 Mon Sep 17 00:00:00 2001 From: samwega Date: Thu, 2 Oct 2025 15:21:44 +0300 Subject: [PATCH 160/162] feat: added rtoi & utoi procs for converting a rune and a u8 character respectively to int --- core/strconv/strconv.odin | 68 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index c1ef31cd7..885ffa1a9 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -1656,6 +1656,74 @@ write_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> stri return string(generic_ftoa(buf, f, fmt, prec, bit_size)) } /* +Casts a rune to int + +**Input:** + +- r: The input rune to find the integer value of + +Example: + + import "core:strconv" + rune_to_int :: proc () { + my_int, ok := strconv.rtoi('5') + if ok { + assert(typeid_of(type_of(my_int)) == int) + fmt.println(my_int) + } + } + +Output: + + 5 + +**Returns:** + +- value: the integer value of the given rune +- ok: ok=false if input not a rune or not numeric. +*/ +rtoi :: proc(r: rune) -> (value: int, ok: bool) #optional_ok { + if '0' <= r && r <= '9' { + return int(r - '0'), true + } + return -1, false +} +/* +Casts one u8 character to int + +**Input:** + +- u: The input u8 character to find the integer value of + +Example: + + import "core:strconv" + u8_to_int :: proc () { + my_u8 := u8('8') + my_int, ok := strconv.utoi(my_u8) + if ok { + assert(typeid_of(type_of(my_int)) == int) + fmt.println(my_int) + } + } + +Output: + + 8 + + +**Returns:** + +- value: the integer value of the given u8 character +- ok: ok=false if input not a u8 character or not numeric. +*/ +utoi :: proc(u: u8) -> (value: int, ok: bool) #optional_ok { + if '0' <= u && u <= '9' { + return int(u - '0'), true + } + return -1, false +} +/* Writes a quoted string representation of the input string to a given byte slice and returns the result as a string **Inputs** From 84e8b8d1add89149e55c46ee7cbf44d750553d92 Mon Sep 17 00:00:00 2001 From: samwega Date: Thu, 2 Oct 2025 15:45:21 +0300 Subject: [PATCH 161/162] fix: copy/paste replaced tabs with spaces --- core/strconv/strconv.odin | 54 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index 885ffa1a9..3a47b312b 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -1664,18 +1664,18 @@ Casts a rune to int Example: - import "core:strconv" - rune_to_int :: proc () { - my_int, ok := strconv.rtoi('5') - if ok { - assert(typeid_of(type_of(my_int)) == int) - fmt.println(my_int) - } - } + import "core:strconv" + rune_to_int :: proc () { + my_int, ok := strconv.rtoi('5') + if ok { + assert(typeid_of(type_of(my_int)) == int) + fmt.println(my_int) + } + } Output: - 5 + 5 **Returns:** @@ -1683,10 +1683,10 @@ Output: - ok: ok=false if input not a rune or not numeric. */ rtoi :: proc(r: rune) -> (value: int, ok: bool) #optional_ok { - if '0' <= r && r <= '9' { - return int(r - '0'), true - } - return -1, false + if '0' <= r && r <= '9' { + return int(r - '0'), true + } + return -1, false } /* Casts one u8 character to int @@ -1697,19 +1697,19 @@ Casts one u8 character to int Example: - import "core:strconv" - u8_to_int :: proc () { - my_u8 := u8('8') - my_int, ok := strconv.utoi(my_u8) - if ok { - assert(typeid_of(type_of(my_int)) == int) - fmt.println(my_int) - } - } + import "core:strconv" + u8_to_int :: proc () { + my_u8 := u8('8') + my_int, ok := strconv.utoi(my_u8) + if ok { + assert(typeid_of(type_of(my_int)) == int) + fmt.println(my_int) + } + } Output: - 8 + 8 **Returns:** @@ -1718,10 +1718,10 @@ Output: - ok: ok=false if input not a u8 character or not numeric. */ utoi :: proc(u: u8) -> (value: int, ok: bool) #optional_ok { - if '0' <= u && u <= '9' { - return int(u - '0'), true - } - return -1, false + if '0' <= u && u <= '9' { + return int(u - '0'), true + } + return -1, false } /* Writes a quoted string representation of the input string to a given byte slice and returns the result as a string From 54805c600e26b17f4efad974bf5fecde3fa90a8a Mon Sep 17 00:00:00 2001 From: samwega Date: Thu, 2 Oct 2025 20:45:00 +0300 Subject: [PATCH 162/162] After discord debate: replaced rtoi and utoi with just digit_to_int and simple comment --- core/strconv/strconv.odin | 65 ++------------------------------------- 1 file changed, 2 insertions(+), 63 deletions(-) diff --git a/core/strconv/strconv.odin b/core/strconv/strconv.odin index 3a47b312b..652da1adb 100644 --- a/core/strconv/strconv.odin +++ b/core/strconv/strconv.odin @@ -1655,75 +1655,14 @@ Output: write_float :: proc(buf: []byte, f: f64, fmt: byte, prec, bit_size: int) -> string { return string(generic_ftoa(buf, f, fmt, prec, bit_size)) } -/* -Casts a rune to int - -**Input:** - -- r: The input rune to find the integer value of - -Example: - - import "core:strconv" - rune_to_int :: proc () { - my_int, ok := strconv.rtoi('5') - if ok { - assert(typeid_of(type_of(my_int)) == int) - fmt.println(my_int) - } - } - -Output: - - 5 - -**Returns:** - -- value: the integer value of the given rune -- ok: ok=false if input not a rune or not numeric. -*/ -rtoi :: proc(r: rune) -> (value: int, ok: bool) #optional_ok { +// Accepts '0'..='9', otherwise returns ok = false +digit_to_int :: proc(r: rune) -> (value: int, ok: bool) { if '0' <= r && r <= '9' { return int(r - '0'), true } return -1, false } /* -Casts one u8 character to int - -**Input:** - -- u: The input u8 character to find the integer value of - -Example: - - import "core:strconv" - u8_to_int :: proc () { - my_u8 := u8('8') - my_int, ok := strconv.utoi(my_u8) - if ok { - assert(typeid_of(type_of(my_int)) == int) - fmt.println(my_int) - } - } - -Output: - - 8 - - -**Returns:** - -- value: the integer value of the given u8 character -- ok: ok=false if input not a u8 character or not numeric. -*/ -utoi :: proc(u: u8) -> (value: int, ok: bool) #optional_ok { - if '0' <= u && u <= '9' { - return int(u - '0'), true - } - return -1, false -} -/* Writes a quoted string representation of the input string to a given byte slice and returns the result as a string **Inputs**