From a4091c5d376adc22719a1bbdc51055254aa9cd91 Mon Sep 17 00:00:00 2001 From: subotac <73706465+subotac@users.noreply.github.com> Date: Sat, 1 Aug 2026 19:48:54 +0300 Subject: [PATCH 01/42] fix: pack tuple results into variadic arguments --- src/llvm_backend_proc.cpp | 181 ++++++++++++++++++------------ tests/issues/run.sh | 1 + tests/issues/test_issue_7167.odin | 9 ++ 3 files changed, 122 insertions(+), 69 deletions(-) create mode 100644 tests/issues/test_issue_7167.odin diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 9f1b5dd3f..3b5699160 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -4788,6 +4788,79 @@ gb_internal void lb_add_values_to_array(lbProcedure *p, Array *args, lb } } +gb_internal lbValue lb_build_variadic_slice(lbProcedure *p, Type *slice_type, Slice var_args) { + GB_ASSERT(is_type_slice(slice_type)); + if (var_args.count == 0) { + return lb_const_nil(p->module, slice_type); + } + + Type *elem_type = slice_type->Slice.elem; + lbAddr slice = {}; + + for (auto const &vr : p->variadic_reuses) { + if (are_types_identical(vr.slice_type, slice_type)) { + slice = vr.slice_addr; + break; + } + } + + DeclInfo *d = decl_info_of_entity(p->entity); + if (d != nullptr && slice.addr.value == nullptr) { + for (auto const &vr : d->variadic_reuses) { + if (are_types_identical(vr.slice_type, slice_type)) { + #if LLVM_VERSION_MAJOR >= 13 + // NOTE(bill): No point wasting even more memory, just reuse this stack variable too + if (p->variadic_reuses.count > 0) { + slice = p->variadic_reuses[0].slice_addr; + } else { + slice = lb_add_local_generated(p, slice_type, true); + } + // NOTE(bill): Change the underlying type to match the specific type + slice.addr.type = alloc_type_pointer(slice_type); + #else + slice = lb_add_local_generated(p, slice_type, true); + #endif + array_add(&p->variadic_reuses, lbVariadicReuseSlices{slice_type, slice}); + break; + } + } + } + + lbValue base_array_ptr = p->variadic_reuse_base_array_ptr.addr; + if (base_array_ptr.value == nullptr) { + if (d != nullptr) { + i64 max_bytes = d->variadic_reuse_max_bytes; + i64 max_align = gb_max(d->variadic_reuse_max_align, 16); + p->variadic_reuse_base_array_ptr = lb_add_local_generated(p, alloc_type_array(t_u8, max_bytes), true); + lb_try_update_alignment(p->variadic_reuse_base_array_ptr.addr, cast(unsigned)max_align); + base_array_ptr = p->variadic_reuse_base_array_ptr.addr; + } else { + base_array_ptr = lb_add_local_generated(p, alloc_type_array(elem_type, var_args.count), true).addr; + } + } + + if (slice.addr.value == nullptr) { + slice = lb_add_local_generated(p, slice_type, true); + } + + GB_ASSERT(base_array_ptr.value != nullptr); + GB_ASSERT(slice.addr.value != nullptr); + + base_array_ptr = lb_emit_conv(p, base_array_ptr, alloc_type_pointer(alloc_type_array(elem_type, var_args.count))); + + for_array(i, var_args) { + lbValue addr = lb_emit_array_epi(p, base_array_ptr, cast(i32)i); + lbValue var_arg = lb_emit_conv(p, var_args[i], elem_type); + lb_emit_store(p, addr, var_arg); + } + + lbValue base_elem = lb_emit_array_epi(p, base_array_ptr, 0); + lbValue len = lb_const_int(p->module, t_int, var_args.count); + lb_fill_slice(p, slice, base_elem, len); + + return lb_addr_load(p, slice); +} + gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr, lbValue *sret_dst) { lbModule *m = p->module; @@ -4873,8 +4946,45 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr, lbVal bool vari_expand = (ce->ellipsis.pos.line != 0); bool is_c_vararg = pt->c_vararg; + bool has_tuple_positional_arg = false; + if (pt->variadic && !is_c_vararg && !vari_expand) { + for (Ast *arg : ce->split_args->positional) { + TypeAndValue tav = type_and_value_of_expr(arg); + if (is_type_tuple(tav.type)) { + has_tuple_positional_arg = true; + break; + } + } + } - for_array(i, ce->split_args->positional) { + if (has_tuple_positional_arg) { + auto flat_args = array_make(heap_allocator()); + defer (array_free(&flat_args)); + + for_array(i, ce->split_args->positional) { + Entity *e = pt->params->Tuple.variables[gb_min(i, cast(isize)pt->variadic_index)]; + if (e->kind == Entity_TypeName) { + array_add(&flat_args, lb_const_nil(p->module, e->type)); + } else if (e->kind == Entity_Constant) { + array_add(&flat_args, lb_const_value(p->module, e->type, e->Constant.value)); + } else { + GB_ASSERT(e->kind == Entity_Variable); + lbValue arg = lb_build_expr(p, ce->split_args->positional[i]); + lb_add_values_to_array(p, &flat_args, arg); + } + } + + isize fixed_count = pt->variadic_index; + isize supplied_fixed_count = gb_min(fixed_count, flat_args.count); + for (isize i = 0; i < supplied_fixed_count; i++) { + array_add(&args, flat_args[i]); + } + array_resize(&args, fixed_count); + + Type *slice_type = pt->params->Tuple.variables[pt->variadic_index]->type; + auto var_args = slice(slice_from_array(flat_args), supplied_fixed_count, flat_args.count); + array_add(&args, lb_build_variadic_slice(p, slice_type, var_args)); + } else for_array(i, ce->split_args->positional) { Entity *e = pt->params->Tuple.variables[i]; if (e->kind == Entity_TypeName) { array_add(&args, lb_const_nil(p->module, e->type)); @@ -4924,74 +5034,7 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr, lbVal lbValue v = lb_build_expr(p, var_arg); lb_add_values_to_array(p, &var_args, v); } - isize slice_len = var_args.count; - if (slice_len > 0) { - lbAddr slice = {}; - - for (auto const &vr : p->variadic_reuses) { - if (are_types_identical(vr.slice_type, slice_type)) { - slice = vr.slice_addr; - break; - } - } - - DeclInfo *d = decl_info_of_entity(p->entity); - if (d != nullptr && slice.addr.value == nullptr) { - for (auto const &vr : d->variadic_reuses) { - if (are_types_identical(vr.slice_type, slice_type)) { - #if LLVM_VERSION_MAJOR >= 13 - // NOTE(bill): No point wasting even more memory, just reuse this stack variable too - if (p->variadic_reuses.count > 0) { - slice = p->variadic_reuses[0].slice_addr; - } else { - slice = lb_add_local_generated(p, slice_type, true); - } - // NOTE(bill): Change the underlying type to match the specific type - slice.addr.type = alloc_type_pointer(slice_type); - #else - slice = lb_add_local_generated(p, slice_type, true); - #endif - array_add(&p->variadic_reuses, lbVariadicReuseSlices{slice_type, slice}); - break; - } - } - } - - lbValue base_array_ptr = p->variadic_reuse_base_array_ptr.addr; - if (base_array_ptr.value == nullptr) { - if (d != nullptr) { - i64 max_bytes = d->variadic_reuse_max_bytes; - i64 max_align = gb_max(d->variadic_reuse_max_align, 16); - p->variadic_reuse_base_array_ptr = lb_add_local_generated(p, alloc_type_array(t_u8, max_bytes), true); - lb_try_update_alignment(p->variadic_reuse_base_array_ptr.addr, cast(unsigned)max_align); - base_array_ptr = p->variadic_reuse_base_array_ptr.addr; - } else { - base_array_ptr = lb_add_local_generated(p, alloc_type_array(elem_type, slice_len), true).addr; - } - } - - if (slice.addr.value == nullptr) { - slice = lb_add_local_generated(p, slice_type, true); - } - - GB_ASSERT(base_array_ptr.value != nullptr); - GB_ASSERT(slice.addr.value != nullptr); - - base_array_ptr = lb_emit_conv(p, base_array_ptr, alloc_type_pointer(alloc_type_array(elem_type, slice_len))); - - for (isize i = 0; i < var_args.count; i++) { - lbValue addr = lb_emit_array_epi(p, base_array_ptr, cast(i32)i); - lbValue var_arg = var_args[i]; - var_arg = lb_emit_conv(p, var_arg, elem_type); - lb_emit_store(p, addr, var_arg); - } - - lbValue base_elem = lb_emit_array_epi(p, base_array_ptr, 0); - lbValue len = lb_const_int(p->module, t_int, slice_len); - lb_fill_slice(p, slice, base_elem, len); - - variadic_args = lb_addr_load(p, slice); - } + variadic_args = lb_build_variadic_slice(p, slice_type, slice_from_array(var_args)); } } array_add(&args, variadic_args); diff --git a/tests/issues/run.sh b/tests/issues/run.sh index ce97a570a..a8134d865 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -82,6 +82,7 @@ else fi $ODIN check ../test_issue_6979.odin -no-entry-point $COMMON $ODIN build ../test_issue_7037.odin $COMMON -o:none +$ODIN build ../test_issue_7167.odin $COMMON if [[ $($ODIN build ../test_issue_7108.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]] ; then echo "SUCCESSFUL 1/1" diff --git a/tests/issues/test_issue_7167.odin b/tests/issues/test_issue_7167.odin new file mode 100644 index 000000000..6c4171088 --- /dev/null +++ b/tests/issues/test_issue_7167.odin @@ -0,0 +1,9 @@ +// Tests issue #7167 https://github.com/odin-lang/Odin/issues/7167 +package test_issues + +import "core:fmt" +import "core:path/filepath" + +main :: proc() { + fmt.printf(filepath.join({})) +} From df6719cb89d9c36866099ca703d06bde530226e2 Mon Sep 17 00:00:00 2001 From: Brody Date: Mon, 3 Aug 2026 18:13:12 +1000 Subject: [PATCH 02/42] fix: remove broken special case for poly proc as default value --- src/check_expr.cpp | 11 ----------- tests/issues/run.bat | 1 + tests/issues/run.sh | 1 + tests/issues/test_issue_6753.odin | 27 +++++++++++++++++++++++++++ 4 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 tests/issues/test_issue_6753.odin diff --git a/src/check_expr.cpp b/src/check_expr.cpp index eea6db2b4..115b13992 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1023,17 +1023,6 @@ gb_internal bool check_is_assignable_to_with_score(CheckerContext *c, Operand *o return false; } - // Handle polymorphic procedure used as default parameter - if (operand->mode == Addressing_Value && is_type_proc(type) && is_type_proc(operand->type)) { - Entity *e = entity_from_expr(operand->expr); - if (e != nullptr && e->kind == Entity_Procedure && is_type_polymorphic(e->type) && !is_type_polymorphic(type)) { - // Special case: Allow a polymorphic procedure to be used as default value for concrete proc type - // during the initial check. It will be properly instantiated when actually used. - if (score_) *score_ = assign_score_function(1); - return true; - } - } - i64 score = check_distance_between_types(c, operand, type, allow_array_programming); if (score >= 0) { if (score_) *score_ = assign_score_function(score, is_variadic); diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 8a85ba90a..269c91a0b 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -37,6 +37,7 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin test ..\test_pr_6470.odin -define:TEST_EXPECT_FAILURE=true %COMMON% 2>&1 | find /c "Error:" | findstr /x "1" || exit /b ..\..\..\odin test ..\test_pr_6476.odin %COMMON% || exit /b ..\..\..\odin check ..\test_issue_6484.odin -no-entry-point %COMMON% || exit /b +..\..\..\odin test ..\test_issue_6753.odin %COMMON% || exit /b ..\..\..\odin check ..\test_issue_6874.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "1" || exit /b ..\..\..\odin check ..\test_issue_6979.odin -no-entry-point %COMMON% || 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 ce97a570a..9bcc53dfe 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -74,6 +74,7 @@ else exit 1 fi $ODIN check ../test_issue_6484.odin -no-entry-point $COMMON +$ODIN test ../test_issue_6753.odin -no-entry-point $COMMON if [[ $($ODIN check ../test_issue_6874.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]] ; then echo "SUCCESSFUL 1/1" else diff --git a/tests/issues/test_issue_6753.odin b/tests/issues/test_issue_6753.odin new file mode 100644 index 000000000..7f6c7208c --- /dev/null +++ b/tests/issues/test_issue_6753.odin @@ -0,0 +1,27 @@ +// test issue for #6753 https://github.com/odin-lang/odin/issues/6753 +package test_issues +import "core:testing" + +identity :: proc(x: $T) -> T { return x } + +foo :: proc(x: int, poly: proc(int) -> int = identity) -> int { + return foo(identity(x)) +} + +// failing as returns before specialized +@test +test_issue_6753 :: proc(t: ^testing.T) { + p: proc(int) -> int = identity + + testing.expect(t, p != nil) + + if p != nil { + testing.expect(t, p(123) == 123) + } +} + +// failing as returns before specialized +@test +test_issue_default_poly_parameter_6753 :: proc(t: ^testing.T) { + testing.expect(t, foo(123) == 123) +} \ No newline at end of file From 6888e843861a35376679c05d92366a7ad22264c2 Mon Sep 17 00:00:00 2001 From: Brody Date: Mon, 3 Aug 2026 18:34:53 +1000 Subject: [PATCH 03/42] fix: Added null check before setting operand type to invalid --- src/check_type.cpp | 8 ++++++-- tests/issues/test_issue_6753.odin | 10 +++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index be06ca108..ed916ceb2 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2143,8 +2143,12 @@ gb_internal Type *check_get_params(CheckerContext *ctx, Scope *scope, Ast *_para // This is just to add the error message to determine_type_from_polymorphic which // depends on valid position information op.expr = _params; - op.mode = Addressing_Invalid; - op.type = t_invalid; + + // NOTE(taylbr): Can still have valid type with null expr. Needed for resolving + if (op.mode == Addressing_Invalid || op.type == nullptr) { + op.mode = Addressing_Invalid; + op.type = t_invalid; + } } if (is_type_polymorphic_type) { type = determine_type_from_polymorphic(ctx, type, op); diff --git a/tests/issues/test_issue_6753.odin b/tests/issues/test_issue_6753.odin index 7f6c7208c..d4c668c6d 100644 --- a/tests/issues/test_issue_6753.odin +++ b/tests/issues/test_issue_6753.odin @@ -12,16 +12,12 @@ foo :: proc(x: int, poly: proc(int) -> int = identity) -> int { @test test_issue_6753 :: proc(t: ^testing.T) { p: proc(int) -> int = identity - - testing.expect(t, p != nil) - - if p != nil { - testing.expect(t, p(123) == 123) - } + testing.expect(t, p != nil, "polymorphic procedure was not instantiated") + testing.expect_value(t, p(123), 123) } // failing as returns before specialized @test test_issue_default_poly_parameter_6753 :: proc(t: ^testing.T) { - testing.expect(t, foo(123) == 123) + testing.expect_value(t, foo(123), 123) } \ No newline at end of file From cce1bdd8c751344f01cd17b90a7ceca9dcac2cfe Mon Sep 17 00:00:00 2001 From: Brody Date: Mon, 3 Aug 2026 21:42:39 +1000 Subject: [PATCH 04/42] fix: return true for already specialised poly proc --- src/check_expr.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 115b13992..8256d9474 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -410,6 +410,15 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E } if (!src->Proc.is_polymorphic || src->Proc.is_poly_specialized) { + // NOTE: polymorphic procedure check not idempotent without this + if (src->Proc.is_poly_specialized && base_entity->Procedure.generated_from_polymorphic) { + if (are_types_identical(src, dst)) { + if (poly_proc_data) { + poly_proc_data->gen_entity = base_entity; + } + return true; + } + } return false; } From 87b514890cb2fd3524e6bd9e5e7e12e3a248ca91 Mon Sep 17 00:00:00 2001 From: Brody Date: Mon, 3 Aug 2026 22:07:18 +1000 Subject: [PATCH 05/42] fix: instantiated procs keep their own scope Was this check here for a reason? Investigate --- src/check_expr.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 8256d9474..609173981 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -466,9 +466,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E scope->flags |= ScopeFlag_Proc; nctx.scope = scope; nctx.allow_polymorphic_types = true; - if (nctx.polymorphic_scope == nullptr) { - nctx.polymorphic_scope = scope; - } + nctx.polymorphic_scope = scope; auto *pt = &src->Proc; From e9cb1e701939c493a9d1f187f337f0f5f192345c Mon Sep 17 00:00:00 2001 From: Max Rabin <927792+maxrabin@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:26:18 +0300 Subject: [PATCH 06/42] Fix grammar in simd.odin doc comment --- core/simd/simd.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/simd/simd.odin b/core/simd/simd.odin index 1631c4d23..fcbceb81a 100644 --- a/core/simd/simd.odin +++ b/core/simd/simd.odin @@ -2,7 +2,7 @@ Cross-platform `SIMD` support types and procedures. SIMD (Single Instruction Multiple Data), is a CPU hardware feature that -introduce special registers and instructions which operate on multiple units +introduces special registers and instructions which operate on multiple units of data at the same time, which enables faster data processing for applications with heavy computational workloads. From 91e0e6ce473bbad09f225d6a6fcaffa0699f9ee9 Mon Sep 17 00:00:00 2001 From: Brody Date: Mon, 3 Aug 2026 22:26:24 +1000 Subject: [PATCH 07/42] fix: record resolved entity for proc-typed default values --- src/check_expr.cpp | 2 +- src/check_type.cpp | 1 + src/entity.cpp | 1 + src/llvm_backend_proc.cpp | 6 +++ tests/issues/run.bat | 1 + tests/issues/run.sh | 8 +++- tests/issues/test_issue_5573.odin | 17 +++++++++ tests/issues/test_issue_6753.odin | 63 +++++++++++++++++++++++++------ 8 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 tests/issues/test_issue_5573.odin diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 609173981..a91664bb0 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -412,7 +412,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E if (!src->Proc.is_polymorphic || src->Proc.is_poly_specialized) { // NOTE: polymorphic procedure check not idempotent without this if (src->Proc.is_poly_specialized && base_entity->Procedure.generated_from_polymorphic) { - if (are_types_identical(src, dst)) { + if (are_types_identical(src, dst)) { if (poly_proc_data) { poly_proc_data->gen_entity = base_entity; } diff --git a/src/check_type.cpp b/src/check_type.cpp index ed916ceb2..97f609dd8 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -1791,6 +1791,7 @@ gb_internal ParameterValue handle_parameter_value(CheckerContext *ctx, Type *in_ if (e->kind == Entity_Procedure) { param_value.kind = ParameterValue_Constant; param_value.value = exact_value_procedure(e->identifier); + param_value.proc_entity = e; add_entity_use(ctx, e->identifier, e); } else { if (e->flags & EntityFlag_Param) { diff --git a/src/entity.cpp b/src/entity.cpp index 31f90023b..d3170c823 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -111,6 +111,7 @@ enum ParameterValueKind { struct ParameterValue { ParameterValueKind kind; Ast *original_ast_expr; + Entity *proc_entity; union { ExactValue value; Ast *ast_value; diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 9f1b5dd3f..1cff61ba9 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -4669,6 +4669,12 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu gb_internal lbValue lb_handle_param_value(lbProcedure *p, Type *parameter_type, ParameterValue const ¶m_value, TypeProc *procedure_type, Ast* call_expression) { switch (param_value.kind) { case ParameterValue_Constant: + if (param_value.proc_entity != nullptr && is_type_proc(parameter_type)) { + lbValue v = lb_find_procedure_value_from_entity(p->module, param_value.proc_entity); + if (v.value != nullptr) { + return lb_emit_conv(p, v, parameter_type); + } + } if (is_type_constant_type(parameter_type)) { auto res = lb_const_value(p->module, parameter_type, param_value.value); return res; diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 269c91a0b..6d562ccbc 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -26,6 +26,7 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin build ..\test_issue_5097.odin %COMMON% || exit /b ..\..\..\odin build ..\test_issue_5097-2.odin %COMMON% || exit /b ..\..\..\odin build ..\test_issue_5265.odin %COMMON% || exit /b +..\..\..\odin build ..\test_issue_5573.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "2" || exit /b ..\..\..\odin test ..\test_issue_5699.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_6068.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_6101.odin %COMMON% || exit /b diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 9bcc53dfe..49b825bb8 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -32,6 +32,12 @@ $ODIN build ../test_issue_5043.odin $COMMON $ODIN build ../test_issue_5097.odin $COMMON $ODIN build ../test_issue_5097-2.odin $COMMON $ODIN build ../test_issue_5265.odin $COMMON +if [[ $($ODIN build ../test_issue_5573.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]] ; then + echo "SUCCESSFUL 1/1" +else + echo "SUCCESSFUL 0/1" + exit 1 +fi $ODIN test ../test_issue_5699.odin $COMMON $ODIN test ../test_issue_6068.odin $COMMON $ODIN test ../test_issue_6101.odin $COMMON @@ -74,7 +80,7 @@ else exit 1 fi $ODIN check ../test_issue_6484.odin -no-entry-point $COMMON -$ODIN test ../test_issue_6753.odin -no-entry-point $COMMON +$ODIN test ../test_issue_6753.odin $COMMON if [[ $($ODIN check ../test_issue_6874.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]] ; then echo "SUCCESSFUL 1/1" else diff --git a/tests/issues/test_issue_5573.odin b/tests/issues/test_issue_5573.odin new file mode 100644 index 000000000..dd154a89a --- /dev/null +++ b/tests/issues/test_issue_5573.odin @@ -0,0 +1,17 @@ +// Tests issue #5573 https://github.com/odin-lang/Odin/issues/5573 +package test_issues + +poly :: proc(x: $T) -> string { + return "poly" +} + +takes_concrete :: proc(f: proc(a: int, b: f32, c: rawptr) -> ^int) { +} + +main :: proc() { + // should error - wrong arity, wrong parameter types, wrong return type + mismatched: proc(a: int, b: f32, c: rawptr) -> ^int = poly + + // should error - same, as a procedure argument + takes_concrete(poly) +} diff --git a/tests/issues/test_issue_6753.odin b/tests/issues/test_issue_6753.odin index d4c668c6d..a14b2dc01 100644 --- a/tests/issues/test_issue_6753.odin +++ b/tests/issues/test_issue_6753.odin @@ -1,23 +1,64 @@ // test issue for #6753 https://github.com/odin-lang/odin/issues/6753 package test_issues import "core:testing" +import "core:fmt" -identity :: proc(x: $T) -> T { return x } - -foo :: proc(x: int, poly: proc(int) -> int = identity) -> int { - return foo(identity(x)) +foo_concrete :: proc(x: int, g: proc(int) -> int) -> int { + return g(x) +} +foo_impossible :: proc(x: int, g: proc(int, int) -> string) -> string { + return "impossible" +} +foo_group :: proc { + foo_concrete, + foo_impossible, +} + +f_poly :: proc(x: $T) -> T { return x } +foo_poly :: proc(x: $T, g: proc(T) -> T = f_poly) -> T { + return g(x) } -// failing as returns before specialized @test -test_issue_6753 :: proc(t: ^testing.T) { - p: proc(int) -> int = identity +test_issue_6753_ambiguous_poly_argumentment :: proc (t: ^testing.T) { + testing.expect_value(t, foo_group(1, f_poly), 1) // should be no ambiguity whether foo_concrete or foo_impossible +} + +@test +test_issue_6753_default_poly_proc :: proc (t: ^testing.T) { + testing.expect_value(t, foo_poly(1), 1) +} + +@test +test_issue_6753_parapoly_proc_variable :: proc(t: ^testing.T) { + p: proc(int) -> int = f_poly testing.expect(t, p != nil, "polymorphic procedure was not instantiated") testing.expect_value(t, p(123), 123) } -// failing as returns before specialized +// -- Fixing above led to some new bugs surfacing -- @test -test_issue_default_poly_parameter_6753 :: proc(t: ^testing.T) { - testing.expect_value(t, foo(123), 123) -} \ No newline at end of file +test_issue_6753_parapoly_proc_as_argument :: proc(t: ^testing.T) { + testing.expect(t, foo_concrete(123, f_poly) == 123, "failed to pass poly proc as argument") +} + +@test +test_issue_6753_parapoly_with_default_proc_same_generic_type_T :: proc(t: ^testing.T) { + testing.expect_value(t, foo_poly(123), 123) + testing.expect_value(t, foo_poly(123, f_poly), 123) +} + +// all together now +describe :: proc(x: $T) -> string { return fmt.tprintf("#%v", x) } +describe_bytes :: proc(x: []byte) -> string { return "bytes" } + +bar_poly :: proc(x: $T, g: proc(x: T) -> string = describe) -> string { return g(x) } +bar_bytes :: proc(x: []byte, g: proc(x: []byte) -> string) -> string { return g(x) } +bar_group :: proc { bar_poly, bar_bytes } + +@test +test_issue_6753_parapoly_default_in_group :: proc(t: ^testing.T) { + testing.expect_value(t, bar_group(123, describe), "#123") + testing.expect_value(t, bar_group("hi"), "#hi") + testing.expect_value(t, bar_group([]byte{1, 2}, describe_bytes), "bytes") +} From d36e9a24b117c23b1bf531f6dc6b14507ed2a8bc Mon Sep 17 00:00:00 2001 From: subotac <73706465+subotac@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:02:06 +0300 Subject: [PATCH 08/42] fix: remove unused variadic element type --- src/llvm_backend_proc.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 3b5699160..3755db128 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -5026,8 +5026,6 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr, lbVal variadic_args = lb_build_expr(p, variadic[0]); variadic_args = lb_emit_conv(p, variadic_args, slice_type); } else { - Type *elem_type = slice_type->Slice.elem; - auto var_args = array_make(heap_allocator(), 0, variadic.count); defer (array_free(&var_args)); for (Ast *var_arg : variadic) { From 90f6c9d0dbb7739aa3e8a0a59576112473065a56 Mon Sep 17 00:00:00 2001 From: Brody Date: Wed, 5 Aug 2026 00:17:48 +1000 Subject: [PATCH 09/42] whitespace --- tests/issues/test_issue_5573.odin | 1 + tests/issues/test_issue_6753.odin | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/issues/test_issue_5573.odin b/tests/issues/test_issue_5573.odin index dd154a89a..150f95863 100644 --- a/tests/issues/test_issue_5573.odin +++ b/tests/issues/test_issue_5573.odin @@ -11,6 +11,7 @@ takes_concrete :: proc(f: proc(a: int, b: f32, c: rawptr) -> ^int) { main :: proc() { // should error - wrong arity, wrong parameter types, wrong return type mismatched: proc(a: int, b: f32, c: rawptr) -> ^int = poly + _ = mismatched // should error - same, as a procedure argument takes_concrete(poly) diff --git a/tests/issues/test_issue_6753.odin b/tests/issues/test_issue_6753.odin index a14b2dc01..26531ae31 100644 --- a/tests/issues/test_issue_6753.odin +++ b/tests/issues/test_issue_6753.odin @@ -20,18 +20,18 @@ foo_poly :: proc(x: $T, g: proc(T) -> T = f_poly) -> T { } @test -test_issue_6753_ambiguous_poly_argumentment :: proc (t: ^testing.T) { - testing.expect_value(t, foo_group(1, f_poly), 1) // should be no ambiguity whether foo_concrete or foo_impossible +test_issue_6753_ambiguous_poly_argument :: proc (t: ^testing.T) { + testing.expect_value(t, foo_group(1, f_poly), 1) // should be no ambiguity whether foo_concrete or foo_impossible } @test test_issue_6753_default_poly_proc :: proc (t: ^testing.T) { - testing.expect_value(t, foo_poly(1), 1) + testing.expect_value(t, foo_poly(1), 1) } @test test_issue_6753_parapoly_proc_variable :: proc(t: ^testing.T) { - p: proc(int) -> int = f_poly + p: proc(int) -> int = f_poly testing.expect(t, p != nil, "polymorphic procedure was not instantiated") testing.expect_value(t, p(123), 123) } @@ -58,7 +58,7 @@ bar_group :: proc { bar_poly, bar_bytes } @test test_issue_6753_parapoly_default_in_group :: proc(t: ^testing.T) { - testing.expect_value(t, bar_group(123, describe), "#123") - testing.expect_value(t, bar_group("hi"), "#hi") - testing.expect_value(t, bar_group([]byte{1, 2}, describe_bytes), "bytes") + testing.expect_value(t, bar_group(123, describe), "#123") + testing.expect_value(t, bar_group("hi"), "#hi") + testing.expect_value(t, bar_group([]byte{1, 2}, describe_bytes), "bytes") } From 21fd0c4645467c3259ccf7fb3b755b2d66cdcba9 Mon Sep 17 00:00:00 2001 From: Brody Date: Wed, 5 Aug 2026 00:40:13 +1000 Subject: [PATCH 10/42] fix: bad merge --- tests/issues/run.sh | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 62213e0c9..0d41652c2 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -81,15 +81,8 @@ else echo "SUCCESSFUL 0/1" exit 1 fi -$ODIN check ../test_issue_6484.odin -no-entry-point $COMMON -$ODIN test ../test_issue_6753.odin $COMMON -if [[ $($ODIN check ../test_issue_6874.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]] ; then $ODIN check ../test_issue_6484.odin -no-entry-point $COMMON_CHECK - echo "SUCCESSFUL 1/1" -else - echo "SUCCESSFUL 0/1" - exit 1 -fi +$ODIN test ../test_issue_6753.odin $COMMON if [[ $($ODIN check ../test_issue_6874.odin $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]] ; then echo "SUCCESSFUL 1/1" else From 1fd3cbb0b54319c8db72eb7b9c78d30fdc22b255 Mon Sep 17 00:00:00 2001 From: Alexander Zhura Date: Tue, 4 Aug 2026 23:50:47 +0300 Subject: [PATCH 11/42] Impl simd arm neon bit manipulation --- core/simd/arm/neon.odin | 596 +++++++++++++++++++++++++++++++++++++++ core/simd/arm/pmull.odin | 72 +++++ core/simd/arm/types.odin | 26 +- 3 files changed, 689 insertions(+), 5 deletions(-) create mode 100644 core/simd/arm/neon.odin diff --git a/core/simd/arm/neon.odin b/core/simd/arm/neon.odin new file mode 100644 index 000000000..512449d27 --- /dev/null +++ b/core/simd/arm/neon.odin @@ -0,0 +1,596 @@ +#+build arm64,arm32 +package simd_arm + +import "core:simd" + +// Count leading sign bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcls_s8) +@(require_results, enable_target_feature = "neon") +vcls_s8 :: #force_inline proc "c" (a: int8x8_t) -> int8x8_t { + return _vcls_s8(a) +} + +// Count leading sign bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcls_s16) +@(require_results, enable_target_feature = "neon") +vcls_s16 :: #force_inline proc "c" (a: int16x4_t) -> int16x4_t { + return _vcls_s16(a) +} + +// Count leading sign bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcls_s32) +@(require_results, enable_target_feature = "neon") +vcls_s32 :: #force_inline proc "c" (a: int32x2_t) -> int32x2_t { + return _vcls_s32(a) +} + +// Count leading sign bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcls_u8) +@(require_results, enable_target_feature = "neon") +vcls_u8 :: #force_inline proc "c" (a: uint8x8_t) -> int8x8_t { + return vcls_s8(transmute(int8x8_t)a) +} + +// Count leading sign bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcls_u16) +@(require_results, enable_target_feature = "neon") +vcls_u16 :: #force_inline proc "c" (a: uint16x4_t) -> int16x4_t { + return vcls_s16(transmute(int16x4_t)a) +} + +// Count leading sign bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcls_u32) +@(require_results, enable_target_feature = "neon") +vcls_u32 :: #force_inline proc "c" (a: uint32x2_t) -> int32x2_t { + return vcls_s32(transmute(int32x2_t)a) +} + +// Count leading sign bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclsq_s8) +@(require_results, enable_target_feature = "neon") +vclsq_s8 :: #force_inline proc "c" (a: int8x16_t) -> int8x16_t { + return _vclsq_s8(a) +} + +// Count leading sign bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclsq_s16) +@(require_results, enable_target_feature = "neon") +vclsq_s16 :: #force_inline proc "c" (a: int16x8_t) -> int16x8_t { + return _vclsq_s16(a) +} + +// Count leading sign bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclsq_s32) +@(require_results, enable_target_feature = "neon") +vclsq_s32 :: #force_inline proc "c" (a: int32x4_t) -> int32x4_t { + return _vclsq_s32(a) +} + +// Count leading sign bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclsq_u8) +@(require_results, enable_target_feature = "neon") +vclsq_u8 :: #force_inline proc "c" (a: uint8x16_t) -> int8x16_t { + return vclsq_s8(transmute(int8x16_t)a) +} + +// Count leading sign bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclsq_u16) +@(require_results, enable_target_feature = "neon") +vclsq_u16 :: #force_inline proc "c" (a: uint16x8_t) -> int16x8_t { + return vclsq_s16(transmute(int16x8_t)a) +} + +// Count leading sign bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclsq_u32) +@(require_results, enable_target_feature = "neon") +vclsq_u32 :: #force_inline proc "c" (a: uint32x4_t) -> int32x4_t { + return vclsq_s32(transmute(int32x4_t)a) +} + +// Count leading zero bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclz_s8) +@(require_results, enable_target_feature = "neon") +vclz_s8 :: #force_inline proc "c" (a: int8x8_t) -> int8x8_t { + return simd.count_leading_zeros(a) +} + +// Count leading zero bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclz_s16) +@(require_results, enable_target_feature = "neon") +vclz_s16 :: #force_inline proc "c" (a: int16x4_t) -> int16x4_t { + return simd.count_leading_zeros(a) +} + +// Count leading zero bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclz_s32) +@(require_results, enable_target_feature = "neon") +vclz_s32 :: #force_inline proc "c" (a: int32x2_t) -> int32x2_t { + return simd.count_leading_zeros(a) +} + +// Count leading zero bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclz_u8) +@(require_results, enable_target_feature = "neon") +vclz_u8 :: #force_inline proc "c" (a: uint8x8_t) -> uint8x8_t { + return transmute(uint8x8_t)vclz_s8(transmute(int8x8_t)a) +} + +// Count leading zero bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclz_u16) +@(require_results, enable_target_feature = "neon") +vclz_u16 :: #force_inline proc "c" (a: uint16x4_t) -> uint16x4_t { + return transmute(uint16x4_t)vclz_s16(transmute(int16x4_t)a) +} + +// Count leading zero bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclz_u32) +@(require_results, enable_target_feature = "neon") +vclz_u32 :: #force_inline proc "c" (a: uint32x2_t) -> uint32x2_t { + return transmute(uint32x2_t)vclz_s32(transmute(int32x2_t)a) +} + +// Count leading zero bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclzq_s8) +@(require_results, enable_target_feature = "neon") +vclzq_s8 :: #force_inline proc "c" (a: int8x16_t) -> int8x16_t { + return simd.count_leading_zeros(a) +} + +// Count leading zero bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclzq_s16) +@(require_results, enable_target_feature = "neon") +vclzq_s16 :: #force_inline proc "c" (a: int16x8_t) -> int16x8_t { + return simd.count_leading_zeros(a) +} + +// Count leading zero bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclzq_s32) +@(require_results, enable_target_feature = "neon") +vclzq_s32 :: #force_inline proc "c" (a: int32x4_t) -> int32x4_t { + return simd.count_leading_zeros(a) +} + +// Count leading zero bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclzq_u8) +@(require_results, enable_target_feature = "neon") +vclzq_u8 :: #force_inline proc "c" (a: uint8x16_t) -> uint8x16_t { + return transmute(uint8x16_t)vclzq_s8(transmute(int8x16_t)a) +} + +// Count leading zero bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclzq_u16) +@(require_results, enable_target_feature = "neon") +vclzq_u16 :: #force_inline proc "c" (a: uint16x8_t) -> uint16x8_t { + return transmute(uint16x8_t)vclzq_s16(transmute(int16x8_t)a) +} + +// Count leading zero bits. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vclzq_u32) +@(require_results, enable_target_feature = "neon") +vclzq_u32 :: #force_inline proc "c" (a: uint32x4_t) -> uint32x4_t { + return transmute(uint32x4_t)vclzq_s32(transmute(int32x4_t)a) +} + +// Population count per byte. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcnt_s8) +@(require_results, enable_target_feature = "neon") +vcnt_s8 :: #force_inline proc "c" (a: int8x8_t) -> int8x8_t { + return simd.count_ones(a) +} + +// Population count per byte. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcnt_u8) +@(require_results, enable_target_feature = "neon") +vcnt_u8 :: #force_inline proc "c" (a: uint8x8_t) -> uint8x8_t { + return transmute(uint8x8_t)vcnt_s8(transmute(int8x8_t)a) +} + +// Population count per byte. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcnt_p8) +@(require_results, enable_target_feature = "neon") +vcnt_p8 :: #force_inline proc "c" (a: poly8x8_t) -> poly8x8_t { + return transmute(poly8x8_t)vcnt_s8(transmute(int8x8_t)a) +} + +// Population count per byte. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcntq_s8) +@(require_results, enable_target_feature = "neon") +vcntq_s8 :: #force_inline proc "c" (a: int8x16_t) -> int8x16_t { + return simd.count_ones(a) +} + +// Population count per byte. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcntq_u8) +@(require_results, enable_target_feature = "neon") +vcntq_u8 :: #force_inline proc "c" (a: uint8x16_t) -> uint8x16_t { + return transmute(uint8x16_t)vcntq_s8(transmute(int8x16_t)a) +} + +// Population count per byte. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcntq_p8) +@(require_results, enable_target_feature = "neon") +vcntq_p8 :: #force_inline proc "c" (a: poly8x16_t) -> poly8x16_t { + return transmute(poly8x16_t)vcntq_s8(transmute(int8x16_t)a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbic_s8) +@(require_results, enable_target_feature = "neon") +vbic_s8 :: #force_inline proc "c" (a: int8x8_t, b: int8x8_t) -> int8x8_t { + c := int8x8_t(-1) + return simd.bit_and(simd.bit_xor(b, c), a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbic_s16) +@(require_results, enable_target_feature = "neon") +vbic_s16 :: #force_inline proc "c" (a: int16x4_t, b: int16x4_t) -> int16x4_t { + c := int16x4_t(-1) + return simd.bit_and(simd.bit_xor(b, c), a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbic_s32) +@(require_results, enable_target_feature = "neon") +vbic_s32 :: #force_inline proc "c" (a: int32x2_t, b: int32x2_t) -> int32x2_t { + c := int32x2_t(-1) + return simd.bit_and(simd.bit_xor(b, c), a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbic_s64) +@(require_results, enable_target_feature = "neon") +vbic_s64 :: #force_inline proc "c" (a: int64x1_t, b: int64x1_t) -> int64x1_t { + c := int64x1_t(-1) + return simd.bit_and(simd.bit_xor(b, c), a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbic_u8) +@(require_results, enable_target_feature = "neon") +vbic_u8 :: #force_inline proc "c" (a: uint8x8_t, b: uint8x8_t) -> uint8x8_t { + c := int8x8_t(-1) + return simd.bit_and(simd.bit_xor(b, transmute(uint8x8_t)c), a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbic_u16) +@(require_results, enable_target_feature = "neon") +vbic_u16 :: #force_inline proc "c" (a: uint16x4_t, b: uint16x4_t) -> uint16x4_t { + c := int16x4_t(-1) + return simd.bit_and(simd.bit_xor(b, transmute(uint16x4_t)c), a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbic_u32) +@(require_results, enable_target_feature = "neon") +vbic_u32 :: #force_inline proc "c" (a: uint32x2_t, b: uint32x2_t) -> uint32x2_t { + c := int32x2_t(-1) + return simd.bit_and(simd.bit_xor(b, transmute(uint32x2_t)c), a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbic_u64) +@(require_results, enable_target_feature = "neon") +vbic_u64 :: #force_inline proc "c" (a: uint64x1_t, b: uint64x1_t) -> uint64x1_t { + c := int64x1_t(-1) + return simd.bit_and(simd.bit_xor(b, transmute(uint64x1_t)c), a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbicq_s8) +@(require_results, enable_target_feature = "neon") +vbicq_s8 :: #force_inline proc "c" (a: int8x16_t, b: int8x16_t) -> int8x16_t { + c := int8x16_t(-1) + return simd.bit_and(simd.bit_xor(b, c), a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbicq_s16) +@(require_results, enable_target_feature = "neon") +vbicq_s16 :: #force_inline proc "c" (a: int16x8_t, b: int16x8_t) -> int16x8_t { + c := int16x8_t(-1) + return simd.bit_and(simd.bit_xor(b, c), a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbicq_s32) +@(require_results, enable_target_feature = "neon") +vbicq_s32 :: #force_inline proc "c" (a: int32x4_t, b: int32x4_t) -> int32x4_t { + c := int32x4_t(-1) + return simd.bit_and(simd.bit_xor(b, c), a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbicq_s64) +@(require_results, enable_target_feature = "neon") +vbicq_s64 :: #force_inline proc "c" (a: int64x2_t, b: int64x2_t) -> int64x2_t { + c := int64x2_t(-1) + return simd.bit_and(simd.bit_xor(b, c), a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbicq_u8) +@(require_results, enable_target_feature = "neon") +vbicq_u8 :: #force_inline proc "c" (a: uint8x16_t, b: uint8x16_t) -> uint8x16_t { + c := int8x16_t(-1) + return simd.bit_and(simd.bit_xor(b, transmute(uint8x16_t)c), a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbicq_u16) +@(require_results, enable_target_feature = "neon") +vbicq_u16 :: #force_inline proc "c" (a: uint16x8_t, b: uint16x8_t) -> uint16x8_t { + c := int16x8_t(-1) + return simd.bit_and(simd.bit_xor(b, transmute(uint16x8_t)c), a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbicq_u32) +@(require_results, enable_target_feature = "neon") +vbicq_u32 :: #force_inline proc "c" (a: uint32x4_t, b: uint32x4_t) -> uint32x4_t { + c := int32x4_t(-1) + return simd.bit_and(simd.bit_xor(b, transmute(uint32x4_t)c), a) +} + +// Vector bitwise bit clear. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbicq_u64) +@(require_results, enable_target_feature = "neon") +vbicq_u64 :: #force_inline proc "c" (a: uint64x2_t, b: uint64x2_t) -> uint64x2_t { + c := int64x2_t(-1) + return simd.bit_and(simd.bit_xor(b, transmute(uint64x2_t)c), a) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbsl_s8) +@(require_results, enable_target_feature = "neon") +vbsl_s8 :: #force_inline proc "c" (a: uint8x8_t, b: int8x8_t, c: int8x8_t) -> int8x8_t { + not := int8x8_t(-1) + return transmute(int8x8_t)simd.bit_or( + simd.bit_and(a, transmute(uint8x8_t)b), + simd.bit_and(simd.bit_xor(a, transmute(uint8x8_t)not), transmute(uint8x8_t)c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbsl_s16) +@(require_results, enable_target_feature = "neon") +vbsl_s16 :: #force_inline proc "c" (a: uint16x4_t, b: int16x4_t, c: int16x4_t) -> int16x4_t { + not := int16x4_t(-1) + return transmute(int16x4_t)simd.bit_or( + simd.bit_and(a, transmute(uint16x4_t)b), + simd.bit_and(simd.bit_xor(a, transmute(uint16x4_t)not), transmute(uint16x4_t)c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbsl_s32) +@(require_results, enable_target_feature = "neon") +vbsl_s32 :: #force_inline proc "c" (a: uint32x2_t, b: int32x2_t, c: int32x2_t) -> int32x2_t { + not := int32x2_t(-1) + return transmute(int32x2_t)simd.bit_or( + simd.bit_and(a, transmute(uint32x2_t)b), + simd.bit_and(simd.bit_xor(a, transmute(uint32x2_t)not), transmute(uint32x2_t)c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbsl_s64) +@(require_results, enable_target_feature = "neon") +vbsl_s64 :: #force_inline proc "c" (a: uint64x1_t, b: int64x1_t, c: int64x1_t) -> int64x1_t { + not := int64x1_t(-1) + return transmute(int64x1_t)simd.bit_or( + simd.bit_and(a, transmute(uint64x1_t)b), + simd.bit_and(simd.bit_xor(a, transmute(uint64x1_t)not), transmute(uint64x1_t)c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbsl_u8) +@(require_results, enable_target_feature = "neon") +vbsl_u8 :: #force_inline proc "c" (a: uint8x8_t, b: uint8x8_t, c: uint8x8_t) -> uint8x8_t { + not := int8x8_t(-1) + return simd.bit_or( + simd.bit_and(a, b), + simd.bit_and(simd.bit_xor(a, transmute(uint8x8_t)not), c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbsl_u16) +@(require_results, enable_target_feature = "neon") +vbsl_u16 :: #force_inline proc "c" (a: uint16x4_t, b: uint16x4_t, c: uint16x4_t) -> uint16x4_t { + not := int16x4_t(-1) + return simd.bit_or( + simd.bit_and(a, b), + simd.bit_and(simd.bit_xor(a, transmute(uint16x4_t)not), c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbsl_u32) +@(require_results, enable_target_feature = "neon") +vbsl_u32 :: #force_inline proc "c" (a: uint32x2_t, b: uint32x2_t, c: uint32x2_t) -> uint32x2_t { + not := int32x2_t(-1) + return simd.bit_or( + simd.bit_and(a, b), + simd.bit_and(simd.bit_xor(a, transmute(uint32x2_t)not), c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbsl_u64) +@(require_results, enable_target_feature = "neon") +vbsl_u64 :: #force_inline proc "c" (a: uint64x1_t, b: uint64x1_t, c: uint64x1_t) -> uint64x1_t { + not := int64x1_t(-1) + return simd.bit_or( + simd.bit_and(a, b), + simd.bit_and(simd.bit_xor(a, transmute(uint64x1_t)not), c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbslq_s8) +@(require_results, enable_target_feature = "neon") +vbslq_s8 :: #force_inline proc "c" (a: uint8x16_t, b: int8x16_t, c: int8x16_t) -> int8x16_t { + not := int8x16_t(-1) + return transmute(int8x16_t)simd.bit_or( + simd.bit_and(a, transmute(uint8x16_t)b), + simd.bit_and(simd.bit_xor(a, transmute(uint8x16_t)not), transmute(uint8x16_t)c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbslq_s16) +@(require_results, enable_target_feature = "neon") +vbslq_s16 :: #force_inline proc "c" (a: uint16x8_t, b: int16x8_t, c: int16x8_t) -> int16x8_t { + not := int16x8_t(-1) + return transmute(int16x8_t)simd.bit_or( + simd.bit_and(a, transmute(uint16x8_t)b), + simd.bit_and(simd.bit_xor(a, transmute(uint16x8_t)not), transmute(uint16x8_t)c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbslq_s32) +@(require_results, enable_target_feature = "neon") +vbslq_s32 :: #force_inline proc "c" (a: uint32x4_t, b: int32x4_t, c: int32x4_t) -> int32x4_t { + not := int32x4_t(-1) + return transmute(int32x4_t)simd.bit_or( + simd.bit_and(a, transmute(uint32x4_t)b), + simd.bit_and(simd.bit_xor(a, transmute(uint32x4_t)not), transmute(uint32x4_t)c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbslq_s64) +@(require_results, enable_target_feature = "neon") +vbslq_s64 :: #force_inline proc "c" (a: uint64x2_t, b: int64x2_t, c: int64x2_t) -> int64x2_t { + not := int64x2_t(-1) + return transmute(int64x2_t)simd.bit_or( + simd.bit_and(a, transmute(uint64x2_t)b), + simd.bit_and(simd.bit_xor(a, transmute(uint64x2_t)not), transmute(uint64x2_t)c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbslq_u8) +@(require_results, enable_target_feature = "neon") +vbslq_u8 :: #force_inline proc "c" (a: uint8x16_t, b: uint8x16_t, c: uint8x16_t) -> uint8x16_t { + not := int8x16_t(-1) + return simd.bit_or( + simd.bit_and(a, b), + simd.bit_and(simd.bit_xor(a, transmute(uint8x16_t)not), c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbslq_u16) +@(require_results, enable_target_feature = "neon") +vbslq_u16 :: #force_inline proc "c" (a: uint16x8_t, b: uint16x8_t, c: uint16x8_t) -> uint16x8_t { + not := int16x8_t(-1) + return simd.bit_or( + simd.bit_and(a, b), + simd.bit_and(simd.bit_xor(a, transmute(uint16x8_t)not), c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbslq_u32) +@(require_results, enable_target_feature = "neon") +vbslq_u32 :: #force_inline proc "c" (a: uint32x4_t, b: uint32x4_t, c: uint32x4_t) -> uint32x4_t { + not := int32x4_t(-1) + return simd.bit_or( + simd.bit_and(a, b), + simd.bit_and(simd.bit_xor(a, transmute(uint32x4_t)not), c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbslq_u64) +@(require_results, enable_target_feature = "neon") +vbslq_u64 :: #force_inline proc "c" (a: uint64x2_t, b: uint64x2_t, c: uint64x2_t) -> uint64x2_t { + not := int64x2_t(-1) + return simd.bit_or( + simd.bit_and(a, b), + simd.bit_and(simd.bit_xor(a, transmute(uint64x2_t)not), c), + ) +} + +@(private, default_calling_convention = "none") +foreign _ { + @(link_name = "llvm.aarch64.neon.cls.v8i8" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vcls.v8i8") + _vcls_s8 :: proc(a: int8x8_t) -> int8x8_t --- + @(link_name = "llvm.aarch64.neon.cls.v4i16" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vcls.v4i16") + _vcls_s16 :: proc(a: int16x4_t) -> int16x4_t --- + @(link_name = "llvm.aarch64.neon.cls.v2i32" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vcls.v2i32") + _vcls_s32 :: proc(a: int32x2_t) -> int32x2_t --- + @(link_name = "llvm.aarch64.neon.cls.v16i8" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vcls.v16i8") + _vclsq_s8 :: proc(a: int8x16_t) -> int8x16_t --- + @(link_name = "llvm.aarch64.neon.cls.v8i16" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vcls.v8i16") + _vclsq_s16 :: proc(a: int16x8_t) -> int16x8_t --- + @(link_name = "llvm.aarch64.neon.cls.v4i32" when ODIN_ARCH == .arm64 else "llvm.arm.neon.vcls.v4i32") + _vclsq_s32 :: proc(a: int32x4_t) -> int32x4_t --- +} diff --git a/core/simd/arm/pmull.odin b/core/simd/arm/pmull.odin index 8d0707da9..023d4022a 100644 --- a/core/simd/arm/pmull.odin +++ b/core/simd/arm/pmull.odin @@ -3,6 +3,78 @@ package simd_arm import "core:simd" +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbsl_p8) +@(require_results, enable_target_feature = "neon") +vbsl_p8 :: #force_inline proc "c" (a: uint8x8_t, b: poly8x8_t, c: poly8x8_t) -> poly8x8_t { + not := int8x8_t(-1) + return simd.bit_or( + simd.bit_and(a, b), + simd.bit_and(simd.bit_xor(a, transmute(uint8x8_t)not), c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbsl_p16) +@(require_results, enable_target_feature = "neon") +vbsl_p16 :: #force_inline proc "c" (a: uint16x4_t, b: poly16x4_t, c: poly16x4_t) -> poly16x4_t { + not := int16x4_t(-1) + return simd.bit_or( + simd.bit_and(a, b), + simd.bit_and(simd.bit_xor(a, transmute(uint16x4_t)not), c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbsl_p64) +@(require_results, enable_target_feature = "neon") +vbsl_p64 :: #force_inline proc "c" (a: poly64x1_t, b: poly64x1_t, c: poly64x1_t) -> poly64x1_t { + not := int64x1_t(-1) + return simd.bit_or( + simd.bit_and(a, b), + simd.bit_and(simd.bit_xor(a, transmute(poly64x1_t)not), c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbslq_p8) +@(require_results, enable_target_feature = "neon") +vbslq_p8 :: #force_inline proc "c" (a: uint8x16_t, b: poly8x16_t, c: poly8x16_t) -> poly8x16_t { + not := int8x16_t(-1) + return simd.bit_or( + simd.bit_and(a, b), + simd.bit_and(simd.bit_xor(a, transmute(poly8x16_t)not), c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbslq_p16) +@(require_results, enable_target_feature = "neon") +vbslq_p16 :: #force_inline proc "c" (a: uint16x8_t, b: poly16x8_t, c: poly16x8_t) -> poly16x8_t { + not := int16x8_t(-1) + return simd.bit_or( + simd.bit_and(a, b), + simd.bit_and(simd.bit_xor(a, transmute(poly16x8_t)not), c), + ) +} + +// Bitwise Select. +// +// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vbslq_p64) +@(require_results, enable_target_feature = "neon") +vbslq_p64 :: #force_inline proc "c" (a: poly64x2_t, b: poly64x2_t, c: poly64x2_t) -> poly64x2_t { + not := int64x2_t(-1) + return simd.bit_or( + simd.bit_and(a, b), + simd.bit_and(simd.bit_xor(a, transmute(poly64x2_t)not), c), + ) +} + // Join two smaller vectors into a single larger vector // // [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/vcombine_p8) diff --git a/core/simd/arm/types.odin b/core/simd/arm/types.odin index 9379449e3..f60edeaa8 100644 --- a/core/simd/arm/types.odin +++ b/core/simd/arm/types.odin @@ -2,6 +2,11 @@ package simd_arm // Type aliases to match `arm_neon.h`. +int8_t :: i8 +int16_t :: i16 +int32_t :: i32 +int64_t :: i64 + uint8_t :: u8 uint16_t :: u16 uint32_t :: u32 @@ -12,12 +17,23 @@ poly16_t :: u16 poly64_t :: u64 poly128_t :: u128 -uint8x16_t :: #simd[16]u8 -uint32x4_t :: #simd[4]u32 -uint64x2_t :: #simd[2]u64 +int8x8_t :: #simd[8]int8_t +int8x16_t :: #simd[16]int8_t +int16x4_t :: #simd[4]int16_t +int16x8_t :: #simd[8]int16_t +int32x2_t :: #simd[2]int32_t +int32x4_t :: #simd[4]int32_t +int64x1_t :: #simd[1]int64_t +int64x2_t :: #simd[2]int64_t -int32_t :: i32 -int8x16_t :: #simd[16]i8 +uint8x8_t :: #simd[8]uint8_t +uint8x16_t :: #simd[16]uint8_t +uint16x4_t :: #simd[4]uint16_t +uint16x8_t :: #simd[8]uint16_t +uint32x2_t :: #simd[2]uint32_t +uint32x4_t :: #simd[4]uint32_t +uint64x1_t :: #simd[1]uint64_t +uint64x2_t :: #simd[2]uint64_t poly8x8_t :: #simd[8]poly8_t poly8x16_t :: #simd[16]poly8_t From 91aec259c00920a15d92fff38b5d9e05b8b2631d Mon Sep 17 00:00:00 2001 From: kalsprite Date: Tue, 4 Aug 2026 19:15:24 -0700 Subject: [PATCH 12/42] crash fix on poly --- src/check_expr.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 5cbb15d1f..c0e5707cc 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7605,7 +7605,9 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, if (max_matched_features > 0) { for_array(i, valids) { - Entity *p = procs[valids[i].index]; + // NOTE: A polymorphic candidate appends its instantiated entity to proc_entities above, + // so valids[i].index can be >= procs.count. + Entity *p = proc_entities[valids[i].index]; Type *t = base_type(p->type); GB_ASSERT(t->kind == Type_Proc); From 6e682a768ec1278157502e1d6a145439dc6a49cf Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Wed, 5 Aug 2026 17:31:31 +0900 Subject: [PATCH 13/42] core/crypto/pbkdf2: Ensure non-zero iteration count --- core/crypto/pbkdf2/pbkdf2.odin | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/crypto/pbkdf2/pbkdf2.odin b/core/crypto/pbkdf2/pbkdf2.odin index c27ec4aa2..ef7fda0d5 100644 --- a/core/crypto/pbkdf2/pbkdf2.odin +++ b/core/crypto/pbkdf2/pbkdf2.odin @@ -19,6 +19,8 @@ derive :: proc( iterations: u32, dst: []byte, ) { + ensure(iterations > 0, "crypto/pbkdf2: non-zero iterations required") + h_len := hash.DIGEST_SIZES[hmac_hash] // 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" From 3bdb32949b873293de8c05698ada0c1668b6c4ca Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Wed, 5 Aug 2026 17:33:31 +0900 Subject: [PATCH 14/42] core/crypto/noise: Reject encrypt/decrypt when `n` == 2^64 - 1 Spec defines the maximal value of `n` as reserved. --- core/crypto/noise/protocol.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/crypto/noise/protocol.odin b/core/crypto/noise/protocol.odin index 7327e638b..4bbe858da 100644 --- a/core/crypto/noise/protocol.odin +++ b/core/crypto/noise/protocol.odin @@ -217,7 +217,7 @@ cipherstate_encrypt_with_ad :: proc(self: ^Cipher_State, ad, plaintext, dst: []b } _encrypt(&self.ctx, self.n, ad, plaintext, dst) self.n += 1 - if self.n == 0 { + if self.n == max(u64) { self.n_exhausted = true } } else { @@ -249,7 +249,7 @@ cipherstate_decrypt_with_ad :: proc(self: ^Cipher_State, ad, ciphertext, dst: [] return nil, status } self.n += 1 - if self.n == 0 { + if self.n == max(u64) { self.n_exhausted = true } } else { From 97b38169de37c3fe0a983daa9688082e087a45f7 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Wed, 5 Aug 2026 18:04:06 +0900 Subject: [PATCH 15/42] core/crypto/chacha20: Explicitly allow the one final block Only affects the incremental use case where the internal counter is exactly the maximum. --- core/crypto/_chacha20/chacha20.odin | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/crypto/_chacha20/chacha20.odin b/core/crypto/_chacha20/chacha20.odin index 7d94d8a95..40ca9fb25 100644 --- a/core/crypto/_chacha20/chacha20.odin +++ b/core/crypto/_chacha20/chacha20.odin @@ -1,5 +1,6 @@ package _chacha20 +import "base:intrinsics" import "core:crypto" import "core:encoding/endian" import "core:math/bits" @@ -108,11 +109,16 @@ check_counter_limit :: proc(ctx: ^Context, nr_blocks: int) { ctr_ok: bool if ctx._is_ietf_flavor { - ctr_ok = u64(ctx._s[12]) + u64(nr_blocks) <= MAX_CTR_IETF + if intrinsics.unlikely(ctx._s[12] == MAX_CTR_IETF && nr_blocks > 1) { + // Allow the final block. + ctr_ok = false + } else { + ctr_ok = u64(ctx._s[12]) + u64(nr_blocks) <= MAX_CTR_IETF + } } else { ctr := (u64(ctx._s[13]) << 32) | u64(ctx._s[12]) - _, carry := bits.add_u64(ctr, u64(nr_blocks), 0) - ctr_ok = carry == 0 + new_ctr, carry := bits.add_u64(ctr, u64(nr_blocks), 0) + ctr_ok = carry == 0 || new_ctr != 0 // Allow the final block. } ensure(ctr_ok, "crypto/chacha20: maximum (X)ChaCha20 keystream per IV reached") From a51cdd43bf5e64f5de9ab4b34306e474e5423b72 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Wed, 5 Aug 2026 18:07:32 +0900 Subject: [PATCH 16/42] core/crypto/kmac: Early reject pathologically short tags in verify --- core/crypto/kmac/kmac.odin | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/crypto/kmac/kmac.odin b/core/crypto/kmac/kmac.odin index f0c27739a..f09176edc 100644 --- a/core/crypto/kmac/kmac.odin +++ b/core/crypto/kmac/kmac.odin @@ -35,6 +35,10 @@ sum :: proc(sec_strength: int, dst, msg, key, domain_sep: []byte) { // strength, key and domain separator over msg and return true if and only if (⟺) the // tag is valid. verify :: proc(sec_strength: int, tag, msg, key, domain_sep: []byte, allocator := context.temp_allocator) -> bool { + if len(tag) < MIN_TAG_SIZE { + return false + } + derived_tag := make([]byte, len(tag), allocator) defer(delete(derived_tag)) From 65450b78abfbb8d678121c94d247a659e92d7b8f Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Wed, 5 Aug 2026 18:26:44 +0900 Subject: [PATCH 17/42] core/crypto/mlkem: Fix compiler error on `key_size(&Encapsualtion_Key)` --- core/crypto/mlkem/api.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/crypto/mlkem/api.odin b/core/crypto/mlkem/api.odin index 3e3956ba8..6cbbd3249 100644 --- a/core/crypto/mlkem/api.odin +++ b/core/crypto/mlkem/api.odin @@ -269,7 +269,7 @@ params :: proc(k: ^$T) -> Parameters where (T == Encapsulation_Key || T == Decap @(require_results) key_size :: proc(k: ^$T) -> int where (T == Encapsulation_Key || T == Decapsulation_Key) { when T == Encapsulation_Key { - return ENCAPSULATION_KEY_SIZES[k.pke_ek.k] + return ENCAPSULATION_KEY_SIZES[params(k)] } else { return DECAPSULATION_KEY_SEED_SIZE } From fd3d182ee5d66df6068f43330ae9c9924d13144a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 5 Aug 2026 12:12:08 +0200 Subject: [PATCH 18/42] Assuage NetBSD CI --- tests/core/math/rand/test_core_math_rand.odin | 6 ++++-- tests/core/slice/test_core_slice.odin | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/core/math/rand/test_core_math_rand.odin b/tests/core/math/rand/test_core_math_rand.odin index 5c2f4af84..965d315e1 100644 --- a/tests/core/math/rand/test_core_math_rand.odin +++ b/tests/core/math/rand/test_core_math_rand.odin @@ -10,7 +10,8 @@ Generator :: struct { biased: bool, } -@(test) +// Disable on NetBSD due to Illegal Instruction on CI, even with `microarch:native` +@(test, disabled=ODIN_OS == .NetBSD) test_prngs :: proc(t: ^testing.T) { gens := []Generator { { @@ -48,7 +49,8 @@ rand_determinism :: proc(t: ^testing.T, rng: Generator) { testing.expectf(t, first_value == second_value, "rng '%s' is non-deterministic.", rng.name) } -@(test) +// Disable on NetBSD due to Illegal Instruction on CI, even with `microarch:native` +@(test, disabled=ODIN_OS == .NetBSD) test_default_rand_determinism_user_set :: proc(t: ^testing.T) { rng_state_1 := rand.create(13) rng_state_2 := rand.create(13) diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index 98cb8dbac..eb630699f 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -225,7 +225,8 @@ UNIQUE_TEST_VECTORS :: [][2][]int{ {{1,2,4,4,5}, {1,2,4,5}}, } -@test +// Disable on NetBSD due to Illegal Instruction on CI, even with `microarch:native` +@(test, disabled=ODIN_OS == .NetBSD) test_unique :: proc(t: ^testing.T) { for v in UNIQUE_TEST_VECTORS { assorted := v[0] From ddf60b459dd7e3db2c6353420a53e21196d3a962 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 5 Aug 2026 12:28:39 +0200 Subject: [PATCH 19/42] #+build !netbsd --- tests/core/math/rand/test_core_math_rand.odin | 8 ++- tests/core/slice/not_netbsd.odin | 62 +++++++++++++++++++ tests/core/slice/test_core_slice.odin | 54 ---------------- 3 files changed, 67 insertions(+), 57 deletions(-) create mode 100644 tests/core/slice/not_netbsd.odin diff --git a/tests/core/math/rand/test_core_math_rand.odin b/tests/core/math/rand/test_core_math_rand.odin index 965d315e1..efc5c1be0 100644 --- a/tests/core/math/rand/test_core_math_rand.odin +++ b/tests/core/math/rand/test_core_math_rand.odin @@ -1,3 +1,4 @@ +#+build !netbsd package test_core_math_rand import "core:math" @@ -11,7 +12,9 @@ Generator :: struct { } // Disable on NetBSD due to Illegal Instruction on CI, even with `microarch:native` -@(test, disabled=ODIN_OS == .NetBSD) +// `@(test, disable="...")` still runs the test. + +@(test) test_prngs :: proc(t: ^testing.T) { gens := []Generator { { @@ -49,8 +52,7 @@ rand_determinism :: proc(t: ^testing.T, rng: Generator) { testing.expectf(t, first_value == second_value, "rng '%s' is non-deterministic.", rng.name) } -// Disable on NetBSD due to Illegal Instruction on CI, even with `microarch:native` -@(test, disabled=ODIN_OS == .NetBSD) +@(test) test_default_rand_determinism_user_set :: proc(t: ^testing.T) { rng_state_1 := rand.create(13) rng_state_2 := rand.create(13) diff --git a/tests/core/slice/not_netbsd.odin b/tests/core/slice/not_netbsd.odin new file mode 100644 index 000000000..f33be479d --- /dev/null +++ b/tests/core/slice/not_netbsd.odin @@ -0,0 +1,62 @@ +#+build !netbsd +package test_core_slice + +import "core:slice" +import "core:testing" +import "core:math/rand" + +// Disable on NetBSD due to Illegal Instruction on CI, even with `microarch:native` +// `@(test, disable="...")` still runs the test. + +@(test) +test_unique :: proc(t: ^testing.T) { + for v in UNIQUE_TEST_VECTORS { + assorted := v[0] + expected := v[1] + + uniq := slice.unique(assorted) + testing.expectf(t, slice.equal(uniq, expected), "Expected slice.uniq(%v) == %v, got %v", v[0], v[1], uniq) + } + + for v in UNIQUE_TEST_VECTORS { + assorted := v[0] + expected := v[1] + + uniq := slice.unique_proc(assorted, proc(a, b: int) -> bool { + return a == b + }) + testing.expectf(t, slice.equal(uniq, expected), "Expected slice.unique_proc(%v, ...) == %v, got %v", v[0], v[1], uniq) + } + + r := rand.create(t.seed) + context.random_generator = rand.default_random_generator(&r) + + // 10_000 random tests + for _ in 0..<10_000 { + assorted: [dynamic]i64 + expected: [dynamic]i64 + + // Prime with 1 value + old := rand.int63() + append(&assorted, old) + append(&expected, old) + + // Add 99 additional random values + for _ in 1..<100 { + new := rand.int63() + append(&assorted, new) + if old != new { + append(&expected, new) + } + old = new + } + + original := slice.clone(assorted[:]) + uniq := slice.unique(assorted[:]) + testing.expectf(t, slice.equal(uniq, expected[:]), "Expected slice.uniq(%v) == %v, got %v", original, expected, uniq) + + delete(assorted) + delete(original) + delete(expected) + } +} \ No newline at end of file diff --git a/tests/core/slice/test_core_slice.odin b/tests/core/slice/test_core_slice.odin index eb630699f..b0fb791a6 100644 --- a/tests/core/slice/test_core_slice.odin +++ b/tests/core/slice/test_core_slice.odin @@ -225,60 +225,6 @@ UNIQUE_TEST_VECTORS :: [][2][]int{ {{1,2,4,4,5}, {1,2,4,5}}, } -// Disable on NetBSD due to Illegal Instruction on CI, even with `microarch:native` -@(test, disabled=ODIN_OS == .NetBSD) -test_unique :: proc(t: ^testing.T) { - for v in UNIQUE_TEST_VECTORS { - assorted := v[0] - expected := v[1] - - uniq := slice.unique(assorted) - testing.expectf(t, slice.equal(uniq, expected), "Expected slice.uniq(%v) == %v, got %v", v[0], v[1], uniq) - } - - for v in UNIQUE_TEST_VECTORS { - assorted := v[0] - expected := v[1] - - uniq := slice.unique_proc(assorted, proc(a, b: int) -> bool { - return a == b - }) - testing.expectf(t, slice.equal(uniq, expected), "Expected slice.unique_proc(%v, ...) == %v, got %v", v[0], v[1], uniq) - } - - r := rand.create(t.seed) - context.random_generator = rand.default_random_generator(&r) - - // 10_000 random tests - for _ in 0..<10_000 { - assorted: [dynamic]i64 - expected: [dynamic]i64 - - // Prime with 1 value - old := rand.int63() - append(&assorted, old) - append(&expected, old) - - // Add 99 additional random values - for _ in 1..<100 { - new := rand.int63() - append(&assorted, new) - if old != new { - append(&expected, new) - } - old = new - } - - original := slice.clone(assorted[:]) - uniq := slice.unique(assorted[:]) - testing.expectf(t, slice.equal(uniq, expected[:]), "Expected slice.uniq(%v) == %v, got %v", original, expected, uniq) - - delete(assorted) - delete(original) - delete(expected) - } -} - @test test_compare_empty :: proc(t: ^testing.T) { a := []int{} From 7ff00d39bda9ac24ef6a5faea609784876bc678e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Moror=C3=B3?= Date: Wed, 5 Aug 2026 08:45:01 -0300 Subject: [PATCH 20/42] os: resolve relative path before SHFileOperationW in remove_all (windows) --- core/os/path_windows.odin | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/os/path_windows.odin b/core/os/path_windows.odin index 83100ad69..753f1665f 100644 --- a/core/os/path_windows.odin +++ b/core/os/path_windows.odin @@ -84,7 +84,13 @@ _remove_all :: proc(path: string) -> Error { } temp_allocator := TEMP_ALLOCATOR_GUARD({}) - dir := win32_utf8_to_wstring(path, temp_allocator) or_return + + // SHFileOperationW is documented as not thread safe with relative paths. + abs_path := path + if !_is_absolute_path(path) { + abs_path = _get_absolute_path(path, temp_allocator) or_return + } + dir := win32_utf8_to_wstring(abs_path, temp_allocator) or_return empty: [1]u16 From 5629c13aa0b971cb13a432ea42d83e887a953ae7 Mon Sep 17 00:00:00 2001 From: Sylphrena Date: Tue, 4 Aug 2026 12:36:20 +0200 Subject: [PATCH 21/42] Fix -vet-packages not working in certain cases --- src/parser.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/parser.cpp b/src/parser.cpp index a527bc7ec..37a9a6d9f 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -6,13 +6,35 @@ gb_internal bool in_vet_packages(AstFile *file) { if (file == nullptr) { return true; } + if (file->pkg == nullptr) { return true; } + + if (file->pkg_decl == nullptr) { + return true; + } + if (build_context.vet_packages.entries.count == 0) { return true; } - return string_set_exists(&build_context.vet_packages, file->pkg->name); + + String pkg_name = {}; + + if (file->pkg->name.len > 0) { + pkg_name = file->pkg->name; + } else if (file->pkg_decl->kind == Ast_PackageDecl) { + Token name_token = file->pkg_decl->PackageDecl.name; + if (name_token.kind == Token_Ident) { + pkg_name = name_token.string; + } + } + + if (pkg_name.len == 0) { + return true; + } + + return string_set_exists(&build_context.vet_packages, pkg_name); } gb_internal u64 ast_file_vet_flags(AstFile *f) { From 95c80dda3483bd5d368971255b0fb0605c392424 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 5 Aug 2026 14:12:27 +0200 Subject: [PATCH 22/42] Fix #7177 Box3d build script on Linux preserved (and thus duplicated) some symbols. --- vendor/box3d/lib/linux-amd64/libbox3d.a | 4 ++-- vendor/box3d/src/build.sh | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/vendor/box3d/lib/linux-amd64/libbox3d.a b/vendor/box3d/lib/linux-amd64/libbox3d.a index 402424fa2..42e9a56cd 100644 --- a/vendor/box3d/lib/linux-amd64/libbox3d.a +++ b/vendor/box3d/lib/linux-amd64/libbox3d.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d698fbbe655db1d1ec6f52dcc23bfed4a2c981fa88040ba449f9f83609edba88 -size 3137140 +oid sha256:ad5491b77cda430ee03adb68bd6b4d56d3f7d8941327643eecc2b636496acda0 +size 1601114 diff --git a/vendor/box3d/src/build.sh b/vendor/box3d/src/build.sh index 60cfd41f1..0513d8ea5 100755 --- a/vendor/box3d/src/build.sh +++ b/vendor/box3d/src/build.sh @@ -70,12 +70,19 @@ Darwin) ;; Linux) LIB_DIR="../lib/linux-$ARCH" - mkdir -p "$LIB_DIR" - $cc -c -O2 -std=c17 -fPIC -Iinclude src/*.c - $ar rcs "$LIB_DIR/$LIB_NAME" ./*.o - rm ./*.o + mkdir -p "$LIB_DIR" build + for src in src/*.c; do + obj="build/$(basename "${src%.c}.o")" + $cc -c -O2 -std=c17 -fPIC -Iinclude "$src" -o "$obj" + done + # Clean up old library in case `ar` is tempted to preserve old symbols + rm -f "$LIB_DIR/$LIB_NAME" + $ar rcs "$LIB_DIR/$LIB_NAME" build/*.o + $ranlib "$LIB_DIR/$LIB_NAME" + rm -rf build ;; *) + echo "Error: Unsupported operating system: $(uname -s)" exit 1 ;; From 9caff6370fb65169e606180a4793c4e6bee9d9a5 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Wed, 5 Aug 2026 19:02:35 +0200 Subject: [PATCH 23/42] Typo --- base/runtime/random_generator_chacha8_ref.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/runtime/random_generator_chacha8_ref.odin b/base/runtime/random_generator_chacha8_ref.odin index b1e812c3f..e3e1d5768 100644 --- a/base/runtime/random_generator_chacha8_ref.odin +++ b/base/runtime/random_generator_chacha8_ref.odin @@ -23,7 +23,7 @@ chacha8rand_refill_ref :: proc(r: ^Default_Random_State) { s8 := intrinsics.byte_swap(k[4]) s9 := intrinsics.byte_swap(k[5]) s10 := intrinsics.byte_swap(k[6]) - s11 := intrinicss.byte_swap(k[7]) + s11 := intrinsics.byte_swap(k[7]) } s12: u32 // Counter starts at 0. s13, s14, s15: u32 // IV of all 0s. From f7f95ad76306f57282bcb6ceb42b3b576c71350a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Tesa=C5=99?= Date: Wed, 5 Aug 2026 23:40:57 +0200 Subject: [PATCH 24/42] Add percent_encode_test to test percent --- tests/core/net/test_core_net.odin | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index fbca15bb1..6a6111a29 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -304,7 +304,7 @@ client_sends_server_data :: proc(t: ^testing.T) { r.length, r.err = net.recv_tcp(client, r.data[:]) return } - + thread_data := [2]Thread_Data{} wg: sync.Wait_Group @@ -313,7 +313,7 @@ client_sends_server_data :: proc(t: ^testing.T) { thread_data[0].t = t thread_data[0].wg = &wg thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context) - + sync.wait_group_wait(&wg) sync.wait_group_add(&wg, 2) @@ -525,6 +525,31 @@ join_url_test :: proc(t: ^testing.T) { } } +@test +percent_encode_test :: proc(t: ^testing.T) { + test_cases := []struct{input, expected: string} { + // Bytes < 0x10 must be zero-padded to two hex digits + {"\n", "%0A"}, + {"\t", "%09"}, + {"\r", "%0D"}, + {"a\nb", "a%0Ab"}, + {"\x00", "%00"}, + + // Bytes >= 0x10 + {" ", "%20"}, + {"😃", "%F0%9F%98%83"}, + + // Unreserved characters pass through unescaped + {"AZaz09-_.~", "AZaz09-_.~"}, + } + + for test in test_cases { + encoded := net.percent_encode(test.input) + defer delete(encoded) + testing.expectf(t, encoded == test.expected, "Expected `net.percent_encode(%q)` to return %q, got %q", test.input, test.expected, encoded) + } +} + @test test_udp_echo :: proc(t: ^testing.T) { endpoint := net.Endpoint{address=net.IP4_Address{127, 0, 0, 1}, port=0} From c7af2576f56ee31730fb41c96c6711c35c06321e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Tesa=C5=99?= Date: Wed, 5 Aug 2026 23:45:34 +0200 Subject: [PATCH 25/42] Zero-pad percent_encode escapes to two uppercase hex digits --- core/net/url.odin | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/net/url.odin b/core/net/url.odin index 29028b16c..1a8028ad0 100644 --- a/core/net/url.odin +++ b/core/net/url.odin @@ -114,6 +114,8 @@ join_url :: proc(scheme, host, path: string, queries: map[string]string, fragmen } percent_encode :: proc(s: string, allocator := context.allocator) -> string { + HEX_DIGITS_UPPER := "0123456789ABCDEF" // NOTE(michtesar): RFC 3986 §2.1 + b := strings.builder_make(allocator) strings.builder_grow(&b, len(s) + 16) // NOTE(tetra): A reasonable number to allow for the number of things we need to escape. @@ -124,10 +126,9 @@ percent_encode :: proc(s: string, allocator := context.allocator) -> string { case: bytes, n := utf8.encode_rune(ch) for byte in bytes[:n] { - buf: [2]u8 = --- - t := strconv.write_int(buf[:], i64(byte), 16) - strings.write_rune(&b, '%') - strings.write_string(&b, t) + strings.write_byte(&b, '%') + strings.write_byte(&b, HEX_DIGITS_UPPER[byte >> 4]) + strings.write_byte(&b, HEX_DIGITS_UPPER[byte & 0xF]) } } } From 56d8293ae0798fea7538a2c52b2ee7e49632c54b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Tesa=C5=99?= Date: Wed, 5 Aug 2026 23:58:33 +0200 Subject: [PATCH 26/42] Restore formatting and remove unused import --- core/net/url.odin | 1 - tests/core/net/test_core_net.odin | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/core/net/url.odin b/core/net/url.odin index 1a8028ad0..b5bd6d917 100644 --- a/core/net/url.odin +++ b/core/net/url.odin @@ -19,7 +19,6 @@ package net */ import "core:strings" -import "core:strconv" import "core:unicode/utf8" import "core:encoding/hex" diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index 6a6111a29..f360f95cd 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -304,7 +304,7 @@ client_sends_server_data :: proc(t: ^testing.T) { r.length, r.err = net.recv_tcp(client, r.data[:]) return } - + thread_data := [2]Thread_Data{} wg: sync.Wait_Group @@ -313,7 +313,7 @@ client_sends_server_data :: proc(t: ^testing.T) { thread_data[0].t = t thread_data[0].wg = &wg thread_data[0].tid = thread.create_and_start_with_data(&thread_data[0], tcp_server, context) - + sync.wait_group_wait(&wg) sync.wait_group_add(&wg, 2) From 8f4f92db8faf9ded279d9cbaa10bfccdad022145 Mon Sep 17 00:00:00 2001 From: Simon Branch Date: Wed, 5 Aug 2026 20:43:50 -0700 Subject: [PATCH 27/42] Fix runtime quaternion division The runtime implementations of quaternion division are incorrect when calculating q/r with nonzero jmag(r) and either nonzero imag(q) or kmag(q). Notably, the inverse 1/r is still calculated correctly because imag(1) = kmag(1) = 0. The mistake can be verified by calculating (a/b)*b which will be very different from both a and a * (1/b) * b. The compile-time constant folding is correct, see exact_value.cpp in function exact_binary_operator_value -> ExactValue_Quaternion -> Token_Quo. quo256 :: proc(q, r: quaternion256) -> quaternion256 { return q/r } a: quaternion256 : 3 + 5i + 7j + 11k b: quaternion256 : 2 + 7i + 3j + 5k c: quaternion256 : a/b fmt.println("comp", abs((c*b) - a)) fmt.println("run ", abs(quo256(a,b)*b - a)) // both values should be very close to zero; // without fix, only the first is --- base/runtime/internal.odin | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index e08d0e01d..a682f7e76 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -1032,9 +1032,9 @@ quo_quaternion64 :: proc "contextless" (q, r: quaternion64) -> quaternion64 { invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) t0 := (r0*q0 + r1*q1 + r2*q2 + r3*q3) * invmag2 - t1 := (r0*q1 - r1*q0 - r2*q3 - r3*q2) * invmag2 + t1 := (r0*q1 - r1*q0 + r2*q3 - r3*q2) * invmag2 t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 - t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 + t3 := (r0*q3 + r1*q2 - r2*q1 - r3*q0) * invmag2 return quaternion(w=f16(t0), x=f16(t1), y=f16(t2), z=f16(t3)) } @@ -1046,9 +1046,9 @@ quo_quaternion128 :: proc "contextless" (q, r: quaternion128) -> quaternion128 { invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) t0 := (r0*q0 + r1*q1 + r2*q2 + r3*q3) * invmag2 - t1 := (r0*q1 - r1*q0 - r2*q3 - r3*q2) * invmag2 + t1 := (r0*q1 - r1*q0 + r2*q3 - r3*q2) * invmag2 t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 - t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 + t3 := (r0*q3 + r1*q2 - r2*q1 - r3*q0) * invmag2 return quaternion(w=t0, x=t1, y=t2, z=t3) } @@ -1060,9 +1060,9 @@ quo_quaternion256 :: proc "contextless" (q, r: quaternion256) -> quaternion256 { invmag2 := 1.0 / (r0*r0 + r1*r1 + r2*r2 + r3*r3) t0 := (r0*q0 + r1*q1 + r2*q2 + r3*q3) * invmag2 - t1 := (r0*q1 - r1*q0 - r2*q3 - r3*q2) * invmag2 + t1 := (r0*q1 - r1*q0 + r2*q3 - r3*q2) * invmag2 t2 := (r0*q2 - r1*q3 - r2*q0 + r3*q1) * invmag2 - t3 := (r0*q3 + r1*q2 + r2*q1 - r3*q0) * invmag2 + t3 := (r0*q3 + r1*q2 - r2*q1 - r3*q0) * invmag2 return quaternion(w=t0, x=t1, y=t2, z=t3) } From a1cb87c1c4369d057943118a884fced687db1f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Tesa=C5=99?= Date: Thu, 6 Aug 2026 07:14:50 +0200 Subject: [PATCH 28/42] Add decode roundtrip test --- tests/core/net/test_core_net.odin | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index f360f95cd..b0cf57cad 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -547,6 +547,11 @@ percent_encode_test :: proc(t: ^testing.T) { encoded := net.percent_encode(test.input) defer delete(encoded) testing.expectf(t, encoded == test.expected, "Expected `net.percent_encode(%q)` to return %q, got %q", test.input, test.expected, encoded) + + decoded, ok := net.percent_decode(encoded) + defer delete(decoded) + testing.expectf(t, ok, "Expected `net.percent_decode(%q)` to succeed", encoded) + testing.expectf(t, decoded == test.input, "Expected percent-encoding roundtrip for %q, got %q", test.input, decoded) } } From dc022d53b99fae4d633144f87a344880e1ebb8fb Mon Sep 17 00:00:00 2001 From: Mihail Moskov <28909106+corleypc@users.noreply.github.com> Date: Thu, 6 Aug 2026 17:42:13 +0300 Subject: [PATCH 29/42] fixes call site attribute->arg association when args are ignored --- src/llvm_backend_proc.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index b7d2d0bc7..b528980ec 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -1024,10 +1024,16 @@ gb_internal lbValue lb_emit_call_internal(lbProcedure *p, lbValue value, lbValue } for_array(i, ft->args) { + // lbArg_Ignore args are not present in the call arguments, so they must not + // advance param_offset (mirrors lb_add_function_type_attributes) + if (ft->args[i].kind == lbArg_Ignore) { + continue; + } LLVMAttributeRef attribute = ft->args[i].attribute; if (attribute != nullptr) { - LLVMAddCallSiteAttribute(ret, param_offset + cast(LLVMAttributeIndex)i, attribute); + LLVMAddCallSiteAttribute(ret, param_offset, attribute); } + param_offset += 1; } switch (inlining) { From e94d3d4871054d026fdf8a309e832b7d13b9bd7c Mon Sep 17 00:00:00 2001 From: Isabella Basso Date: Wed, 13 May 2026 15:00:33 -0300 Subject: [PATCH 30/42] llvm: handle #soa fixed compound literals for vars --- src/llvm_backend_expr.cpp | 56 ++++++++++++++++++++++++------- tests/issues/run.bat | 1 + tests/issues/run.sh | 24 ++++++------- tests/issues/test_issue_7188.odin | 13 +++++++ 4 files changed, 70 insertions(+), 24 deletions(-) create mode 100644 tests/issues/test_issue_7188.odin diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 368ee709e..29dd75c9f 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4924,6 +4924,11 @@ gb_internal void lb_build_addr_compound_lit_populate(lbProcedure *p, SliceSimdVector.elem; break; case Type_Matrix: et = bt->Matrix.elem; break; case Type_FixedCapacityDynamicArray: et = bt->FixedCapacityDynamicArray.elem; break; + case Type_Struct: + if (bt->Struct.soa_kind == StructSoa_Fixed) { + et = bt->Struct.soa_elem; + } + break; } GB_ASSERT(et != nullptr); @@ -5038,18 +5043,17 @@ gb_internal void lb_build_addr_compound_lit_populate(lbProcedure *p, Slice const &temp_data) { for (auto const &td : temp_data) { - if (td.value.value != nullptr) { - if (td.elem_length > 0) { - auto loop_data = lb_loop_start(p, cast(isize)td.elem_length, t_i32); - { - lbValue dst = td.gep; - dst = lb_emit_ptr_offset(p, dst, loop_data.idx); - lb_emit_store(p, dst, td.value); - } - lb_loop_end(p, loop_data); - } else { - lb_emit_store(p, td.gep, td.value); + GB_ASSERT(td.value.value != nullptr); + if (td.elem_length > 0) { + auto loop_data = lb_loop_start(p, cast(isize)td.elem_length, t_i32); + { + lbValue dst = td.gep; + dst = lb_emit_ptr_offset(p, dst, loop_data.idx); + lb_emit_store(p, dst, td.value); } + lb_loop_end(p, loop_data); + } else { + lb_emit_store(p, td.gep, td.value); } } } @@ -6120,7 +6124,35 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { } case Type_Struct: - lb_build_addr_struct_compound_lit_populate(p, expr, type, v); + if (is_type_soa_struct(type)) { + GB_ASSERT(bt->Struct.soa_kind == StructSoa_Fixed); + if (cl->elems.count == 0) { + break; + } + lb_addr_store(p, v, lb_const_value(p->module, type, exact_value_compound(expr))); + + auto temp_data = array_make(temporary_allocator(), 0, cl->elems.count); + lb_build_addr_compound_lit_populate(p, cl->elems, &temp_data, type); + for (auto const &td : temp_data) { + GB_ASSERT(td.value.value != nullptr); + lbValue offset = lb_const_int(p->module, t_i32, td.elem_index); + if (td.elem_length > 0) { + auto loop_data = lb_loop_start(p, cast(isize)td.elem_length, t_i32); + { + lbValue index = lb_emit_arith(p, Token_Add, offset, loop_data.idx, t_i32); + lbAddr dst = lb_addr_soa_variable(v.addr, index, td.expr); + lb_addr_store(p, dst, td.value); + } + lb_loop_end(p, loop_data); + } else { + lbValue index = offset; + lbAddr dst = lb_addr_soa_variable(v.addr, index, td.expr); + lb_addr_store(p, dst, td.value); + } + } + } else { + lb_build_addr_struct_compound_lit_populate(p, expr, type, v); + } break; case Type_Map: { diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 6d562ccbc..7d8359a6c 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -42,6 +42,7 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin check ..\test_issue_6874.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "1" || exit /b ..\..\..\odin check ..\test_issue_6979.odin -no-entry-point %COMMON% || exit /b ..\..\..\odin build ..\test_issue_7037.odin %COMMON% -o:none || exit /b +..\..\..\odin build ..\test_issue_7188.odin %COMMON% || exit /b ..\..\..\odin build ..\test_issue_7073-1.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "2" || exit /b @echo off diff --git a/tests/issues/run.sh b/tests/issues/run.sh index bf1db5b6e..cca26fe8a 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -7,10 +7,9 @@ ODIN=../../../odin COMMON="-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused-defineables -microarch:native" COMMON_CHECK="-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused-defineables" - set -x -$ODIN test ../test_issue_829.odin $COMMON +$ODIN test ../test_issue_829.odin $COMMON $ODIN test ../test_issue_1592.odin $COMMON $ODIN test ../test_issue_1730.odin $COMMON $ODIN test ../test_issue_2056.odin $COMMON @@ -24,7 +23,7 @@ $ODIN test ../test_issue_3435.odin $COMMON $ODIN test ../test_issue_4210.odin $COMMON $ODIN test ../test_issue_4364.odin $COMMON $ODIN test ../test_issue_4584.odin $COMMON -if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]] ; then +if [[ $($ODIN build ../test_issue_2395.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]]; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" @@ -34,7 +33,7 @@ $ODIN build ../test_issue_5043.odin $COMMON $ODIN build ../test_issue_5097.odin $COMMON $ODIN build ../test_issue_5097-2.odin $COMMON $ODIN build ../test_issue_5265.odin $COMMON -if [[ $($ODIN build ../test_issue_5573.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]] ; then +if [[ $($ODIN build ../test_issue_5573.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]]; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" @@ -49,25 +48,25 @@ $ODIN test ../test_issue_6344.odin $COMMON -o:speed $ODIN test ../test_issue_6396.odin $COMMON $ODIN test ../test_pr_6476.odin $COMMON -if [[ $($ODIN build ../test_issue_6240.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 3 ]] ; then +if [[ $($ODIN build ../test_issue_6240.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 3 ]]; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" exit 1 fi -if [[ $($ODIN build ../test_issue_6401.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 3 ]] ; then +if [[ $($ODIN build ../test_issue_6401.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 3 ]]; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" exit 1 fi -if [[ $($ODIN build ../test_issue_6594.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]] ; then +if [[ $($ODIN build ../test_issue_6594.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]]; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" exit 1 fi -if [[ $($ODIN build ../test_issue_6621.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]] ; then +if [[ $($ODIN build ../test_issue_6621.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]]; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" @@ -75,7 +74,7 @@ else fi $ODIN test ../test_issue_6419.odin $COMMON $ODIN test ../test_pr_6470.odin $COMMON -if [[ $($ODIN test ../test_pr_6470.odin -define:TEST_EXPECT_FAILURE=true $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]] ; then +if [[ $($ODIN test ../test_pr_6470.odin -define:TEST_EXPECT_FAILURE=true $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]]; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" @@ -83,7 +82,7 @@ else fi $ODIN check ../test_issue_6484.odin -no-entry-point $COMMON_CHECK $ODIN test ../test_issue_6753.odin $COMMON -if [[ $($ODIN check ../test_issue_6874.odin $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]] ; then +if [[ $($ODIN check ../test_issue_6874.odin $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 1 ]]; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" @@ -92,15 +91,16 @@ fi $ODIN check ../test_issue_6979.odin -no-entry-point $COMMON_CHECK $ODIN build ../test_issue_7037.odin $COMMON -o:none $ODIN build ../test_issue_7167.odin $COMMON +$ODIN build ../test_issue_7188.odin $COMMON -if [[ $($ODIN build ../test_issue_7108.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]] ; then +if [[ $($ODIN build ../test_issue_7108.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]]; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" exit 1 fi -if [[ $($ODIN build ../test_issue_7073-1.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]] ; then +if [[ $($ODIN build ../test_issue_7073-1.odin $COMMON 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]]; then echo "SUCCESSFUL 1/1" else echo "SUCCESSFUL 0/1" diff --git a/tests/issues/test_issue_7188.odin b/tests/issues/test_issue_7188.odin new file mode 100644 index 000000000..8a21bde4e --- /dev/null +++ b/tests/issues/test_issue_7188.odin @@ -0,0 +1,13 @@ +package test_issues + +main :: proc() { + val: f32 = 42.0 + // unable to broadcast like ([4][4]f32)(1) or (#soa[4][4]f32)(([4]f32)(1)) + _ = #soa[2][2]f32 { + 0..<2 = val + } + vtxs := #soa[33][2]f32 { + 0..<33 = val + } + _ = vtxs.x[32] +} From 56c49287a6c9455b574c7c151af868c1eb4cbdfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Tesa=C5=99?= Date: Thu, 6 Aug 2026 22:08:28 +0200 Subject: [PATCH 31/42] Fix default linker timing label on non-Windows platforms --- src/linker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linker.cpp b/src/linker.cpp index cb3cbe023..98d299eb8 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -155,7 +155,7 @@ try_cross_linking:; String section_name = str_lit("msvc-link"); bool is_windows = build_context.metrics.os == TargetOs_windows; #else - String section_name = str_lit("lld-link"); + String section_name = str_lit("ld-link"); bool is_windows = false; #endif From 678a7273b7fd9005613974939a6e724f98c2a7fe Mon Sep 17 00:00:00 2001 From: kalsprite Date: Thu, 6 Aug 2026 21:30:23 -0700 Subject: [PATCH 32/42] switch assert to 'no file' --- src/docs_writer.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 7a12d0875..67d350f24 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -265,8 +265,12 @@ gb_internal OdinDocPosition odin_doc_token_pos_cast(OdinDocWriter *w, TokenPos c AstFile *file = global_files[pos.file_id]; if (file != nullptr) { OdinDocFileIndex *file_index_found = map_get(&w->file_cache, file); - GB_ASSERT(file_index_found != nullptr); - file_index = *file_index_found; + // NOTE: a documented entity may hold a position in a file belonging to an + // imported package. Such files are only in file_cache when -all-packages is + // used, so fall back to the reserved "no file" index. + if (file_index_found != nullptr) { + file_index = *file_index_found; + } } } From 33636c82366b3029db072bd5ef6cce8f7649a75e Mon Sep 17 00:00:00 2001 From: kalsprite Date: Thu, 6 Aug 2026 21:40:32 -0700 Subject: [PATCH 33/42] fix bigint negative prints --- src/big_int.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/big_int.cpp b/src/big_int.cpp index ffb7a6ad6..6db3301da 100644 --- a/src/big_int.cpp +++ b/src/big_int.cpp @@ -694,7 +694,9 @@ gb_internal String big_int_to_string(gbAllocator allocator, BigInt const *x, u64 big_int_dealloc(&r); big_int_dealloc(&b); - for (isize i = first_word_idx; i < buf.count/2; i++) { + // NOTE: only the digits are reversed, not the leading '-'. + isize digit_count = buf.count - first_word_idx; + for (isize i = first_word_idx; i < first_word_idx + digit_count/2; i++) { isize j = buf.count + first_word_idx - i - 1; char tmp = buf[i]; buf[i] = buf[j]; From 6276f6e12f62cc20b976ff3373c9b16721ec7591 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Thu, 6 Aug 2026 21:51:03 -0700 Subject: [PATCH 34/42] src/check_expr.cpp --- src/check_expr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index c25de50dc..80c425f2b 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -4082,7 +4082,7 @@ gb_internal bool check_transmute(CheckerContext *c, Ast *node, Operand *o, Type big_int_shl_eq(&umax, &sz_in_bits); if (is_type_unsigned(src_t) && !is_type_unsigned(dst_t)) { - if (big_int_cmp(&v, &smax) >= 0) { + if (big_int_cmp(&v, &smax) > 0) { big_int_sub_eq(&v, &umax); } } else if (!is_type_unsigned(src_t) && is_type_unsigned(dst_t)) { From 713c7017b6db18ba00ddc32d5880958dce44e5ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Tesa=C5=99?= Date: Fri, 7 Aug 2026 15:09:08 +0200 Subject: [PATCH 35/42] Clarify completed task handling in thread pools --- core/thread/thread_pool.odin | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/thread/thread_pool.odin b/core/thread/thread_pool.odin index 10f1438d7..70734137e 100644 --- a/core/thread/thread_pool.odin +++ b/core/thread/thread_pool.odin @@ -187,6 +187,9 @@ pool_join :: proc(pool: ^Pool) { // // Each task also needs an allocator which it either owns, or which is thread // safe. +// +// Completed tasks remain in the pool until removed with `pool_pop_done`. +// When reusing the pool, call it once for every task added. pool_add_task :: proc(pool: ^Pool, allocator: mem.Allocator, procedure: Task_Proc, data: rawptr, user_index: int = 0) { sync.guard(&pool.mutex) @@ -344,7 +347,10 @@ pool_pop_waiting :: proc(pool: ^Pool) -> (task: Task, got_task: bool) { return } -// Use this to take out finished tasks. +// Remove and return the next completed task, if one is available. +// +// The caller is responsible for processing the result and releasing any +// resources associated with the task. pool_pop_done :: proc(pool: ^Pool) -> (task: Task, got_task: bool) { sync.guard(&pool.mutex) @@ -374,6 +380,9 @@ pool_do_work :: proc(pool: ^Pool, task: Task) { // Process the rest of the tasks, also use this thread for processing, then join // all the pool threads. +// +// Completed tasks are not removed. Retrieve each one with `pool_pop_done`. +// The pool cannot be restarted after this procedure returns. pool_finish :: proc(pool: ^Pool) { for task in pool_pop_waiting(pool) { pool_do_work(pool, task) From 2162e7605e34f90b9956de0b2c41040416930023 Mon Sep 17 00:00:00 2001 From: jack gleeson Date: Fri, 10 Jul 2026 13:50:47 -0700 Subject: [PATCH 36/42] core/unicode/utf8: Fix truncated text for grapheme clusters --- core/unicode/utf8/grapheme.odin | 26 ++++++++++++----- tests/core/unicode/test_core_unicode.odin | 34 +++++++++++++++++++++++ 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/core/unicode/utf8/grapheme.odin b/core/unicode/utf8/grapheme.odin index d8f6557b6..bcd234972 100644 --- a/core/unicode/utf8/grapheme.odin +++ b/core/unicode/utf8/grapheme.odin @@ -54,6 +54,9 @@ Grapheme_Iterator :: struct { current_sequence: Grapheme_Cluster_Sequence, continue_sequence: bool, + + current_grapheme: Grapheme, + continue_grapheme: bool, } @@ -147,13 +150,13 @@ decode_grapheme_iterate :: proc(it: ^Grapheme_Iterator) -> (text: string, graphe if it.grapheme_count > it.last_grapheme_count { it.width += normalized_east_asian_width(this_rune) - grapheme = Grapheme{ - byte_index, - it.rune_count, - it.width - it.last_width, + if it.continue_grapheme { + grapheme = it.current_grapheme + text = it.str[it.current_grapheme.byte_index:byte_index] + ok = true } - text = it.str[byte_index:][:grapheme.width] - ok = true + it.current_grapheme = Grapheme{byte_index, it.rune_count, it.width - it.last_width} + it.continue_grapheme = true it.last_grapheme_count = it.grapheme_count @@ -385,5 +388,14 @@ decode_grapheme_iterate :: proc(it: ^Grapheme_Iterator) -> (text: string, graphe it.grapheme_count += 1 } + // Flush the remaining grapheme - the loop only flushes when + // a new grapheme is encountered. + if !ok && it.continue_grapheme { + grapheme = it.current_grapheme + text = it.str[it.current_grapheme.byte_index:] + ok = true + it.continue_grapheme = false + } + return -} \ No newline at end of file +} diff --git a/tests/core/unicode/test_core_unicode.odin b/tests/core/unicode/test_core_unicode.odin index 30a40b30b..874f4eebe 100644 --- a/tests/core/unicode/test_core_unicode.odin +++ b/tests/core/unicode/test_core_unicode.odin @@ -9,6 +9,11 @@ Test_Case :: struct { expected_clusters: int, } +Text_Test_Case :: struct { + str: string, + expected_output: []string, +} + run_test_cases :: proc(t: ^testing.T, test_cases: []Test_Case, loc := #caller_location) { failed := 0 for c, i in test_cases { @@ -132,3 +137,32 @@ test_width :: proc(t: ^testing.T) { testing.expect_value(t, width, 50) } } + +@test +test_grapheme_cluster_text :: proc(t: ^testing.T) { + + cases :: []Text_Test_Case { + {"abc", {"a", "b", "c"}}, + {"é", {"é"}}, + {"中", {"中"}}, + {"\U0001F1FA\U0001F1F8", {"\U0001F1FA\U0001F1F8"}}, + {"\U0001F1FA\U0001F1F8\U0001F1EE\U0001F1EA", {"\U0001F1FA\U0001F1F8", "\U0001F1EE\U0001F1EA"}}, + {"\U0001F468‍\U0001F469‍\U0001F467‍\U0001F466", {"\U0001F468‍\U0001F469‍\U0001F467‍\U0001F466"}}, + {"\U0001F44D\U0001F3FD", {"\U0001F44D\U0001F3FD"}}, + {"a\r\nb", {"a", "\r\n", "b"}}, + } + + for c in cases { + it := utf8.decode_grapheme_iterator_make(c.str) + i := 0 + for text, grapheme in utf8.decode_grapheme_iterate(&it) { + if !testing.expectf(t, i < len(c.expected_output), "%q: expected %d clusters, got at least %d", c.str, len(c.expected_output), i + 1) { + break + } + testing.expectf(t, text == c.expected_output[i], "%q cluster %d: expected text %q, got %q", c.str, i, c.expected_output[i], text) + testing.expectf(t, text == c.str[grapheme.byte_index:][:len(text)], "%q cluster %d: text does not start at byte_index %d", c.str, i, grapheme.byte_index) + i += 1 + } + testing.expectf(t, i == len(c.expected_output), "%q: expected %d clusters, got %d", c.str, len(c.expected_output), i) + } +} From e14051295457154da9f53e4eb033265731597d8a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Fri, 7 Aug 2026 20:05:53 +0200 Subject: [PATCH 37/42] Update test_issue_7188.odin Add missing commas. --- tests/issues/test_issue_7188.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/issues/test_issue_7188.odin b/tests/issues/test_issue_7188.odin index 8a21bde4e..679494086 100644 --- a/tests/issues/test_issue_7188.odin +++ b/tests/issues/test_issue_7188.odin @@ -4,10 +4,10 @@ main :: proc() { val: f32 = 42.0 // unable to broadcast like ([4][4]f32)(1) or (#soa[4][4]f32)(([4]f32)(1)) _ = #soa[2][2]f32 { - 0..<2 = val + 0..<2 = val, } vtxs := #soa[33][2]f32 { - 0..<33 = val + 0..<33 = val, } _ = vtxs.x[32] } From 5393a11ee1070dfb021089bdef5a0b3f1c424cb8 Mon Sep 17 00:00:00 2001 From: kalsprite Date: Fri, 7 Aug 2026 14:49:25 -0700 Subject: [PATCH 38/42] untyped integral float index conversion to int --- src/check_type.cpp | 17 ++++++++--------- src/exact_value.cpp | 6 ++++++ tests/internal/test_array_count.odin | 25 +++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 tests/internal/test_array_count.odin diff --git a/src/check_type.cpp b/src/check_type.cpp index 5e5959e24..9414b794e 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2846,9 +2846,14 @@ gb_internal i64 check_array_count(CheckerContext *ctx, Operand *o, Ast *e) { } Type *type = core_type(o->type); if (is_type_untyped(type) || is_type_integer(type)) { - if (o->value.kind == ExactValue_Integer) { - BigInt count = o->value.value_integer; - if (big_int_is_neg(&o->value.value_integer)) { + ExactValue value = o->value; + if (value.kind == ExactValue_Float) { + // NOTE: an integral float is a valid count, but it must be range checked as an integer + value = exact_value_to_integer(value); + } + if (value.kind == ExactValue_Integer) { + BigInt count = value.value_integer; + if (big_int_is_neg(&count)) { gbAllocator a = heap_allocator(); String str = big_int_to_string(a, &count); error(e, "Invalid negative array count, %.*s", LIT(str)); @@ -2864,12 +2869,6 @@ gb_internal i64 check_array_count(CheckerContext *ctx, Operand *o, Ast *e) { error(e, "Array count too large, %.*s", LIT(str)); gb_free(a, str.text); return 0; - } else if (o->value.kind == ExactValue_Float) { - u64 u = cast(u64)o->value.value_float; - f64 f = cast(f64)u; - if (f == o->value.value_float) { - return u; - } } } diff --git a/src/exact_value.cpp b/src/exact_value.cpp index 545b415a6..2f82a52cf 100644 --- a/src/exact_value.cpp +++ b/src/exact_value.cpp @@ -419,6 +419,12 @@ gb_internal ExactValue exact_value_to_integer(ExactValue v) { case ExactValue_Integer: return v; case ExactValue_Float: { + f64 const min = cast(f64)I64_MIN; // -2^63 + f64 const max = -min; // 2^63, one past I64_MAX + // NOTE: the conversion below is undefined outside of this range, NaN included + if (!(v.value_float >= min && v.value_float < max)) { + break; + } i64 i = cast(i64)v.value_float; f64 f = cast(f64)i; if (f == v.value_float) { diff --git a/tests/internal/test_array_count.odin b/tests/internal/test_array_count.odin new file mode 100644 index 000000000..a5375435b --- /dev/null +++ b/tests/internal/test_array_count.odin @@ -0,0 +1,25 @@ +package test_internal + +import "core:testing" + +@(test) +array_count_from_integral_float :: proc(t: ^testing.T) { + testing.expect_value(t, len([3.0]u8{}), 3) + testing.expect_value(t, len([0.0]u8{}), 0) + testing.expect_value(t, size_of([3.0]u32), 12) + + // folded constant expressions + Halved :: 6.0 / 2.0 + Summed :: 1.5 + 1.5 + testing.expect_value(t, len([Halved]u8{}), 3) + testing.expect_value(t, len([Summed]u8{}), 3) + + // 2^53, the largest integer an f64 holds exactly, on a zero-sized element + testing.expect_value(t, len([9007199254740992.0]struct{}{}), 1 << 53) + + testing.expect_value(t, size_of(matrix[2.0, 3.0]f32), 24) + testing.expect_value(t, size_of(#simd[4.0]u8), 4) + + Sparse :: [2.0]u8 + testing.expect_value(t, len(Sparse{1, 2}), 2) +} From df41b93c45aeafe626c736f8b4442500e7f175aa Mon Sep 17 00:00:00 2001 From: kalsprite Date: Fri, 7 Aug 2026 15:05:35 -0700 Subject: [PATCH 39/42] fix typo --- 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 0b2d21c51..d7e4523dd 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -1700,7 +1700,7 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty } } - res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->Array.count, values, cc); + res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, cc); return res; } } else if (is_type_fixed_capacity_dynamic_array(type)) { From 3dc7741e06ddd30ce6df159f39311534bf568ebd Mon Sep 17 00:00:00 2001 From: kalsprite Date: Fri, 7 Aug 2026 15:18:46 -0700 Subject: [PATCH 40/42] correct windows limb size --- tests/internal/test_array_count.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/internal/test_array_count.odin b/tests/internal/test_array_count.odin index a5375435b..597462c4c 100644 --- a/tests/internal/test_array_count.odin +++ b/tests/internal/test_array_count.odin @@ -14,8 +14,8 @@ array_count_from_integral_float :: proc(t: ^testing.T) { testing.expect_value(t, len([Halved]u8{}), 3) testing.expect_value(t, len([Summed]u8{}), 3) - // 2^53, the largest integer an f64 holds exactly, on a zero-sized element - testing.expect_value(t, len([9007199254740992.0]struct{}{}), 1 << 53) + // NOTE: keep below one BigInt limb: 2^28 on windows, 2^60 on linux + testing.expect_value(t, len([65536.0]struct{}{}), 1 << 16) testing.expect_value(t, size_of(matrix[2.0, 3.0]f32), 24) testing.expect_value(t, size_of(#simd[4.0]u8), 4) From 391a862e4f8a0b1d34de5edd6e25ecc3a214616e Mon Sep 17 00:00:00 2001 From: kalsprite Date: Fri, 7 Aug 2026 15:58:25 -0700 Subject: [PATCH 41/42] make tok stable under multi-thread --- src/checker.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index 0c4b6788f..a69d8c500 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2050,18 +2050,26 @@ gb_internal bool redeclaration_error(String name, Entity *prev, Entity *found) { // NOTE(bill): Error should have been handled already return false; } + // NOTE: the insertion order is a race between the files of a package, so anchor on the + // lower position to keep the diagnostic stable + TokenPos lo = prev->token.pos; + TokenPos hi = pos; + if (hi < lo) { + lo = pos; + hi = prev->token.pos; + } if (found->flags & EntityFlag_Result) { - error(prev->token, + error(lo, "Direct shadowing of the named return value '%.*s' in this scope\n" "\tat %s", LIT(name), - token_pos_to_string(pos)); + token_pos_to_string(hi)); } else { - error(prev->token, + error(lo, "Redeclaration of '%.*s' in this scope\n" "\tat %s", LIT(name), - token_pos_to_string(pos)); + token_pos_to_string(hi)); } } return false; From 42bd26cb72f67c7a9a4ce80656b883c6d82a68ba Mon Sep 17 00:00:00 2001 From: kalsprite Date: Fri, 7 Aug 2026 16:14:12 -0700 Subject: [PATCH 42/42] switch ordering --- src/checker.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index a69d8c500..23ac59a3e 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2050,26 +2050,26 @@ gb_internal bool redeclaration_error(String name, Entity *prev, Entity *found) { // NOTE(bill): Error should have been handled already return false; } - // NOTE: the insertion order is a race between the files of a package, so anchor on the - // lower position to keep the diagnostic stable - TokenPos lo = prev->token.pos; - TokenPos hi = pos; - if (hi < lo) { - lo = pos; - hi = prev->token.pos; + // NOTE: the insertion order is a race between the files of a package, so order the pair by + // position; the later declaration stays the anchor, as it is the one being reported + TokenPos first = prev->token.pos; + TokenPos second = pos; + if (second < first) { + first = pos; + second = prev->token.pos; } if (found->flags & EntityFlag_Result) { - error(lo, + error(second, "Direct shadowing of the named return value '%.*s' in this scope\n" "\tat %s", LIT(name), - token_pos_to_string(hi)); + token_pos_to_string(first)); } else { - error(lo, + error(second, "Redeclaration of '%.*s' in this scope\n" "\tat %s", LIT(name), - token_pos_to_string(hi)); + token_pos_to_string(first)); } } return false;