From 3bf48e29d2919b98ffdf0d939c89c640b96c0e96 Mon Sep 17 00:00:00 2001 From: A1029384756 Date: Sun, 14 Jun 2026 23:55:36 -0400 Subject: [PATCH 01/21] [subprocess] move away from `system` for `odin run` --- src/main.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 6589efb54..a4acf1481 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -178,11 +178,41 @@ gb_internal i32 system_exec_command_line_app(char const *name, char const *fmt, return exit_code; } -gb_internal void system_must_exec_command_line_app(char const *name, char const *fmt, ...) { - va_list va; - va_start(va, fmt); - system_exec_command_line_app_internal(/* exit_on_err= */ true, name, fmt, va); - va_end(va); +#if defined(GB_SYSTEM_WINDOWS) +#include +#else +#include +extern char **environ; +#endif + +int run_subprocess(const char *name, const char **args) { +#if defined(GB_SYSTEM_WINDOWS) + return (int)_spawnvp(_P_WAIT, name, args); +#else + pid_t pid; + int status; + status = posix_spawn(&pid, name, NULL, NULL, (char *const *)args, environ); + if (status != 0) { + gb_printf_err("Could not spawn subprocess: %s\n", strerror(errno)); + return -1; + } + + for (;;) { + if (waitpid(pid, &status, WUNTRACED) < 0) { + gb_printf_err("Could not wait on subprocess: (pid: %d): %s\n", pid, strerror(errno)); + return -1; + } + + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + return -1; + } else if (WIFSTOPPED(status)) { + return -1; + } + } + GB_PANIC("Subprocess failure"); +#endif } #if defined(GB_SYSTEM_WINDOWS) @@ -3621,6 +3651,8 @@ int main(int arg_count, char const **arg_ptr) { init_build_context_error_pos_style(); Array args = setup_args(arg_count, arg_ptr); + Array run_args = array_make(heap_allocator(), 0, arg_count); + defer (array_free(&run_args)); String command = args[1]; String init_filename = {}; @@ -3648,9 +3680,6 @@ int main(int arg_count, char const **arg_ptr) { build_context.command_kind = Command_test; } - Array run_args = array_make(heap_allocator(), 0, arg_count); - defer (array_free(&run_args)); - isize run_args_start_idx = -1; for_array(i, args) { if (args[i] == "--") { @@ -4261,8 +4290,23 @@ end_of_code_gen:; String exe_name = path_to_string(heap_allocator(), build_context.build_paths[BuildPath_Output]); defer (gb_free(heap_allocator(), exe_name.text)); - system_must_exec_command_line_app("odin run", "\"%.*s\" %.*s", LIT(exe_name), LIT(run_args_string)); + const char* exe_name_cstring = alloc_cstring(heap_allocator(), exe_name); + Array run_args_cstring = array_make(heap_allocator(), 0, run_args.count); + defer({ + for_array(i, run_args_cstring) { gb_free(heap_allocator(), (void*)run_args_cstring[i]); } + array_free(&run_args_cstring); + }); + array_add(&run_args_cstring, exe_name_cstring); + for_array(i, run_args) { + array_add(&run_args_cstring, alloc_cstring(heap_allocator(), run_args[i])); + } + array_add(&run_args_cstring, NULL); + + int subprocess_res = run_subprocess(exe_name_cstring, run_args_cstring.data); + if (subprocess_res) { + gb_exit(subprocess_res); + } if (!build_context.keep_executable) { char const *filename = cast(char const *)exe_name.text; gb_file_remove(filename); From 1b4e277f585072672006fc0c3dac923c83bb9f0e Mon Sep 17 00:00:00 2001 From: A1029384756 Date: Mon, 15 Jun 2026 22:59:58 -0400 Subject: [PATCH 02/21] [subprocess] removed unused variable --- src/main.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index a4acf1481..631ace841 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3656,7 +3656,6 @@ int main(int arg_count, char const **arg_ptr) { String command = args[1]; String init_filename = {}; - String run_args_string = {}; isize last_non_run_arg = args.count; for_array(i, args) { @@ -3701,7 +3700,6 @@ int main(int arg_count, char const **arg_ptr) { } } args = array_slice(args, 0, last_non_run_arg); - run_args_string = string_join_and_quote(heap_allocator(), run_args); init_filename = args[2]; run_output = true; From 32db7d8d562e4e9846f1d781aef1cbcb2415b5c8 Mon Sep 17 00:00:00 2001 From: A1029384756 Date: Wed, 17 Jun 2026 00:12:36 -0400 Subject: [PATCH 03/21] [subprocess] remove `p` as `PATH` is not needed to find executable --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 631ace841..9b85fd3b7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -187,7 +187,7 @@ extern char **environ; int run_subprocess(const char *name, const char **args) { #if defined(GB_SYSTEM_WINDOWS) - return (int)_spawnvp(_P_WAIT, name, args); + return (int)_spawnv(_P_WAIT, name, args); #else pid_t pid; int status; From c3b2c9a9b388c355eaa9faa5dfb33e80158966ca Mon Sep 17 00:00:00 2001 From: ARay Date: Thu, 18 Jun 2026 18:05:54 +0300 Subject: [PATCH 04/21] add test for the pr --- tests/issues/test_issue_6853.odin | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/issues/test_issue_6853.odin diff --git a/tests/issues/test_issue_6853.odin b/tests/issues/test_issue_6853.odin new file mode 100644 index 000000000..af0bcebe5 --- /dev/null +++ b/tests/issues/test_issue_6853.odin @@ -0,0 +1,25 @@ +package test_issues + +import "core:testing" + +@(test) +test_issue_6853 :: proc(t: ^testing.T) { + + test_s :: struct { + a, b, c, d, e: u64, + } + + expected := test_s{1, 2, 3, 4, 5} + + case0 :: proc() -> (u64, u64) {return 1, 2} + test0 := test_s{case0(), 3, 4, 5} + testing.expect_value(t, test0, expected) + + case1 :: proc() -> (u64, u64, u64) {return 1, 2, 3} + test1 := test_s{case1(), 4, 5} + testing.expect_value(t, test1, expected) + + case2 :: proc() -> (u64, u64) {return 2, 3} + test2 := test_s{1, case2(), 4, 5} + testing.expect_value(t, test2, expected) +} From 3db074900fac9341a6df40824b310a9f1c9152bf Mon Sep 17 00:00:00 2001 From: ARay Date: Fri, 19 Jun 2026 15:05:23 +0300 Subject: [PATCH 05/21] comment out dead code: val is not used anywhere else in that scope --- src/llvm_backend_const.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 0f0d458dc..20df5fdd0 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -1970,10 +1970,13 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty for_array(i, cl->elems) { Entity *f = type->Struct.fields[i]; TypeAndValue tav = cl->elems[i]->tav; - ExactValue val = {}; - if (tav.mode != Addressing_Invalid) { - val = tav.value; - } + + // INFO: dead code: + + // ExactValue val = {}; + // if (tav.mode != Addressing_Invalid) { + // val = tav.value; + // } i32 index = field_remapping[f->Variable.field_index]; if (elem_type_can_be_constant(f->type)) { From 912a45769bc95f248fd9b1dda14535f4eca9463c Mon Sep 17 00:00:00 2001 From: ARay Date: Fri, 19 Jun 2026 15:39:24 +0300 Subject: [PATCH 06/21] fix issue 6853: when iterating the values of an array literal,if one of the values is a function with multiple returns, it doesn't offset the struct field index by the number of values. --- src/llvm_backend_const.cpp | 13 +++++++++++-- tests/issues/test_issue_6853.odin | 5 +++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 20df5fdd0..a59ed4401 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -1967,8 +1967,9 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty } } } else { + isize multiple_return_offset = 0; for_array(i, cl->elems) { - Entity *f = type->Struct.fields[i]; + Entity *f = type->Struct.fields[i+multiple_return_offset]; TypeAndValue tav = cl->elems[i]->tav; // INFO: dead code: @@ -1978,11 +1979,19 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty // val = tav.value; // } + if (is_type_tuple(tav.type)){ + multiple_return_offset += tav.type->Tuple.variables.count-1; + } + i32 index = field_remapping[f->Variable.field_index]; + + if (elem_type_can_be_constant(f->type)) { lbValue value = lb_const_value(m, f->type, tav.value, tav.type, cc); LLVMTypeRef value_type = LLVMTypeOf(value.value); - GB_ASSERT_MSG(lb_sizeof(value_type) == type_size_of(f->type), "%s vs %s", LLVMPrintTypeToString(value_type), type_to_string(f->type)); + isize lb_sizeof_value_type = lb_sizeof(value_type); + isize type_size_of_f_type = type_size_of(f->type); + GB_ASSERT_MSG(lb_sizeof_value_type ==type_size_of_f_type, "%s vs %s", LLVMPrintTypeToString(value_type), type_to_string(f->type)); values[index] = value.value; visited[index] = true; } diff --git a/tests/issues/test_issue_6853.odin b/tests/issues/test_issue_6853.odin index af0bcebe5..fec4951a5 100644 --- a/tests/issues/test_issue_6853.odin +++ b/tests/issues/test_issue_6853.odin @@ -22,4 +22,9 @@ test_issue_6853 :: proc(t: ^testing.T) { case2 :: proc() -> (u64, u64) {return 2, 3} test2 := test_s{1, case2(), 4, 5} testing.expect_value(t, test2, expected) + + case3_1 :: proc() -> (u64, u64) {return 1, 2} + case3_2 :: proc() -> (u64, u64) {return 3, 4} + test3 := test_s{case3_1(), case3_2(), 5} + testing.expect_value(t, test3, expected) } From 76fb0aa975cfab74a55cea3e556336657b778b29 Mon Sep 17 00:00:00 2001 From: Brad Lewis <22850972+BradLewis@users.noreply.github.com> Date: Sun, 17 May 2026 12:01:01 +1000 Subject: [PATCH 07/21] Correct divergence with expr levels causing failures with the odin parser --- core/odin/parser/parser.odin | 27 +++++++++++++++-------- tests/core/odin/test_parser.odin | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index e213f900e..eb2a3e1b2 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -378,7 +378,14 @@ advance_token :: proc(p: ^Parser) -> tokenizer.Token { prev := p.prev_tok if next_token0(p) { - consume_comment_groups(p, prev) + #partial switch p.curr_tok.kind { + case .Comment: + consume_comment_groups(p, prev) + case .Semicolon: + if p.expr_level > 0 && p.curr_tok.text == "\n" { + advance_token(p) + } + } } return prev } @@ -2366,10 +2373,10 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { case .Open_Paren: open := expect_token(p, .Open_Paren) - p.expr_level += 1 + prev_expr_level := p.expr_level + p.expr_level = max(p.expr_level, 0) + 1 expr := parse_expr(p, false) - skip_possible_newline(p) - p.expr_level -= 1 + p.expr_level = prev_expr_level close := expect_token(p, .Close_Paren) pe := ast.new(ast.Paren_Expr, open.pos, end_pos(close)) @@ -3208,11 +3215,12 @@ parse_elem_list :: proc(p: ^Parser) -> []^ast.Expr { parse_literal_value :: proc(p: ^Parser, type: ^ast.Expr) -> ^ast.Comp_Lit { elems: []^ast.Expr open := expect_token(p, .Open_Brace) - p.expr_level += 1 + prev_expr_level := p.expr_level + p.expr_level = 0 if p.curr_tok.kind != .Close_Brace { elems = parse_elem_list(p) } - p.expr_level -= 1 + p.expr_level = prev_expr_level skip_possible_newline(p) close := expect_closing_brace_of_field_list(p) @@ -3231,7 +3239,8 @@ parse_call_expr :: proc(p: ^Parser, operand: ^ast.Expr) -> ^ast.Expr { ellipsis: tokenizer.Token - p.expr_level += 1 + prev_expr_level := p.expr_level + p.expr_level = 0 open := expect_token(p, .Open_Paren) seen_ellipsis := false @@ -3278,8 +3287,8 @@ parse_call_expr :: proc(p: ^Parser, operand: ^ast.Expr) -> ^ast.Expr { allow_token(p, .Comma) or_break } + p.expr_level = prev_expr_level close := expect_closing_token_of_field_list(p, .Close_Paren, "argument list") - p.expr_level -= 1 ce := ast.new(ast.Call_Expr, operand.pos, end_pos(close)) ce.expr = operand @@ -3365,8 +3374,8 @@ parse_atom_expr :: proc(p: ^Parser, value: ^ast.Expr, lhs: bool) -> (operand: ^a } } - close := expect_token(p, .Close_Bracket) p.expr_level -= 1 + close := expect_token(p, .Close_Bracket) if is_slice_op { if interval.kind == .Comma { diff --git a/tests/core/odin/test_parser.odin b/tests/core/odin/test_parser.odin index cc180f9af..3fb15d893 100644 --- a/tests/core/odin/test_parser.odin +++ b/tests/core/odin/test_parser.odin @@ -94,3 +94,40 @@ test_parse_stb_image :: proc(t: ^testing.T) { testing.expectf(t, value.syntax_error_count == 0, "%v should contain zero errors", key) } } + +@test +test_parse_multiline_ternary :: proc(t: ^testing.T) { + context.allocator = context.temp_allocator + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + file := ast.File{ + fullpath = "test.odin", + src = ` +package main + +my_func :: proc (cond: bool, a: string, b: string) -> string { + out := ( + cond + ? a + : b + ) + return out +} + `, + } + + p := parser.default_parser() + + p.err = proc(pos: tokenizer.Pos, format: string, args: ..any) { + message := fmt.tprintf(format, ..args) + log.errorf("%s(%d:%d): %s", pos.file, pos.line, pos.column, message) + } + + p.warn = proc(pos: tokenizer.Pos, format: string, args: ..any) { + message := fmt.tprintf(format, ..args) + log.warnf("%s(%d:%d): %s", pos.file, pos.line, pos.column, message) + } + + ok := parser.parse_file(&p, &file) + testing.expect(t, ok, "bad parse") + testing.expect(t, file.syntax_error_count == 0, "should contain zero errors") +} From 5dd0041eb2372abb35527c41cd23f27e65255ee4 Mon Sep 17 00:00:00 2001 From: Franz Date: Mon, 22 Jun 2026 12:32:39 +0200 Subject: [PATCH 08/21] Raise child process error signal if `odin run` child gets terminated. Previously one would get a nice `$ pid segmentation fault (core dumped) odin run .` message, but this behaviour was (probably unintentionally) removed in #6848. --- src/main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 89b2654d4..3a72c2a9c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -206,6 +206,9 @@ int run_subprocess(const char *name, const char **args) { if (WIFEXITED(status)) { return WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { + struct rlimit limit = { 0, 0, }; + setrlimit(RLIMIT_CORE, &limit); + raise(WTERMSIG(status)); return -1; } else if (WIFSTOPPED(status)) { return -1; From 67cd4669749a9d5fe3292bad52308da1001d0275 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 22 Jun 2026 12:45:44 +0100 Subject: [PATCH 09/21] Fix the mistake caused by #6845 --- src/types.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/types.cpp b/src/types.cpp index 759f90ca6..66de402f8 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -3094,7 +3094,7 @@ gb_internal bool lookup_subtype_polymorphic_selection(Type *dst, Type *src, Sele return true; } } - if ((f->flags & EntityFlag_Using) != 0 && is_type_struct(f->type)) { + if ((f->flags & EntityFlags_IsSubtype) != 0 && is_type_struct(f->type)) { String name = lookup_subtype_polymorphic_field(dst, f->type); if (name.len > 0) { array_add(&sel->index, cast(i32)i); @@ -5030,11 +5030,7 @@ gb_internal isize check_is_assignable_to_using_subtype(Type *src, Type *dst, isi return level+1; } } - // Only follow the chain transitively when the field also has `using`, which is - // what the backend's lookup_subtype_polymorphic_selection requires (it gates - // recursion on EntityFlag_Using). A plain `#subtype`-only field enables a - // single-hop conversion but not a two-or-more hop transitive one. - if (f->flags & EntityFlag_Using) { + if (f->flags & EntityFlags_IsSubtype) { isize nested_level = check_is_assignable_to_using_subtype(f->type, dst, level+1, src_is_ptr, allow_polymorphic); if (nested_level > 0) { return nested_level; From baef272bbd08c365fe961abd76084227fc91c0d4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jun 2026 15:51:27 +0100 Subject: [PATCH 10/21] Support constant compound literals --- src/check_expr.cpp | 20 ++-- src/llvm_backend_const.cpp | 186 ++++++++++++++++++++++++++++++++++++- src/llvm_backend_expr.cpp | 10 -- src/types.cpp | 17 +++- 4 files changed, 212 insertions(+), 21 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index cbaea1483..ad088b00c 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -10175,7 +10175,7 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, Slicekind == Type_BitField) { - assignment_str = str_lit("bit_field literal"); + assignment_str = str_lit("'bit_field' literal"); } for (Ast *elem : elems) { @@ -10187,14 +10187,14 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, Slicefield; if (ident->kind == Ast_ImplicitSelectorExpr) { gbString expr_str = expr_to_string(ident); - error(ident, "Field names do not start with a '.', remove the '.' in structure literal", expr_str); + error(ident, "Field names do not start with a '.', remove the '.' in %.*s", expr_str, LIT(assignment_str)); gb_string_free(expr_str); ident = ident->ImplicitSelectorExpr.selector; } if (ident->kind != Ast_Ident) { gbString expr_str = expr_to_string(ident); - error(elem, "Invalid field name '%s' in structure literal", expr_str); + error(elem, "Invalid field name '%s' in %.*s", expr_str, LIT(assignment_str)); gb_string_free(expr_str); continue; } @@ -10204,7 +10204,9 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, Slicemode == Addressing_Type); bool is_unknown = sel.entity == nullptr; if (is_unknown) { - error(ident, "Unknown field '%.*s' in structure literal", LIT(name)); + gbString s = type_to_string(type); + error(ident, "Unknown field '%.*s' in %.*s", LIT(name), LIT(assignment_str)); + gb_string_free(s); continue; } @@ -10258,7 +10260,7 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, SliceArray.elem; break; case Type_BitField: - is_constant = false; + // is_constant = false; ft = bt->BitField.fields[index]->type; break; default: @@ -10307,6 +10309,12 @@ gb_internal void check_compound_literal_field_values(CheckerContext *c, Slicetype); } + if (bt->kind == Type_BitField) { + if (is_type_different_to_arch_endianness(field->type)) { + is_constant = false; + } + } + u8 prev_bit_field_bit_size = c->bit_field_bit_size; if (field->kind == Entity_Variable && field->Variable.bit_field_bit_size) { @@ -11358,7 +11366,7 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * if (cl->elems.count == 0) { break; // NOTE(bill): No need to init } - is_constant = false; + // is_constant = false; if (cl->elems[0]->kind != Ast_FieldValue) { gbString type_str = type_to_string(type); error(node, "%s ('bit_field') compound literals are only allowed to contain 'field = value' elements", type_str); diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index a59ed4401..73e927e08 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -1,3 +1,12 @@ +gb_internal LLVMValueRef lb_const_low_bits_mask(LLVMTypeRef type, u64 bit_count) { + GB_ASSERT(bit_count <= 64); + if (bit_count == 0) { + return LLVMConstInt(type, 0, false); + } + u64 mask = bit_count == 64 ? ~0ull : (1ull<BitField.backing_type); + + struct FieldData { + Type *field_type; + u64 bit_offset; + u64 bit_size; + }; + auto values = array_make(temporary_allocator(), 0, cl->elems.count); + auto fields = array_make(temporary_allocator(), 0, cl->elems.count); + + for (Ast *elem : cl->elems) { + ast_node(fv, FieldValue, elem); + InternedString interned = fv->field->Ident.interned; + Selection sel = lookup_field(bt, interned, false); + GB_ASSERT(sel.is_bit_field); + GB_ASSERT(!sel.indirect); + GB_ASSERT(sel.index.count == 1); + GB_ASSERT(sel.entity != nullptr); + + i64 index = sel.index[0]; + Entity *f = bt->BitField.fields[index]; + GB_ASSERT(f == sel.entity); + i64 bit_offset = bt->BitField.bit_offsets[index]; + i64 bit_size = bt->BitField.bit_sizes[index]; + GB_ASSERT(bit_size > 0); + + Type *field_type = sel.entity->type; + if (fv->value->tav.mode != Addressing_Constant) { + continue; + } + lbValue field_expr = lb_const_value(m, field_type, fv->value->tav.value, field_type); + array_add(&values, field_expr); + array_add(&fields, FieldData{field_type, cast(u64)bit_offset, cast(u64)bit_size}); + } + + // NOTE(bill): inline insertion sort should be good enough, right? + for (isize i = 1; i < values.count; i++) { + for (isize j = i; + j > 0 && fields[i].bit_offset < fields[j].bit_offset; + j--) { + auto vtmp = values[j]; + values[j] = values[j-1]; + values[j-1] = vtmp; + + auto ftmp = fields[j]; + fields[j] = fields[j-1]; + fields[j-1] = ftmp; + } + } + + bool any_fields_different_endian = false; + for (auto const &f : fields) { + if (is_type_different_to_arch_endianness(f.field_type)) { + // NOTE(bill): Just be slow for this, to be correct + any_fields_different_endian = true; + break; + } + } + GB_ASSERT(!any_fields_different_endian); + + + Type *backing_type = core_type(bt->BitField.backing_type); + GB_ASSERT(is_type_integer(backing_type) || + (is_type_array(backing_type) && is_type_integer(backing_type->Array.elem))); + + if (is_type_integer(backing_type)) { + // SINGLE INTEGER BACKING ONLY + + LLVMTypeRef lit = lb_type(m, backing_type); + + LLVMValueRef res = LLVMConstInt(lit, 0, false); + + for (isize i = 0; i < fields.count; i++) { + auto const &f = fields[i]; + + LLVMValueRef mask = lb_const_low_bits_mask(lit, f.bit_size); + + LLVMValueRef elem = values[i].value; + if (lb_sizeof(lit) < lb_sizeof(LLVMTypeOf(elem))) { + elem = LLVMBuildTrunc(m->const_dummy_builder, elem, lit, ""); + } else { + elem = LLVMBuildZExt(m->const_dummy_builder, elem, lit, ""); + } + elem = LLVMBuildAnd(m->const_dummy_builder, elem, mask, ""); + + elem = LLVMBuildShl(m->const_dummy_builder, elem, LLVMConstInt(lit, f.bit_offset, false), ""); + + res = LLVMBuildOr(m->const_dummy_builder, res, elem, ""); + } + + return {res, type}; + } else if (is_type_array(backing_type)) { + // ARRAY OF INTEGER BACKING + + i64 array_count = backing_type->Array.count; + LLVMTypeRef lit = lb_type(m, core_type(backing_type->Array.elem)); + + LLVMValueRef *elems = gb_alloc_array(temporary_allocator(), LLVMValueRef, array_count); + for (i64 i = 0; i < array_count; i++) { + elems[i] = LLVMConstInt(lit, 0, false); + } + + u64 elem_bit_size = cast(u64)(8*type_size_of(backing_type->Array.elem)); + u64 curr_bit_offset = 0; + for (isize i = 0; i < fields.count; i++) { + auto const &f = fields[i]; + + LLVMValueRef val = values[i].value; + LLVMTypeRef vt = lb_type(m, values[i].type); + + curr_bit_offset = f.bit_offset; + + for (u64 bits_to_set = f.bit_size; + bits_to_set > 0; + /**/) { + i64 elem_idx = curr_bit_offset/elem_bit_size; + u64 elem_bit_offset = curr_bit_offset%elem_bit_size; + + u64 mask_width = gb_min(bits_to_set, elem_bit_size-elem_bit_offset); + GB_ASSERT(mask_width > 0); + bits_to_set -= mask_width; + + LLVMValueRef mask = lb_const_low_bits_mask(vt, mask_width); + + LLVMValueRef to_set = LLVMBuildAnd(m->const_dummy_builder, val, mask, ""); + + if (elem_bit_offset != 0) { + to_set = LLVMBuildShl(m->const_dummy_builder, to_set, LLVMConstInt(vt, elem_bit_offset, false), ""); + } + to_set = LLVMBuildTrunc(m->const_dummy_builder, to_set, lit, ""); + + if (LLVMIsNull(elems[elem_idx])) { + elems[elem_idx] = to_set; // don't even bother doing `0 | to_set` + } else { + elems[elem_idx] = LLVMBuildOr(m->const_dummy_builder, elems[elem_idx], to_set, ""); + } + + if (mask_width != 0) { + val = LLVMBuildLShr(m->const_dummy_builder, val, LLVMConstInt(vt, mask_width, false), ""); + } + curr_bit_offset += mask_width; + } + + GB_ASSERT_MSG(curr_bit_offset == f.bit_offset + f.bit_size, "%llu == %llu + %llu", + cast(unsigned long long)curr_bit_offset, + cast(unsigned long long)f.bit_offset, + cast(unsigned long long)f.bit_size + ); + } + + LLVMValueRef res = LLVMConstArray(lit, elems, cast(unsigned)array_count); + return {res, type}; + } else { + // SLOW STORAGE + GB_PANIC("TODO(bill): bit_field storage of an unknown kind"); + return {}; + } +} + + gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Type *value_type, lbConstContext cc) { if (cc.allow_local) { cc.is_rodata = false; @@ -728,8 +904,10 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty lbValue res = {}; res.type = original_type; - type = core_type(type); - value = convert_exact_value_for_type(value, type); + if (!is_type_bit_field(original_type)) { + type = core_type(type); + value = convert_exact_value_for_type(value, type); + } bool is_local = cc.allow_local && m->curr_procedure != nullptr; @@ -1278,7 +1456,9 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, Ty return res; case ExactValue_Compound: - if (is_type_slice(type)) { + if (is_type_bit_field(original_type)) { + return lb_const_value_bit_field(m, original_type, value.value_compound); + } else if (is_type_slice(type)) { return lb_const_value(m, type, value, value_type, cc); } else if (is_type_soa_struct(type)) { GB_ASSERT(type->kind == Type_Struct); diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 554ac79b7..e02f743ca 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -2,15 +2,6 @@ gb_internal lbValue lb_emit_arith_matrix(lbProcedure *p, TokenKind op, lbValue l gb_internal lbValue lb_build_slice_expr_value(lbProcedure *p, Ast *expr); gb_internal lbValue lb_expand_values(lbProcedure *p, lbValue val, Type *type); -gb_internal LLVMValueRef lb_const_low_bits_mask(LLVMTypeRef type, u64 bit_count) { - GB_ASSERT(bit_count <= 64); - if (bit_count == 0) { - return LLVMConstInt(type, 0, false); - } - u64 mask = bit_count == 64 ? ~0ull : (1ull<module; @@ -4422,7 +4413,6 @@ gb_internal lbValue lb_build_expr_internal(lbProcedure *p, Ast *expr) { GB_ASSERT_MSG(tv.mode != Addressing_Invalid, "invalid expression '%s' (tv.mode = %d, tv.type = %s) @ %s\n Current Proc: %.*s : %s", expr_to_string(expr), tv.mode, type_to_string(tv.type), token_pos_to_string(expr_pos), LIT(p->name), type_to_string(p->type)); - if (tv.value.kind != ExactValue_Invalid) { Type *original_type = lb_build_expr_original_const_type(expr); // NOTE(bill): Short on constant values diff --git a/src/types.cpp b/src/types.cpp index 66de402f8..45486e6bb 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -3829,8 +3829,22 @@ gb_internal Selection lookup_field_with_selection(Type *type_, InternedString fi } } else if (type->kind == Type_BitSet) { return lookup_field_with_selection(type->BitSet.elem, field_name, true, sel, allow_blank_ident); - } + } else if (type->kind == Type_BitField) { + for_array(i, type->BitField.fields) { + Entity *f = type->BitField.fields[i]; + if (f->kind != Entity_Variable || (f->flags & EntityFlag_Field) == 0) { + continue; + } + auto str = entity_interned_name(f); + if (field_name == str) { + selection_add_index(&sel, i); // HACK(bill): Leaky memory + sel.entity = f; + sel.is_bit_field = true; + return sel; + } + } + } if (type->kind == Type_Generic && type->Generic.specialized != nullptr) { Type *specialized = type->Generic.specialized; @@ -3929,7 +3943,6 @@ gb_internal Selection lookup_field_with_selection(Type *type_, InternedString fi return sel; } } - } else if (type->kind == Type_Basic) { switch (type->Basic.kind) { case Basic_any: { From a7186b8af00b26edc3893b02eb1aa5b6e156e8fb Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 14 Jun 2026 18:55:26 +0100 Subject: [PATCH 11/21] Minimize error propagation of `map[key]` indexing --- src/check_expr.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index ad088b00c..46fbc6717 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -11768,11 +11768,11 @@ gb_internal ExprKind check_index_expr(CheckerContext *c, Operand *o, Ast *node, check_expr_with_type_hint(c, &key, ie->index, t->Map.key); } check_assignment(c, &key, t->Map.key, str_lit("map index")); - if (key.mode == Addressing_Invalid) { - o->mode = Addressing_Invalid; - o->expr = node; - return kind; - } + // if (key.mode == Addressing_Invalid) { + // o->mode = Addressing_Invalid; + // o->expr = node; + // return kind; + // } o->mode = Addressing_MapIndex; o->type = t->Map.value; o->expr = node; From 6af1fe3e22db92a05dc665d14bf2f85073e07858 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jun 2026 11:44:00 +0100 Subject: [PATCH 12/21] doc writer: Use string interning and minimize the size of the compound literal generation when it is HUGE --- src/docs_writer.cpp | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index e0b0a05c1..6b700a3bf 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -370,7 +370,7 @@ gb_internal OdinDocString odin_doc_pkg_doc_string(OdinDocWriter *w, AstPackage * if (pkg == nullptr) { return {}; } - auto buf = array_make(permanent_allocator(), 0, 0); // Minor leak + auto buf = array_make(heap_allocator(), 0, 0); for_array(i, pkg->files) { AstFile *f = pkg->files[i]; @@ -380,31 +380,37 @@ gb_internal OdinDocString odin_doc_pkg_doc_string(OdinDocWriter *w, AstPackage * } } - return odin_doc_write_string_without_cache(w, make_string(buf.data, buf.count)); + String str = string_intern_string(make_string(buf.data, buf.count)); + array_free(&buf); + return odin_doc_write_string_without_cache(w, str); } gb_internal OdinDocString odin_doc_comment_group_string(OdinDocWriter *w, CommentGroup *g) { if (g == nullptr) { return {}; } - auto buf = array_make(permanent_allocator(), 0, 0); // Minor leak + auto buf = array_make(heap_allocator(), 0, 0); odin_doc_append_comment_group_string(&buf, g); - return odin_doc_write_string_without_cache(w, make_string(buf.data, buf.count)); + String str = string_intern_string(make_string(buf.data, buf.count)); + array_free(&buf); + return odin_doc_write_string_without_cache(w, str); } -gb_internal OdinDocString odin_doc_expr_string(OdinDocWriter *w, Ast *expr) { +gb_internal OdinDocString odin_doc_expr_string(OdinDocWriter *w, Ast *expr, bool use_shorthand=false) { if (expr == nullptr) { return {}; } - gbString s = write_expr_to_string( // Minor leak - gb_string_make(permanent_allocator(), ""), + gbString s = write_expr_to_string( + gb_string_make(heap_allocator(), ""), expr, - build_context.cmd_doc_flags & CmdDocFlag_Short + use_shorthand || (build_context.cmd_doc_flags & CmdDocFlag_Short) ); + String str = string_intern_string(make_string(cast(u8 *)s, gb_string_length(s))); + gb_string_free(s); - return odin_doc_write_string(w, make_string(cast(u8 *)s, gb_string_length(s))); + return odin_doc_write_string(w, str); } gb_internal OdinDocArray odin_doc_attributes(OdinDocWriter *w, Array const &attributes) { @@ -920,7 +926,16 @@ gb_internal OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) OdinDocString init_string = {}; if (init_expr) { - init_string = odin_doc_expr_string(w, init_expr); + bool use_shorthand = false; + if (e->kind == Entity_Variable) { + Ast *expr = init_expr; + if (expr->kind == Ast_CompoundLit) { + if (expr->CompoundLit.elems.count > 512) { + use_shorthand = true; + } + } + } + init_string = odin_doc_expr_string(w, init_expr, use_shorthand); } else { if (e->kind == Entity_Constant) { if (e->Constant.flags & EntityConstantFlag_ImplicitEnumValue) { From b3f6d9ce9b800dca43b321bf4665c56b900c2c75 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jun 2026 11:46:26 +0100 Subject: [PATCH 13/21] doc_writer: String intern constant values expressions --- src/docs_writer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 6b700a3bf..7a12d0875 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -943,7 +943,10 @@ gb_internal OdinDocEntityIndex odin_doc_add_entity(OdinDocWriter *w, Entity *e) } else if (e->Constant.param_value.original_ast_expr) { init_string = odin_doc_expr_string(w, e->Constant.param_value.original_ast_expr); } else { - init_string = odin_doc_write_string(w, make_string_c(exact_value_to_string(e->Constant.value))); + gbString s = exact_value_to_string(e->Constant.value); + String str = string_intern_string(make_string(cast(u8 *)s, gb_string_length(s))); + gb_string_free(s); + init_string = odin_doc_write_string(w, str); } } else if (e->kind == Entity_Variable) { if (e->Variable.param_value.original_ast_expr) { From 244430bd4ac57d5f1cc122ce0ce1d465b5731c86 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jun 2026 14:42:38 +0100 Subject: [PATCH 14/21] Allow for `struct #raw_union #packed` --- core/odin/parser/parser.odin | 5 ----- src/parser.cpp | 4 ---- 2 files changed, 9 deletions(-) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index eb2a3e1b2..2339e0d6e 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -2812,11 +2812,6 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { } p.expr_level = prev_level - if is_raw_union && is_packed { - is_packed = false - error(p, tok.pos, "'#raw_union' cannot also be '#packed") - } - if is_raw_union && is_all_or_none { is_all_or_none = false error(p, tok.pos, "'#raw_union' cannot also be '#all_or_none") diff --git a/src/parser.cpp b/src/parser.cpp index 60e5ac9e1..14258b5c6 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2894,10 +2894,6 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { f->expr_level = prev_level; - if (is_raw_union && is_packed) { - is_packed = false; - syntax_error(token, "'#raw_union' cannot also be '#packed'"); - } if (is_raw_union && is_all_or_none) { is_all_or_none = false; syntax_error(token, "'#raw_union' cannot also be '#all_or_none'"); From 900ebd6b5bee9f7c257eed5c8b01f1229b298dc5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jun 2026 16:53:15 +0100 Subject: [PATCH 15/21] Heavily improve reading and writing to bit fields --- base/runtime/internal.odin | 97 ++++++++++++----- src/llvm_backend_general.cpp | 203 +++++++++++++++++++++++++---------- 2 files changed, 217 insertions(+), 83 deletions(-) diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index bb9fc4b36..091feece7 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -8,10 +8,10 @@ IS_WASM :: ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 @(private) RUNTIME_LINKAGE :: "strong" when ODIN_USE_SEPARATE_MODULES else - "internal" when ODIN_NO_ENTRY_POINT && (ODIN_BUILD_MODE == .Static || ODIN_BUILD_MODE == .Dynamic || ODIN_BUILD_MODE == .Object) else - "strong" when ODIN_BUILD_MODE == .Dynamic else - "strong" when !ODIN_NO_CRT else - "internal" + "internal" when ODIN_NO_ENTRY_POINT && (ODIN_BUILD_MODE == .Static || ODIN_BUILD_MODE == .Dynamic || ODIN_BUILD_MODE == .Object) else + "strong" when ODIN_BUILD_MODE == .Dynamic else + "strong" when !ODIN_NO_CRT else + "internal" RUNTIME_REQUIRE :: false // !ODIN_TILDE @(private) @@ -24,7 +24,7 @@ HAS_HARDWARE_SIMD :: false when (ODIN_ARCH == .amd64 || ODIN_ARCH == .i386) && ! true // Size of a native SIMD register for the current compilation target -NATIVE_SIMD_BIT_WIDTH :: +NATIVE_SIMD_BIT_WIDTH :: 512 when (ODIN_ARCH == .amd64) && intrinsics.has_target_feature("avx512f") else 256 when (ODIN_ARCH == .amd64) && (intrinsics.has_target_feature("avx2") || intrinsics.has_target_feature("avx")) else // Fallback for no hardware SIMD, but also SSE, NEON, SVE, RVV and WASM SIMD128. @@ -1186,7 +1186,7 @@ floattidf :: proc "c" (a: i128) -> f64 { // okay case: a = i128(u128(a) >> u128(sd - (DBL_MANT_DIG+2))) | - i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0) + i128(u128(a) & (~u128(0) >> u128(N + DBL_MANT_DIG+2 - sd)) != 0) } a |= i128((a & 4) != 0) @@ -1202,8 +1202,8 @@ floattidf :: proc "c" (a: i128) -> f64 { } fb: [2]u32 fb[1] = (u32(s) & 0x80000000) | // sign - (u32(e + 1023) << 20) | // exponent - u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high + (u32(e + 1023) << 20) | // exponent + u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high fb[0] = u32(a) // mantissa-low return transmute(f64)fb } @@ -1243,8 +1243,8 @@ floattidf_unsigned :: proc "c" (a: u128) -> f64 { } fb: [2]u32 fb[1] = (0) | // sign - u32((e + 1023) << 20) | // exponent - u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high + u32((e + 1023) << 20) | // exponent + u32((u64(a) >> 32) & 0x000FFFFF) // mantissa-high fb[0] = u32(a) // mantissa-low return transmute(f64)fb } @@ -1374,24 +1374,71 @@ fixdfti :: proc "c" (a: u64) -> i128 { } - - -__write_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) { - for i in 0..>3]) & (1<<(i&7)) != 0) - dst[j>>3] &~= 1<<(j&7) - dst[j>>3] |= the_bit<<(j&7) +__copy_bits :: #force_inline proc "contextless" ( + dst: [^]byte, + src: [^]byte, + buf_bytes: uintptr, + dst_bit: uintptr, + src_bit: uintptr, + size_bits: uintptr, +) #no_bounds_check { + src_byte := src_bit >> 3 + dst_byte := dst_bit >> 3 + src_shift := src_bit & 7 + dst_shift := dst_bit & 7 + src_need_bytes := ((src_shift + size_bits + 7) >> 3) + a, b: u64 + if src_need_bytes <= 4 { + a = u64(intrinsics.unaligned_load((^u32)(&src[src_byte]))) + } else { + a = intrinsics.unaligned_load((^u64)(&src[src_byte])) + b = intrinsics.unaligned_load((^u64)(&src[src_byte + 8])) + } + bits := (a >> src_shift) | (b << (64 - src_shift)) + mask := ~u64(0) + if size_bits < 64 { + mask = (u64(1) << size_bits) - 1 + } + bits &= mask + dst_need_bytes := ((dst_shift + size_bits + 7) >> 3) + if dst_shift == 0 { + if dst_need_bytes <= 4 { + v := u64(intrinsics.unaligned_load((^u32)(&dst[dst_byte]))) + v = (v & ~mask) | bits + intrinsics.unaligned_store((^u32)(&dst[dst_byte]), u32(v)) + } else { + v := intrinsics.unaligned_load((^u64)(&dst[dst_byte])) + v = (v & ~mask) | bits + intrinsics.unaligned_store((^u64)(&dst[dst_byte]), v) + } + } else { + v0 := intrinsics.unaligned_load((^u64)(&dst[dst_byte])) + v1 := intrinsics.unaligned_load((^u64)(&dst[dst_byte + 8])) + v0 = (v0 & ~(mask << dst_shift)) | (bits << dst_shift) + v1 = (v1 & ~(mask >> (64 - dst_shift))) | (bits >> (64 - dst_shift)) + intrinsics.unaligned_store((^u64)(&dst[dst_byte]), v0) + intrinsics.unaligned_store((^u64)(&dst[dst_byte + 8]), v1) } } -__read_bits :: proc "contextless" (dst, src: [^]byte, offset: uintptr, size: uintptr) { - for j in 0..>3]) & (1<<(i&7)) != 0) - dst[j>>3] &~= 1<<(j&7) - dst[j>>3] |= the_bit<<(j&7) - } +__write_bits :: proc "contextless" (dst, src: [^]byte, dst_size_bytes: uintptr, offset_bits: uintptr, size_bits: uintptr) { + __copy_bits(dst, src, dst_size_bytes, offset_bits, 0, size_bits) + // for i in 0..>3]) & (1<<(i&7)) != 0) + // dst[j>>3] &~= 1<<(j&7) + // dst[j>>3] |= the_bit<<(j&7) + // } +} + +__read_bits :: proc "contextless" (dst, src: [^]byte, src_size_bytes: uintptr, offset_bits: uintptr, size_bits: uintptr) { + __copy_bits(dst, src, src_size_bytes, 0, offset_bits, size_bits) + // for j in 0..>3]) & (1<<(i&7)) != 0) + // dst[j>>3] &~= 1<<(j&7) + // dst[j>>3] |= the_bit<<(j&7) + // } } when .Address in ODIN_SANITIZER_FLAGS { diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 64829d4c4..00bbc77c0 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -964,6 +964,133 @@ gb_internal LLVMValueRef OdinLLVMBuildLoadAligned(lbProcedure *p, LLVMTypeRef ty return result; } +// gb_internal void OdinLLVMBuildUnalignedStore(lbProcedure *p, LLVMValueRef dst, LLVMValueRef src, Type *ptr_type) { +// Type *t = type_deref(ptr_type); + +// if (is_type_simd_vector(t)) { +// LLVMValueRef store = LLVMBuildStore(p->builder, src, dst); +// LLVMSetAlignment(store, 1); +// } else { +// lb_mem_copy_non_overlapping(p, {dst, ptr_type}, {src, ptr_type}, lb_const_int(p->module, t_int, type_size_of(t)), false); +// } + +// } +gb_internal LLVMValueRef OdinLLVMBuildUnalignedLoad(lbProcedure *p, LLVMValueRef src, Type *ptr_type) { + LLVMTypeRef type = lb_type(p->module, type_deref(ptr_type)); + + src = LLVMBuildPointerCast(p->builder, src, lb_type(p->module, ptr_type), ""); + LLVMValueRef load = LLVMBuildLoad2(p->builder, type, src, ""); + LLVMSetAlignment(load, 1); + return load; +} + +gb_internal void lb_copy_bits(lbProcedure *p, + LLVMValueRef dst, + LLVMValueRef src, + u64 buf_bytes, + u64 dst_bit, + u64 src_bit, + u64 size_bits +) { + auto ptr_offset = [](lbProcedure *p, LLVMValueRef ptr, u64 offset) -> LLVMValueRef { + LLVMValueRef indices[1] = {LLVMConstInt(lb_type(p->module, t_u64), offset, false)}; + ptr = LLVMBuildPointerCast(p->builder, ptr, lb_type(p->module, t_u8_ptr), ""); + return LLVMBuildGEP2(p->builder, lb_type(p->module, t_u8), ptr, indices, 1, ""); + }; + + Type *t_u32_ptr = alloc_type_pointer(t_u32); + Type *t_u64_ptr = alloc_type_pointer(t_u64); + LLVMTypeRef llvm_u32 = lb_type(p->module, t_u32); + LLVMTypeRef llvm_u64 = lb_type(p->module, t_u64); + LLVMTypeRef llvm_u32_ptr = lb_type(p->module, t_u32_ptr); + LLVMTypeRef llvm_u64_ptr = lb_type(p->module, t_u64_ptr); + + dst = LLVMBuildPointerCast(p->builder, dst, lb_type(p->module, t_u8_ptr), ""); + src = LLVMBuildPointerCast(p->builder, src, lb_type(p->module, t_u8_ptr), ""); + + u64 src_byte = src_bit>>3; + u64 dst_byte = dst_bit>>3; + u64 src_shift = src_bit&7; + u64 dst_shift = dst_bit&7; + u64 src_need_bytes = (src_shift + size_bits + 7)>>3; + + LLVMValueRef a = LLVMConstInt(llvm_u64, 0, false); + LLVMValueRef b = LLVMConstInt(llvm_u64, 0, false); + if (src_need_bytes <= 4) { + // a = u64(intrinsics.unaligned_load((^u32)(&src[src_byte]))) + a = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, src, src_byte), t_u32_ptr); + a = LLVMBuildZExt(p->builder, a, llvm_u64, ""); + } else { + // a = intrinsics.unaligned_load((^u64)(&src[src_byte])) + a = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, src, src_byte), t_u64_ptr); + // b = intrinsics.unaligned_load((^u64)(&src[src_byte + 8])) + b = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, src, src_byte + 8), t_u64_ptr); + } + + // bits := (a >> src_shift) | (b << (64 - src_shift)) + LLVMValueRef bits = LLVMBuildOr(p->builder, + LLVMBuildLShr(p->builder, a, LLVMConstInt(llvm_u64, src_shift, false), ""), + LLVMBuildShl (p->builder, b, LLVMConstInt(llvm_u64, 64 - src_shift, false), ""), + "" + ); + u64 mask = ~cast(u64)0; + if (size_bits < 64) { + mask = ((cast(u64)1)<builder, bits, LLVMConstInt(llvm_u64, mask, false), ""); + + u64 dst_need_bytes = (dst_shift + size_bits + 7) >> 3; + if (dst_shift == 0) { + if (dst_need_bytes <= 4) { + // v := u64(intrinsics.unaligned_load((^u32)(&dst[dst_byte]))) + LLVMValueRef v = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, dst, dst_byte), t_u32_ptr); + v = LLVMBuildZExt(p->builder, v, llvm_u64, ""); + + // v = (v & ~mask) | bits + v = LLVMBuildAnd(p->builder, v, LLVMConstInt(llvm_u64, ~mask, false), ""); + v = LLVMBuildOr(p->builder, v, bits, ""); + + // intrinsics.unaligned_store((^u32)(&dst[dst_byte]), u32(v)) + v = LLVMBuildTrunc(p->builder, v, llvm_u32, ""); + LLVMValueRef store = LLVMBuildStore(p->builder, v, LLVMBuildPointerCast(p->builder, ptr_offset(p, dst, dst_byte), llvm_u32_ptr, "")); + LLVMSetAlignment(store, 1); + } else { + // v := intrinsics.unaligned_load((^u64)(&dst[dst_byte])) + LLVMValueRef v = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, dst, dst_byte), t_u64_ptr); + + // v = (v & ~mask) | bits + v = LLVMBuildAnd(p->builder, v, LLVMConstInt(llvm_u64, ~mask, false), ""); + v = LLVMBuildOr(p->builder, v, bits, ""); + + // intrinsics.unaligned_store((^u64)(&dst[dst_byte]), v) + LLVMValueRef store = LLVMBuildStore(p->builder, v, LLVMBuildPointerCast(p->builder, ptr_offset(p, dst, dst_byte), llvm_u64_ptr, "")); + LLVMSetAlignment(store, 1); + } + } else { + // v0 := intrinsics.unaligned_load((^u64)(&dst[dst_byte])) + // v1 := intrinsics.unaligned_load((^u64)(&dst[dst_byte + 8])) + LLVMValueRef v0 = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, dst, dst_byte+0), t_u64_ptr); + LLVMValueRef v1 = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, dst, dst_byte+8), t_u64_ptr); + + // v0 = (v0 & ~(mask << dst_shift)) | (bits << dst_shift) + v0 = LLVMBuildAnd(p->builder, v0, LLVMConstInt(llvm_u64, ~(mask << dst_shift), false), ""); + v0 = LLVMBuildOr (p->builder, v0, LLVMBuildShl(p->builder, bits, LLVMConstInt(llvm_u64, dst_shift, false), ""), ""); + + // v1 = (v1 & ~(mask >> (64 - dst_shift))) | (bits >> (64 - dst_shift)) + v1 = LLVMBuildAnd(p->builder, v1, LLVMConstInt(llvm_u64, ~(mask >> (64 - dst_shift)), false), ""); + v1 = LLVMBuildOr (p->builder, v1, LLVMBuildLShr(p->builder, bits, LLVMConstInt(llvm_u64, (64 - dst_shift), false), ""), ""); + + // intrinsics.unaligned_store((^u64)(&dst[dst_byte]), v0) + // intrinsics.unaligned_store((^u64)(&dst[dst_byte + 8]), v1) + LLVMValueRef s0 = LLVMBuildStore(p->builder, v0, LLVMBuildPointerCast(p->builder, ptr_offset(p, dst, dst_byte+0), llvm_u64_ptr, "")); + LLVMValueRef s1 = LLVMBuildStore(p->builder, v1, LLVMBuildPointerCast(p->builder, ptr_offset(p, dst, dst_byte+8), llvm_u64_ptr, "")); + LLVMSetAlignment(s0, 1); + LLVMSetAlignment(s1, 1); + } +} + + gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { if (addr.addr.value == nullptr) { return; @@ -981,6 +1108,7 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { if (addr.kind == lbAddr_BitField) { lbValue dst = addr.addr; + lbValue src = {}; if (is_type_endian_big(addr.bitfield.type)) { i64 shift_amount = 8*type_size_of(value.type) - addr.bitfield.bit_size; lbValue shifted_value = value; @@ -988,33 +1116,17 @@ gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { shifted_value.value, LLVMConstInt(LLVMTypeOf(shifted_value.value), shift_amount, false), ""); - lbValue src = lb_address_from_load_or_generate_local(p, shifted_value); - - auto args = array_make(temporary_allocator(), 4); - args[0] = dst; - args[1] = src; - args[2] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset); - args[3] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size); - lb_emit_runtime_call(p, "__write_bits", args); - } else if ((addr.bitfield.bit_offset % 8) == 0 && - (addr.bitfield.bit_size % 8) == 0) { - lbValue src = lb_address_from_load_or_generate_local(p, value); - - lbValue byte_offset = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset/8); - lbValue byte_size = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size/8); - lbValue dst_offset = lb_emit_conv(p, dst, t_u8_ptr); - dst_offset = lb_emit_ptr_offset(p, dst_offset, byte_offset); - lb_mem_copy_non_overlapping(p, dst_offset, src, byte_size); + src = lb_address_from_load_or_generate_local(p, shifted_value); } else { - lbValue src = lb_address_from_load_or_generate_local(p, value); - - auto args = array_make(temporary_allocator(), 4); - args[0] = dst; - args[1] = src; - args[2] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset); - args[3] = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size); - lb_emit_runtime_call(p, "__write_bits", args); + src = lb_address_from_load_or_generate_local(p, value); } + + u64 buf_bytes = cast(u64)type_size_of(type_deref(dst.type)); + u64 dst_bit = cast(u64)addr.bitfield.bit_offset; + u64 src_bit = cast(u64)0; + u64 size_bits = cast(u64)addr.bitfield.bit_size; + + lb_copy_bits(p, dst.value, src.value, buf_bytes, dst_bit, src_bit, size_bits); return; } else if (addr.kind == lbAddr_Map) { lb_internal_dynamic_map_set(p, addr.addr, addr.map.type, addr.map.key, value, p->curr_stmt); @@ -1296,53 +1408,28 @@ gb_internal lbValue lb_addr_load(lbProcedure *p, lbAddr const &addr) { } } - i64 total_bitfield_bit_size = 8*type_size_of(lb_addr_type(addr)); i64 dst_byte_size = type_size_of(addr.bitfield.type); lbAddr dst = lb_add_local_generated(p, addr.bitfield.type, true); lbValue src = addr.addr; - lbValue bit_offset = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_offset); - lbValue bit_size = lb_const_int(p->module, t_uintptr, addr.bitfield.bit_size); - lbValue byte_offset = lb_const_int(p->module, t_uintptr, (addr.bitfield.bit_offset+7)/8); - lbValue byte_size = lb_const_int(p->module, t_uintptr, (addr.bitfield.bit_size+7)/8); - GB_ASSERT(type_size_of(addr.bitfield.type) >= ((addr.bitfield.bit_size+7)/8)); lbValue r = {}; - if (is_type_endian_big(addr.bitfield.type)) { - auto args = array_make(temporary_allocator(), 4); - args[0] = dst.addr; - args[1] = src; - args[2] = bit_offset; - args[3] = bit_size; - lb_emit_runtime_call(p, "__read_bits", args); + u64 buf_bytes = cast(u64)type_size_of(type_deref(src.type)); + u64 dst_bit = cast(u64)0; + u64 src_bit = cast(u64)addr.bitfield.bit_offset; + u64 size_bits = cast(u64)addr.bitfield.bit_size; + + lb_copy_bits(p, dst.addr.value, src.value, buf_bytes, dst_bit, src_bit, size_bits); + r = lb_addr_load(p, dst); + if (is_type_endian_big(addr.bitfield.type)) { LLVMValueRef shift_amount = LLVMConstInt( lb_type(p->module, lb_addr_type(dst)), 8*dst_byte_size - addr.bitfield.bit_size, false ); - r = lb_addr_load(p, dst); r.value = LLVMBuildShl(p->builder, r.value, shift_amount, ""); - } else if ((addr.bitfield.bit_offset % 8) == 0) { - do_mask = 8*dst_byte_size != addr.bitfield.bit_size; - - lbValue copy_size = byte_size; - lbValue src_offset = lb_emit_conv(p, src, t_u8_ptr); - src_offset = lb_emit_ptr_offset(p, src_offset, byte_offset); - if (addr.bitfield.bit_offset + 8*dst_byte_size <= total_bitfield_bit_size) { - copy_size = lb_const_int(p->module, t_uintptr, dst_byte_size); - } - lb_mem_copy_non_overlapping(p, dst.addr, src_offset, copy_size, false); - r = lb_addr_load(p, dst); - } else { - auto args = array_make(temporary_allocator(), 4); - args[0] = dst.addr; - args[1] = src; - args[2] = bit_offset; - args[3] = bit_size; - lb_emit_runtime_call(p, "__read_bits", args); - r = lb_addr_load(p, dst); } Type *t = addr.bitfield.type; From 588a8148f2e8fac5da7ba871847e11cb9d5e6a51 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jun 2026 16:57:27 +0100 Subject: [PATCH 16/21] Remove the now defunct `__write_bits` and `__read_bits` --- base/runtime/internal.odin | 21 +-------------------- src/check_expr.cpp | 5 ----- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index 091feece7..3847ce046 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -1374,7 +1374,7 @@ fixdfti :: proc "c" (a: u64) -> i128 { } -__copy_bits :: #force_inline proc "contextless" ( +__copy_bits :: proc "contextless" ( dst: [^]byte, src: [^]byte, buf_bytes: uintptr, @@ -1421,25 +1421,6 @@ __copy_bits :: #force_inline proc "contextless" ( } } -__write_bits :: proc "contextless" (dst, src: [^]byte, dst_size_bytes: uintptr, offset_bits: uintptr, size_bits: uintptr) { - __copy_bits(dst, src, dst_size_bytes, offset_bits, 0, size_bits) - // for i in 0..>3]) & (1<<(i&7)) != 0) - // dst[j>>3] &~= 1<<(j&7) - // dst[j>>3] |= the_bit<<(j&7) - // } -} - -__read_bits :: proc "contextless" (dst, src: [^]byte, src_size_bytes: uintptr, offset_bits: uintptr, size_bits: uintptr) { - __copy_bits(dst, src, src_size_bytes, 0, offset_bits, size_bits) - // for j in 0..>3]) & (1<<(i&7)) != 0) - // dst[j>>3] &~= 1<<(j&7) - // dst[j>>3] |= the_bit<<(j&7) - // } -} when .Address in ODIN_SANITIZER_FLAGS { foreign { diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 46fbc6717..53d72f35b 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6131,11 +6131,6 @@ gb_internal Entity *check_selector(CheckerContext *c, Operand *operand, Ast *nod operand->type = entity->type; operand->expr = node; - if (entity->flags & EntityFlag_BitFieldField) { - add_package_dependency(c, "runtime", "__write_bits"); - add_package_dependency(c, "runtime", "__read_bits"); - } - switch (entity->kind) { case Entity_Constant: operand->value = entity->Constant.value; From 8971e0f1ec59346be463721797335cde28519483 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jun 2026 17:13:33 +0100 Subject: [PATCH 17/21] Fix shifting buf in lb_copy_bits --- src/llvm_backend_general.cpp | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 00bbc77c0..c3d89b583 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -1023,16 +1023,28 @@ gb_internal void lb_copy_bits(lbProcedure *p, } else { // a = intrinsics.unaligned_load((^u64)(&src[src_byte])) a = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, src, src_byte), t_u64_ptr); - // b = intrinsics.unaligned_load((^u64)(&src[src_byte + 8])) - b = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, src, src_byte + 8), t_u64_ptr); + if (src_need_bytes > 8) { + // b = intrinsics.unaligned_load((^u64)(&src[src_byte + 8])) + b = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, src, src_byte + 8), t_u64_ptr); + } } - // bits := (a >> src_shift) | (b << (64 - src_shift)) + LLVMValueRef hi; + if (src_shift == 0) { + hi = LLVMConstInt(llvm_u64, 0, false); + } else { + hi = LLVMBuildShl(p->builder, b, LLVMConstInt(llvm_u64, 64 - src_shift, false), ""); + } LLVMValueRef bits = LLVMBuildOr(p->builder, - LLVMBuildLShr(p->builder, a, LLVMConstInt(llvm_u64, src_shift, false), ""), - LLVMBuildShl (p->builder, b, LLVMConstInt(llvm_u64, 64 - src_shift, false), ""), - "" - ); + LLVMBuildLShr(p->builder, a, LLVMConstInt(llvm_u64, src_shift, false), ""), + hi, ""); + + // // bits := (a >> src_shift) | (b << (64 - src_shift)) + // LLVMValueRef bits = LLVMBuildOr(p->builder, + // LLVMBuildLShr(p->builder, a, LLVMConstInt(llvm_u64, src_shift, false), ""), + // LLVMBuildShl (p->builder, b, LLVMConstInt(llvm_u64, 64 - src_shift, false), ""), + // "" + // ); u64 mask = ~cast(u64)0; if (size_bits < 64) { mask = ((cast(u64)1)< Date: Mon, 15 Jun 2026 17:46:54 +0100 Subject: [PATCH 18/21] Improve `lb_copy_bits` --- src/llvm_backend_general.cpp | 136 ++++++++++++++--------------------- 1 file changed, 52 insertions(+), 84 deletions(-) diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index c3d89b583..d0249172c 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -992,117 +992,85 @@ gb_internal void lb_copy_bits(lbProcedure *p, u64 src_bit, u64 size_bits ) { + if (size_bits == 0) { + return; + } + GB_ASSERT(size_bits <= 64); // this routine assembles the field in a single u64 + auto ptr_offset = [](lbProcedure *p, LLVMValueRef ptr, u64 offset) -> LLVMValueRef { LLVMValueRef indices[1] = {LLVMConstInt(lb_type(p->module, t_u64), offset, false)}; ptr = LLVMBuildPointerCast(p->builder, ptr, lb_type(p->module, t_u8_ptr), ""); return LLVMBuildGEP2(p->builder, lb_type(p->module, t_u8), ptr, indices, 1, ""); }; - Type *t_u32_ptr = alloc_type_pointer(t_u32); - Type *t_u64_ptr = alloc_type_pointer(t_u64); - LLVMTypeRef llvm_u32 = lb_type(p->module, t_u32); + LLVMTypeRef llvm_u8 = lb_type(p->module, t_u8); LLVMTypeRef llvm_u64 = lb_type(p->module, t_u64); - LLVMTypeRef llvm_u32_ptr = lb_type(p->module, t_u32_ptr); - LLVMTypeRef llvm_u64_ptr = lb_type(p->module, t_u64_ptr); dst = LLVMBuildPointerCast(p->builder, dst, lb_type(p->module, t_u8_ptr), ""); src = LLVMBuildPointerCast(p->builder, src, lb_type(p->module, t_u8_ptr), ""); - u64 src_byte = src_bit>>3; - u64 dst_byte = dst_bit>>3; - u64 src_shift = src_bit&7; - u64 dst_shift = dst_bit&7; - u64 src_need_bytes = (src_shift + size_bits + 7)>>3; + u64 src_byte = src_bit >> 3; + u64 dst_byte = dst_bit >> 3; + u64 src_shift = src_bit & 7; + u64 dst_shift = dst_bit & 7; - LLVMValueRef a = LLVMConstInt(llvm_u64, 0, false); - LLVMValueRef b = LLVMConstInt(llvm_u64, 0, false); - if (src_need_bytes <= 4) { - // a = u64(intrinsics.unaligned_load((^u32)(&src[src_byte]))) - a = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, src, src_byte), t_u32_ptr); - a = LLVMBuildZExt(p->builder, a, llvm_u64, ""); - } else { - // a = intrinsics.unaligned_load((^u64)(&src[src_byte])) - a = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, src, src_byte), t_u64_ptr); - if (src_need_bytes > 8) { - // b = intrinsics.unaligned_load((^u64)(&src[src_byte + 8])) - b = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, src, src_byte + 8), t_u64_ptr); - } - } + u64 src_need_bytes = (src_shift + size_bits + 7) >> 3; // 1..9, exact span of the field + u64 dst_need_bytes = (dst_shift + size_bits + 7) >> 3; // 1..9, exact span of the field - LLVMValueRef hi; - if (src_shift == 0) { - hi = LLVMConstInt(llvm_u64, 0, false); - } else { - hi = LLVMBuildShl(p->builder, b, LLVMConstInt(llvm_u64, 64 - src_shift, false), ""); - } - LLVMValueRef bits = LLVMBuildOr(p->builder, - LLVMBuildLShr(p->builder, a, LLVMConstInt(llvm_u64, src_shift, false), ""), - hi, ""); + // These spans are exactly the bytes the field occupies, so they are in bounds + // whenever the field is. (buf_bytes must bound the buffer each pointer refers to.) + GB_ASSERT(src_byte + src_need_bytes <= buf_bytes); + GB_ASSERT(dst_byte + dst_need_bytes <= buf_bytes); - // // bits := (a >> src_shift) | (b << (64 - src_shift)) - // LLVMValueRef bits = LLVMBuildOr(p->builder, - // LLVMBuildLShr(p->builder, a, LLVMConstInt(llvm_u64, src_shift, false), ""), - // LLVMBuildShl (p->builder, b, LLVMConstInt(llvm_u64, 64 - src_shift, false), ""), - // "" - // ); u64 mask = ~cast(u64)0; if (size_bits < 64) { - mask = ((cast(u64)1)<builder, byte, llvm_u64, ""); + + // byte i sits at frame bit i*8; the field starts at frame bit src_shift + if (i*8 >= src_shift) { + u64 sh = i*8 - src_shift; // 0..63 (sh==64 only needs i==8, which requires src_shift>=1) + byte = LLVMBuildShl (p->builder, byte, LLVMConstInt(llvm_u64, sh, false), ""); + } else { + u64 sh = src_shift - i*8; // 1..7 + byte = LLVMBuildLShr(p->builder, byte, LLVMConstInt(llvm_u64, sh, false), ""); + } + bits = LLVMBuildOr(p->builder, bits, byte, ""); } - // bits &= mask bits = LLVMBuildAnd(p->builder, bits, LLVMConstInt(llvm_u64, mask, false), ""); - u64 dst_need_bytes = (dst_shift + size_bits + 7) >> 3; - if (dst_shift == 0) { - if (dst_need_bytes <= 4) { - // v := u64(intrinsics.unaligned_load((^u32)(&dst[dst_byte]))) - LLVMValueRef v = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, dst, dst_byte), t_u32_ptr); - v = LLVMBuildZExt(p->builder, v, llvm_u64, ""); + // Scatter: write exactly dst_need_bytes bytes, each a masked read-modify-write --- + for (u64 i = 0; i < dst_need_bytes; i++) { + LLVMValueRef contrib = nullptr; + u64 byte_mask = 0; // which bits (0..7) of this byte belong to the field - // v = (v & ~mask) | bits - v = LLVMBuildAnd(p->builder, v, LLVMConstInt(llvm_u64, ~mask, false), ""); - v = LLVMBuildOr(p->builder, v, bits, ""); - - // intrinsics.unaligned_store((^u32)(&dst[dst_byte]), u32(v)) - v = LLVMBuildTrunc(p->builder, v, llvm_u32, ""); - LLVMValueRef store = LLVMBuildStore(p->builder, v, LLVMBuildPointerCast(p->builder, ptr_offset(p, dst, dst_byte), llvm_u32_ptr, "")); - LLVMSetAlignment(store, 1); + if (i*8 >= dst_shift) { + u64 sh = i*8 - dst_shift; + contrib = LLVMBuildLShr(p->builder, bits, LLVMConstInt(llvm_u64, sh, false), ""); + byte_mask = (mask >> sh) & 0xff; } else { - // v := intrinsics.unaligned_load((^u64)(&dst[dst_byte])) - LLVMValueRef v = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, dst, dst_byte), t_u64_ptr); - - // v = (v & ~mask) | bits - v = LLVMBuildAnd(p->builder, v, LLVMConstInt(llvm_u64, ~mask, false), ""); - v = LLVMBuildOr(p->builder, v, bits, ""); - - // intrinsics.unaligned_store((^u64)(&dst[dst_byte]), v) - LLVMValueRef store = LLVMBuildStore(p->builder, v, LLVMBuildPointerCast(p->builder, ptr_offset(p, dst, dst_byte), llvm_u64_ptr, "")); - LLVMSetAlignment(store, 1); + u64 sh = dst_shift - i*8; // 1..7 + contrib = LLVMBuildShl(p->builder, bits, LLVMConstInt(llvm_u64, sh, false), ""); + byte_mask = (mask << sh) & 0xff; } - } else { - // v0 := intrinsics.unaligned_load((^u64)(&dst[dst_byte])) - // v1 := intrinsics.unaligned_load((^u64)(&dst[dst_byte + 8])) - LLVMValueRef v0 = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, dst, dst_byte+0), t_u64_ptr); - LLVMValueRef v1 = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, dst, dst_byte+8), t_u64_ptr); + contrib = LLVMBuildTrunc(p->builder, contrib, llvm_u8, ""); + contrib = LLVMBuildAnd(p->builder, contrib, LLVMConstInt(llvm_u8, byte_mask, false), ""); - // v0 = (v0 & ~(mask << dst_shift)) | (bits << dst_shift) - v0 = LLVMBuildAnd(p->builder, v0, LLVMConstInt(llvm_u64, ~(mask << dst_shift), false), ""); - v0 = LLVMBuildOr (p->builder, v0, LLVMBuildShl(p->builder, bits, LLVMConstInt(llvm_u64, dst_shift, false), ""), ""); + LLVMValueRef old = OdinLLVMBuildUnalignedLoad(p, ptr_offset(p, dst, dst_byte + i), t_u8_ptr); + old = LLVMBuildAnd(p->builder, old, LLVMConstInt(llvm_u8, (~byte_mask) & 0xff, false), ""); - // v1 = (v1 & ~(mask >> (64 - dst_shift))) | (bits >> (64 - dst_shift)) - v1 = LLVMBuildAnd(p->builder, v1, LLVMConstInt(llvm_u64, ~(mask >> (64 - dst_shift)), false), ""); - v1 = LLVMBuildOr (p->builder, v1, LLVMBuildLShr(p->builder, bits, LLVMConstInt(llvm_u64, (64 - dst_shift), false), ""), ""); - - // intrinsics.unaligned_store((^u64)(&dst[dst_byte]), v0) - // intrinsics.unaligned_store((^u64)(&dst[dst_byte + 8]), v1) - LLVMValueRef s0 = LLVMBuildStore(p->builder, v0, LLVMBuildPointerCast(p->builder, ptr_offset(p, dst, dst_byte+0), llvm_u64_ptr, "")); - LLVMValueRef s1 = LLVMBuildStore(p->builder, v1, LLVMBuildPointerCast(p->builder, ptr_offset(p, dst, dst_byte+8), llvm_u64_ptr, "")); - LLVMSetAlignment(s0, 1); - LLVMSetAlignment(s1, 1); + LLVMValueRef merged = LLVMBuildOr(p->builder, old, contrib, ""); + LLVMValueRef store = LLVMBuildStore(p->builder, merged, ptr_offset(p, dst, dst_byte + i)); + LLVMSetAlignment(store, 1); } } - gb_internal void lb_addr_store(lbProcedure *p, lbAddr addr, lbValue value) { if (addr.addr.value == nullptr) { return; From 233242f4eade3a0e0085700236ed13bd8919de02 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 15 Jun 2026 17:48:33 +0100 Subject: [PATCH 19/21] Remove `__copy_bits` --- base/runtime/internal.odin | 47 -------------------------------------- 1 file changed, 47 deletions(-) diff --git a/base/runtime/internal.odin b/base/runtime/internal.odin index 3847ce046..2b9cdae62 100644 --- a/base/runtime/internal.odin +++ b/base/runtime/internal.odin @@ -1374,53 +1374,6 @@ fixdfti :: proc "c" (a: u64) -> i128 { } -__copy_bits :: proc "contextless" ( - dst: [^]byte, - src: [^]byte, - buf_bytes: uintptr, - dst_bit: uintptr, - src_bit: uintptr, - size_bits: uintptr, -) #no_bounds_check { - src_byte := src_bit >> 3 - dst_byte := dst_bit >> 3 - src_shift := src_bit & 7 - dst_shift := dst_bit & 7 - src_need_bytes := ((src_shift + size_bits + 7) >> 3) - a, b: u64 - if src_need_bytes <= 4 { - a = u64(intrinsics.unaligned_load((^u32)(&src[src_byte]))) - } else { - a = intrinsics.unaligned_load((^u64)(&src[src_byte])) - b = intrinsics.unaligned_load((^u64)(&src[src_byte + 8])) - } - bits := (a >> src_shift) | (b << (64 - src_shift)) - mask := ~u64(0) - if size_bits < 64 { - mask = (u64(1) << size_bits) - 1 - } - bits &= mask - dst_need_bytes := ((dst_shift + size_bits + 7) >> 3) - if dst_shift == 0 { - if dst_need_bytes <= 4 { - v := u64(intrinsics.unaligned_load((^u32)(&dst[dst_byte]))) - v = (v & ~mask) | bits - intrinsics.unaligned_store((^u32)(&dst[dst_byte]), u32(v)) - } else { - v := intrinsics.unaligned_load((^u64)(&dst[dst_byte])) - v = (v & ~mask) | bits - intrinsics.unaligned_store((^u64)(&dst[dst_byte]), v) - } - } else { - v0 := intrinsics.unaligned_load((^u64)(&dst[dst_byte])) - v1 := intrinsics.unaligned_load((^u64)(&dst[dst_byte + 8])) - v0 = (v0 & ~(mask << dst_shift)) | (bits << dst_shift) - v1 = (v1 & ~(mask >> (64 - dst_shift))) | (bits >> (64 - dst_shift)) - intrinsics.unaligned_store((^u64)(&dst[dst_byte]), v0) - intrinsics.unaligned_store((^u64)(&dst[dst_byte + 8]), v1) - } -} - when .Address in ODIN_SANITIZER_FLAGS { foreign { From 3834aeec49fb0b17bb975ad9c2ac24546e1e48cf Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 16 Jun 2026 15:26:01 +0100 Subject: [PATCH 20/21] Compiler: Improve error propagation when all of the overloads have the same return values --- src/check_expr.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 53d72f35b..24ee86810 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -7818,6 +7818,30 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, } data.result_type = t_invalid; + if (procs.count > 0) { + Type *first_type = base_type(procs[0]->type); + GB_ASSERT(first_type->kind == Type_Proc); + Type *first_results = first_type->Proc.results; + bool all_the_same = true; + for (isize i = 1; i < procs.count; i++) { + Type *type = base_type(procs[i]->type); + if (type->kind != Type_Proc) { + all_the_same = false; + break; + } + Type *results = type->Proc.results; + if (!are_types_identical(first_results, results)) { + all_the_same = false; + break; + } + } + if (all_the_same) { + GB_ASSERT_MSG(is_type_tuple(first_results), "%s", type_to_string(first_results)); + data.result_type = first_results; + } + } + + } else if (valids.count > 1) { ERROR_BLOCK(); From c843f3bb651cea525069af188940aa84e35ff753 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 19 Jun 2026 11:19:16 +0100 Subject: [PATCH 21/21] Optimize `append_elem` for different optimization levels * For `-o:size` and below, uses the type erased approach * For `-o:speed` and above, the inlined form is used This is necessary because a generic `mem_copy_non_overlapping` cannot be optimized when type erasure is used, meaning in a hot path where `append_elem` is used a lot; thus `mem_copy_non_overlapping` becomes a bottleneck. --- base/runtime/core_builtin.odin | 61 ++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index e2e4e693a..cdf0fbcab 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -715,10 +715,10 @@ _append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, a if array.cap < array.len+1 { // Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY. - cap := 2 * array.cap + DEFAULT_DYNAMIC_ARRAY_CAPACITY + cap := max(2 * array.cap, DEFAULT_DYNAMIC_ARRAY_CAPACITY) // do not 'or_return' here as it could be a partial success - err = _reserve_dynamic_array(array, size_of_elem, align_of_elem, cap, should_zero, loc) + err = _reserve_dynamic_array_unsafe(array, size_of_elem, align_of_elem, cap, should_zero, loc) } if array.cap-array.len > 0 { data := ([^]byte)(array.data) @@ -735,11 +735,37 @@ _append_elem :: #force_no_inline proc(array: ^Raw_Dynamic_Array, size_of_elem, a @builtin append_elem :: proc(array: ^$T/[dynamic]$E, #no_broadcast arg: E, loc := #caller_location) -> (num_appended: int, err: Allocator_Error) #optional_allocator_error { when size_of(E) == 0 { + if array == nil { + return + } (^Raw_Dynamic_Array)(array).len += 1 return 1, nil - } else { + } else when ODIN_OPTIMIZATION_MODE <= .Size { arg := arg return _append_elem((^Raw_Dynamic_Array)(array), size_of(E), align_of(E), &arg, true, loc=loc) + } else { + if array == nil { + return + } + arg := arg + arr := (^Raw_Dynamic_Array)(array) + if arr.cap < arr.len+1 { + // Same behavior as _append_elems but there's only one arg, so we always just add DEFAULT_DYNAMIC_ARRAY_CAPACITY. + cap := max(2 * arr.cap, DEFAULT_DYNAMIC_ARRAY_CAPACITY) + + // do not 'or_return' here as it could be a partial success + err = _reserve_dynamic_array_unsafe(arr, size_of(E), align_of(E), cap, true, loc) + } + if arr.cap-arr.len > 0 { + // NOTE(bill, 2026-06-19): When this is in the hot path with -o:speed or -o:aggressive enabled, + // this code path cannot rely on type erasure and `mem_copy_non_overlapping`. + // So directly inlining the call and storing the argument like this helps the optimize a lot + assert(arr.data != nil, loc=loc) + ([^]E)(arr.data)[arr.len] = arg + arr.len += 1 + num_appended = 1 + } + return } } @@ -1311,6 +1337,35 @@ _reserve_dynamic_array :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_e return nil } +_reserve_dynamic_array_unsafe :: #force_no_inline proc(a: ^Raw_Dynamic_Array, size_of_elem, align_of_elem: int, capacity: int, should_zero: bool, loc := #caller_location) -> Allocator_Error { + if capacity <= a.cap { + return nil + } + + if a.allocator.procedure == nil { + a.allocator = context.allocator + assert(a.allocator.procedure != nil) + } + + old_size := a.cap * size_of_elem + new_size := capacity * size_of_elem + allocator := a.allocator + + new_data: []byte + if should_zero { + new_data = mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return + } else { + new_data = non_zero_mem_resize(a.data, old_size, new_size, align_of_elem, allocator, loc) or_return + } + if new_data == nil && new_size > 0 { + return .Out_Of_Memory + } + + a.data = raw_data(new_data) + a.cap = capacity + return nil +} + // `reserve_dynamic_array` will try to reserve memory of a passed dynamic array or map to the requested element count (setting the `cap`). // // When a memory resize allocation is required, the memory will be asked to be zeroed (i.e. it calls `mem_resize`).