From 366c33e5afc336986cd0c5848ddabb23a7c38e03 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Sun, 23 Aug 2026 23:17:55 -0700 Subject: [PATCH 1/8] Delete dead code that crashed the checker on an asm template group with no parameters --- src/types.cpp | 8 ----- tests/issues/run.bat | 1 + tests/issues/run.sh | 10 +++++++ .../test_issue_asm_group_empty_params.odin | 29 +++++++++++++++++++ 4 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 tests/issues/test_issue_asm_group_empty_params.odin diff --git a/src/types.cpp b/src/types.cpp index d12d8dbee..54481098b 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -3722,14 +3722,6 @@ gb_internal ProcTypeOverloadKind are_proc_types_overload_safe(Type *x, Type *y) return ProcOverload_TargetFeatures; } - if (px.params != nullptr && py.params != nullptr) { - Entity *ex = px.params->Tuple.variables[0]; - Entity *ey = py.params->Tuple.variables[0]; - bool ok = are_types_identical(ex->type, ey->type); - if (ok) { - } - } - return ProcOverload_Identical; } diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 4cbb424b4..4978b146e 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -46,6 +46,7 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin check ..\test_issue_ellipsis_type_call.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "10" || exit /b ..\..\..\odin check ..\test_issue_foreign_redeclaration.odin -no-entry-point %COMMON% || exit /b ..\..\..\odin check ..\test_issue_foreign_redeclaration_mismatch.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "1" || exit /b +..\..\..\odin check ..\test_issue_asm_group_empty_params.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "3" || exit /b ..\..\..\odin build ..\test_issue_7037.odin %COMMON% -o:none || exit /b ..\..\..\odin build ..\test_issue_7188.odin %COMMON% || exit /b clang -c ..\test_issue_sysv_abi.c -o test_issue_sysv_abi_c.o || exit /b diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 98c144d9c..3d3746339 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -110,6 +110,16 @@ else exit 1 fi +# `asm` templates are amd64-only, so this file is empty on every other architecture +if [[ "$(uname -m)" == "x86_64" || "$(uname -m)" == "amd64" ]]; then + if [[ $($ODIN check ../test_issue_asm_group_empty_params.odin -no-entry-point $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 3 ]]; then + echo "SUCCESSFUL 1/1" + else + echo "SUCCESSFUL 0/1" + exit 1 + fi +fi + if [[ $($ODIN build ../test_issue_7108.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]]; then echo "SUCCESSFUL 1/1" else diff --git a/tests/issues/test_issue_asm_group_empty_params.odin b/tests/issues/test_issue_asm_group_empty_params.odin new file mode 100644 index 000000000..f35dcf0af --- /dev/null +++ b/tests/issues/test_issue_asm_group_empty_params.odin @@ -0,0 +1,29 @@ +#+build amd64 +// A zero-parameter asm template carries a non-null but empty parameter tuple, unlike a +// zero-parameter procedure, so dead code at the end of `are_proc_types_overload_safe` indexed +// `variables[0]` on it and tripped the bounds assertion. The duplicate-signature diagnostic the +// checker was about to print was lost, and the crash arrived with nothing else on stderr. +package test_issues + +nop_a :: asm() { nop; } +nop_b :: asm() { nop; } +dup_no_params :: asm { nop_a, nop_b } + +out_a :: asm() -> (r: u64) [r = %rax] { nop; } +out_b :: asm() -> (r: u64) [r = %rax] { nop; } +dup_result_only :: asm { out_a, out_b } + +three_a :: asm() { nop; } +three_b :: asm() { nop; } +three_c :: asm() { nop; } +dup_three :: asm { three_a, three_b, three_c } + +// a group whose members differ in their parameters has to keep resolving cleanly +add32 :: asm(a: u32, b: u32) -> (r: u32) { mov r, a; add r, b; } +add64 :: asm(a: u64, b: u64) -> (r: u64) { mov r, a; add r, b; } +add_ok :: asm { add32, add64 } + +use :: proc() { + _ = add_ok(u32(20), u32(22)) + _ = add_ok(u64(1), u64(2)) +} From b875baea08dd45e23fd5c60138b80ca9dfd39286 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Mon, 24 Aug 2026 00:55:16 -0700 Subject: [PATCH 2/8] asm template doc category --- src/docs.cpp | 9 +++++++-- src/main.cpp | 1 + tests/issues/run.bat | 1 + tests/issues/run.sh | 10 ++++++++++ tests/issues/test_issue_asm_doc_category.odin | 16 ++++++++++++++++ 5 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 tests/issues/test_issue_asm_doc_category.odin diff --git a/src/docs.cpp b/src/docs.cpp index 1d8886edb..5c3c13104 100644 --- a/src/docs.cpp +++ b/src/docs.cpp @@ -1,6 +1,6 @@ // Generates Documentation -gb_global int print_entity_kind_ordering[Entity_Count] = { +gb_global int print_entity_kind_ordering[] = { /*Invalid*/ -1, /*Constant*/ 0, /*Variable*/ 1, @@ -12,8 +12,10 @@ gb_global int print_entity_kind_ordering[Entity_Count] = { /*LibraryName*/ -1, /*Nil*/ -1, /*Label*/ -1, + /*AsmTemplate*/ 5, }; -gb_global char const *print_entity_names[Entity_Count] = { +GB_STATIC_ASSERT(gb_count_of(print_entity_kind_ordering) == Entity_Count); +gb_global char const *print_entity_names[] = { /*Invalid*/ "", /*Constant*/ "constants", /*Variable*/ "variables", @@ -25,7 +27,9 @@ gb_global char const *print_entity_names[Entity_Count] = { /*LibraryName*/ "library names", /*Nil*/ "", /*Label*/ "", + /*AsmTemplate*/ "asm templates", }; +GB_STATIC_ASSERT(gb_count_of(print_entity_names) == Entity_Count); gb_internal GB_COMPARE_PROC(cmp_entities_for_printing) { @@ -257,6 +261,7 @@ gb_internal void print_doc_package(CheckerInfo *info, AstPackage *pkg) { case Entity_ProcGroup: case Entity_ImportName: case Entity_LibraryName: + case Entity_AsmTemplate: // Fine break; } diff --git a/src/main.cpp b/src/main.cpp index 84ec22bdf..c5fb9ba1c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3463,6 +3463,7 @@ gb_internal void print_show_unused(Checker *c) { case Entity_ProcGroup: case Entity_ImportName: case Entity_LibraryName: + case Entity_AsmTemplate: // Fine break; } diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 4cbb424b4..e793a9e84 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -46,6 +46,7 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin check ..\test_issue_ellipsis_type_call.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "10" || exit /b ..\..\..\odin check ..\test_issue_foreign_redeclaration.odin -no-entry-point %COMMON% || exit /b ..\..\..\odin check ..\test_issue_foreign_redeclaration_mismatch.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "1" || exit /b +..\..\..\odin doc ..\test_issue_asm_doc_category.odin -file 2>&1 | find /c "asm templates" | findstr /x "1" || exit /b ..\..\..\odin build ..\test_issue_7037.odin %COMMON% -o:none || exit /b ..\..\..\odin build ..\test_issue_7188.odin %COMMON% || exit /b clang -c ..\test_issue_sysv_abi.c -o test_issue_sysv_abi_c.o || exit /b diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 98c144d9c..d4a653cdb 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -110,6 +110,16 @@ else exit 1 fi +# `asm` templates are amd64-only, so this file is empty on every other architecture +if [[ "$(uname -m)" == "x86_64" || "$(uname -m)" == "amd64" ]]; then + if [[ $($ODIN doc ../test_issue_asm_doc_category.odin -file 2>&1 | grep -c "asm templates") -eq 1 ]]; then + echo "SUCCESSFUL 1/1" + else + echo "SUCCESSFUL 0/1" + exit 1 + fi +fi + if [[ $($ODIN build ../test_issue_7108.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]]; then echo "SUCCESSFUL 1/1" else diff --git a/tests/issues/test_issue_asm_doc_category.odin b/tests/issues/test_issue_asm_doc_category.odin new file mode 100644 index 000000000..ddf330d25 --- /dev/null +++ b/tests/issues/test_issue_asm_doc_category.odin @@ -0,0 +1,16 @@ +#+build amd64 +// `Entity_AsmTemplate` was added to `ENTITY_KINDS` but not to the two `[Entity_Count]` tables in +// `src/docs.cpp`, so its slot was zero-filled: ordering `0` and a null name. `odin doc` and +// `odin check -show-unused` printed an `asm` template under an empty category header, and passed +// the null name to a `%s`. +package test_issues + +DOC_CONST :: 1 + +doc_proc :: proc() {} + +doc_asm :: asm() { nop; } + +doc_asm_32 :: asm(a: i32) -> (v: i32) { mov v, a; } +doc_asm_64 :: asm(a: i64) -> (v: i64) { mov v, a; } +doc_asm_group :: asm { doc_asm_32, doc_asm_64 } From ddffec5c9706b86bea2cdb76c1a5f3e64dc4f411 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Mon, 24 Aug 2026 17:55:12 -0700 Subject: [PATCH 3/8] remove the asm group error-count test --- tests/issues/run.bat | 1 - tests/issues/run.sh | 6 ---- .../test_issue_asm_group_empty_params.odin | 29 ------------------- 3 files changed, 36 deletions(-) delete mode 100644 tests/issues/test_issue_asm_group_empty_params.odin diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 67fa39723..d31751a87 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -47,7 +47,6 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin check ..\test_issue_ellipsis_type_call.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "10" || exit /b ..\..\..\odin check ..\test_issue_foreign_redeclaration.odin -no-entry-point %COMMON% || exit /b ..\..\..\odin check ..\test_issue_foreign_redeclaration_mismatch.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "1" || exit /b -..\..\..\odin check ..\test_issue_asm_group_empty_params.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "3" || exit /b ..\..\..\odin check ..\test_issue_asm_rip_register.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "6" || exit /b ..\..\..\odin check ..\test_issue_asm_template_as_value.odin -no-entry-point %COMMON% 2>&1 | find /c "Error:" | findstr /x "10" || exit /b ..\..\..\odin build ..\test_issue_7037.odin %COMMON% -o:none || exit /b diff --git a/tests/issues/run.sh b/tests/issues/run.sh index d50961959..b936a9e30 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -122,12 +122,6 @@ fi # `asm` templates are amd64-only, so this file is empty on every other architecture if [[ "$(uname -m)" == "x86_64" || "$(uname -m)" == "amd64" ]]; then - if [[ $($ODIN check ../test_issue_asm_group_empty_params.odin -no-entry-point $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 3 ]]; then - echo "SUCCESSFUL 1/1" - else - echo "SUCCESSFUL 0/1" - exit 1 - fi if [[ $($ODIN check ../test_issue_asm_rip_register.odin -no-entry-point $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 6 ]]; then echo "SUCCESSFUL 1/1" else diff --git a/tests/issues/test_issue_asm_group_empty_params.odin b/tests/issues/test_issue_asm_group_empty_params.odin deleted file mode 100644 index f35dcf0af..000000000 --- a/tests/issues/test_issue_asm_group_empty_params.odin +++ /dev/null @@ -1,29 +0,0 @@ -#+build amd64 -// A zero-parameter asm template carries a non-null but empty parameter tuple, unlike a -// zero-parameter procedure, so dead code at the end of `are_proc_types_overload_safe` indexed -// `variables[0]` on it and tripped the bounds assertion. The duplicate-signature diagnostic the -// checker was about to print was lost, and the crash arrived with nothing else on stderr. -package test_issues - -nop_a :: asm() { nop; } -nop_b :: asm() { nop; } -dup_no_params :: asm { nop_a, nop_b } - -out_a :: asm() -> (r: u64) [r = %rax] { nop; } -out_b :: asm() -> (r: u64) [r = %rax] { nop; } -dup_result_only :: asm { out_a, out_b } - -three_a :: asm() { nop; } -three_b :: asm() { nop; } -three_c :: asm() { nop; } -dup_three :: asm { three_a, three_b, three_c } - -// a group whose members differ in their parameters has to keep resolving cleanly -add32 :: asm(a: u32, b: u32) -> (r: u32) { mov r, a; add r, b; } -add64 :: asm(a: u64, b: u64) -> (r: u64) { mov r, a; add r, b; } -add_ok :: asm { add32, add64 } - -use :: proc() { - _ = add_ok(u32(20), u32(22)) - _ = add_ok(u64(1), u64(2)) -} From c1aeb47ce5f20904a8bfd6be4381744f0c9e1e94 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 25 Aug 2026 22:22:33 -0700 Subject: [PATCH 4/8] swizzle target multi-assignment --- src/llvm_backend_utility.cpp | 2 +- tests/issues/run.bat | 1 + tests/issues/run.sh | 1 + .../test_issue_swizzle_multi_assign.odin | 80 +++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 tests/issues/test_issue_swizzle_multi_assign.odin diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 947f7d259..430677aa3 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -994,7 +994,7 @@ gb_internal lbAddr lb_find_or_generate_context_ptr(lbProcedure *p) { } gb_internal lbValue lb_address_from_load_or_generate_local(lbProcedure *p, lbValue value) { - if (LLVMIsALoadInst(value.value)) { + if (!p->in_multi_assignment && LLVMIsALoadInst(value.value)) { lbValue res = {}; res.value = LLVMGetOperand(value.value, 0); res.type = alloc_type_pointer(value.type); diff --git a/tests/issues/run.bat b/tests/issues/run.bat index f211e153b..ebdc56495 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -52,6 +52,7 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused clang -c ..\test_issue_sysv_abi.c -o test_issue_sysv_abi_c.o || exit /b ..\..\..\odin test ..\test_issue_sysv_abi.odin %COMMON% || exit /b ..\..\..\odin build ..\test_issue_7073-1.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "2" || exit /b +..\..\..\odin test ..\test_issue_swizzle_multi_assign.odin %COMMON% || exit /b @echo off diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 16133d72d..4e442ceaa 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -97,6 +97,7 @@ $ODIN build ../test_issue_7167.odin $COMMON $ODIN build ../test_issue_7188.odin $COMMON $ODIN check ../test_issue_7260.odin -no-entry-point $COMMON_CHECK $ODIN test ../test_issue_bool_to_be_conversion.odin $COMMON +$ODIN test ../test_issue_swizzle_multi_assign.odin $COMMON $ODIN check ../test_issue_foreign_redeclaration.odin -no-entry-point $COMMON_CHECK if [[ $($ODIN check ../test_issue_foreign_redeclaration_mismatch.odin -no-entry-point $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]]; then diff --git a/tests/issues/test_issue_swizzle_multi_assign.odin b/tests/issues/test_issue_swizzle_multi_assign.odin new file mode 100644 index 000000000..f16442c6e --- /dev/null +++ b/tests/issues/test_issue_swizzle_multi_assign.odin @@ -0,0 +1,80 @@ +package test_issues + +import "core:testing" + +// A multi-assignment reads every right-hand side before it stores into any target, which is what +// makes `a, b = b, a` a swap. Storing into a swizzle reused the address the source was loaded from +// instead of the value read at that point, so a source that named a target written earlier in the +// same statement read back the new contents. + +@(test) +swizzle_multi_assign_swap :: proc(t: ^testing.T) { + v := [4]f32{1, 2, 3, 4} + v.xy, v.zw = v.zw, v.xy + testing.expect_value(t, v, [4]f32{3, 4, 1, 2}) + + // the other order happened to be correct already + w := [4]f32{1, 2, 3, 4} + w.zw, w.xy = w.xy, w.zw + testing.expect_value(t, w, [4]f32{3, 4, 1, 2}) + + x := [4]f32{1, 2, 3, 4} + x.xy, x.wz = x.wz, x.xy + testing.expect_value(t, x, [4]f32{4, 3, 2, 1}) +} + +@(test) +swizzle_multi_assign_element_types :: proc(t: ^testing.T) { + a := [4]i32{1, 2, 3, 4} + a.xy, a.zw = a.zw, a.xy + testing.expect_value(t, a, [4]i32{3, 4, 1, 2}) + + b := [4]f64{1, 2, 3, 4} + b.xy, b.zw = b.zw, b.xy + testing.expect_value(t, b, [4]f64{3, 4, 1, 2}) + + c := [4]u8{1, 2, 3, 4} + c.xy, c.zw = c.zw, c.xy + testing.expect_value(t, c, [4]u8{3, 4, 1, 2}) +} + +@(test) +swizzle_multi_assign_through_pointer :: proc(t: ^testing.T) { + v := [4]f32{1, 2, 3, 4} + p := &v + p.xy, p.zw = p.zw, p.xy + testing.expect_value(t, v, [4]f32{3, 4, 1, 2}) +} + +@(test) +swizzle_multi_assign_across_variables :: proc(t: ^testing.T) { + // the source names a different variable, which an earlier store still writes to + a := [4]f32{1, 2, 3, 4} + b := [4]f32{5, 6, 7, 8} + a.xy, b.xy = b.xy, a.xy + testing.expect_value(t, a, [4]f32{5, 6, 3, 4}) + testing.expect_value(t, b, [4]f32{1, 2, 7, 8}) +} + +@(test) +swizzle_multi_assign_three_targets :: proc(t: ^testing.T) { + v := [4]f32{1, 2, 3, 4} + v.xy, v.zw, v.x = v.zw, v.xy, v.w + testing.expect_value(t, v, [4]f32{4, 4, 1, 2}) +} + +@(test) +swizzle_single_assign_unchanged :: proc(t: ^testing.T) { + // one target still reads every lane before it writes any of them + v := [4]f32{1, 2, 3, 4} + v.yzw = v.xyz + testing.expect_value(t, v, [4]f32{1, 1, 2, 3}) + + w := [4]f32{1, 2, 3, 4} + w.xy = w.zw + testing.expect_value(t, w, [4]f32{3, 4, 3, 4}) + + x := [4]f32{1, 2, 3, 4} + x.x, x.y = x.y, x.x + testing.expect_value(t, x, [4]f32{2, 1, 3, 4}) +} From 915fc97b5fb792e0185f2d762e6f259b80ba634f Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 25 Aug 2026 22:46:30 -0700 Subject: [PATCH 5/8] add missing int 128le --- src/types.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types.cpp b/src/types.cpp index cf0927cdd..c77755105 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2156,6 +2156,7 @@ gb_internal bool is_type_endian_specific(Type *t) { case Basic_u32le: case Basic_i64le: case Basic_u64le: + case Basic_i128le: case Basic_u128le: return true; @@ -2165,6 +2166,7 @@ gb_internal bool is_type_endian_specific(Type *t) { case Basic_u32be: case Basic_i64be: case Basic_u64be: + case Basic_i128be: case Basic_u128be: return true; From deeddc7b331ca5314925fc50708708f1ba970317 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 25 Aug 2026 23:03:11 -0700 Subject: [PATCH 6/8] add missing endian 128 types --- core/encoding/json/marshal.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index e6c8fba38..e467536cc 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -782,6 +782,7 @@ cast_any_int_to_u128 :: proc(any_int_value: any) -> u128 { case i16le: u = u128(i) case i32le: u = u128(i) case i64le: u = u128(i) + case i128le: u = u128(i) case u16le: u = u128(i) case u32le: u = u128(i) case u64le: u = u128(i) @@ -790,6 +791,7 @@ cast_any_int_to_u128 :: proc(any_int_value: any) -> u128 { case i16be: u = u128(i) case i32be: u = u128(i) case i64be: u = u128(i) + case i128be: u = u128(i) case u16be: u = u128(i) case u32be: u = u128(i) case u64be: u = u128(i) From 7e9ffac5b0c315b8e0846ddcc0119e9909868b75 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 25 Aug 2026 23:27:06 -0700 Subject: [PATCH 7/8] const truncation sweep --- src/check_builtin.cpp | 12 ++++++++++++ src/check_stmt.cpp | 19 +++++++++++-------- src/check_type.cpp | 7 +++++++ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 34cf19f43..00065b701 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -5230,6 +5230,12 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As operand->type = t_invalid; return false; } + convert_to_typed(c, &x, t_int); + if (x.mode == Addressing_Invalid) { + operand->mode = Addressing_Type; + operand->type = t_invalid; + return false; + } i64 count = big_int_to_i64(&x.value.value_integer); check_expr_or_type(c, &y, ce->args[1]); @@ -7710,6 +7716,12 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As return false; } + convert_to_typed(c, &x, t_int); + if (x.mode == Addressing_Invalid) { + operand->mode = Addressing_Type; + operand->type = t_invalid; + return false; + } i64 index = big_int_to_i64(&x.value.value_integer); if (index < 0 || index >= u->Union.variants.count) { error(call, "Variant tag out of bounds index for '%.*s", LIT(builtin_name)); diff --git a/src/check_stmt.cpp b/src/check_stmt.cpp index f92c97f02..2b547a30a 100644 --- a/src/check_stmt.cpp +++ b/src/check_stmt.cpp @@ -954,14 +954,17 @@ gb_internal void check_unroll_range_stmt(CheckerContext *ctx, Ast *node, u32 mod error(x.expr, "Expected a constant integer for #unroll, got '%s'", s); gb_string_free(s); } else { - ExactValue value = exact_value_to_integer(x.value); - i64 v = exact_value_to_i64(value); - if (v < 1) { - error(x.expr, "Expected a constant integer >= 1 for #unroll, got %lld", cast(long long)v); - } else { - unroll_count = v; - if (v > 1024) { - error(x.expr, "Too large of a value for #unroll, got %lld, expected <= 1024", cast(long long)v); + convert_to_typed(ctx, &x, t_int); + if (x.mode != Addressing_Invalid) { + ExactValue value = exact_value_to_integer(x.value); + i64 v = exact_value_to_i64(value); + if (v < 1) { + error(x.expr, "Expected a constant integer >= 1 for #unroll, got %lld", cast(long long)v); + } else { + unroll_count = v; + if (v > 1024) { + error(x.expr, "Too large of a value for #unroll, got %lld, expected <= 1024", cast(long long)v); + } } } diff --git a/src/check_type.cpp b/src/check_type.cpp index 0b912905b..09106c502 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1120,6 +1120,13 @@ gb_internal void check_bit_field_type(CheckerContext *ctx, Type *bit_field_type, gb_string_free(s); } + if (o.mode == Addressing_Constant) { + convert_to_typed(ctx, &o, t_int); + if (o.mode == Addressing_Invalid) { + o.value = exact_value_i64(1); + } + } + ExactValue bit_size = o.value; if (bit_size.kind != ExactValue_Integer) { From 7d7d9a01ee3215f12bd4a6f612ebc966a2e84c05 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 25 Aug 2026 23:55:55 -0700 Subject: [PATCH 8/8] fill left-justified field with spaces --- core/fmt/fmt.odin | 4 +++- tests/core/fmt/test_core_fmt.odin | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 3ef5a13e6..20c2d7123 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -1037,7 +1037,9 @@ fmt_write_padding :: proc(fi: ^Info, width: int) { } pad_byte: byte = ' ' - if !fi.space { + if !fi.space && !fi.minus { + // a left-justified field pads to the right of the digits, where a '0' would read + // as part of the number rather than as filler pad_byte = '0' } diff --git a/tests/core/fmt/test_core_fmt.odin b/tests/core/fmt/test_core_fmt.odin index c79a15e85..c0396b71f 100644 --- a/tests/core/fmt/test_core_fmt.odin +++ b/tests/core/fmt/test_core_fmt.odin @@ -389,6 +389,34 @@ leaking_struct_tag :: proc(t: ^testing.T) { check(t, "My_Struct{names = [\"hello?\"], name_count = 1}", "%v", foo) } +@(test) +test_fmt_left_justified_padding :: proc(t: ^testing.T) { + // a left-justified field pads to the right of the digits, so the fill has to be a space; + // a '0' there is read back as part of the number + check(t, "42 ", "%-5d", 42) + check(t, "42 ", "%-5v", 42) + check(t, "-42 ", "%-6d", -42) + check(t, "ff ", "%-8x", 255) + check(t, "101 ", "%-10b", 5) + check(t, "3.140 ", "%-8f", 3.14) + check(t, "3.0e+02 ", "%-10.1e", 300.0) + check(t, "3.000000e+00 ", "%-14e", 3.0) + check(t, "1tib ", "%-8.0m", mem.Terabyte) + check(t, " ", "%-5.0d", 0) + check(t, "ab ", "%-5s", "ab") + check(t, "true ", "%-5t", true) + check(t, "42 ", "%- 5d", 42) + check(t, "42 ", "{:-5d}", 42) + + // right-justified fields still zero fill, which is Odin's own convention + check(t, "00042", "%5d", 42) + check(t, "00042", "%05d", 42) + check(t, "03.140", "%6f", 3.14) + check(t, "-00042", "%6d", -42) + check(t, "01tib", "%5.0m", mem.Terabyte) + check(t, " ab", "%5s", "ab") +} + @(private) check :: proc(t: ^testing.T, exp: string, format: string, args: ..any, loc := #caller_location) { got := fmt.tprintf(format, ..args)