From f1dbe9c242b44e02d8420b06486a7c061181efe9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 11 Mar 2026 16:46:33 +0000 Subject: [PATCH 01/27] `[dynamic; N]T` proof of concept: fixed capacity dynamic array (akin to `small_array.Small_Array(N, T)`) --- base/runtime/core.odin | 14 ++++ base/runtime/core_builtin.odin | 80 ++++++++++++++++++ base/runtime/print.odin | 12 +++ core/fmt/fmt.odin | 15 ++++ core/odin/ast/ast.odin | 12 +++ core/odin/ast/clone.odin | 6 ++ core/odin/ast/walk.odin | 6 ++ core/odin/parser/parser.odin | 16 ++++ core/reflect/reflect.odin | 128 +++++++++++++++++------------ core/reflect/types.odin | 11 +++ src/check_builtin.cpp | 11 +++ src/check_expr.cpp | 32 ++++++++ src/check_type.cpp | 25 ++++++ src/checker.cpp | 11 +++ src/llvm_backend.hpp | 1 + src/llvm_backend_debug.cpp | 64 +++++++++++++++ src/llvm_backend_expr.cpp | 44 ++++++++++ src/llvm_backend_general.cpp | 20 +++++ src/llvm_backend_proc.cpp | 4 + src/llvm_backend_type.cpp | 16 ++++ src/llvm_backend_utility.cpp | 10 +++ src/name_canonicalization.cpp | 4 + src/parser.cpp | 41 +++++++++- src/parser.hpp | 10 ++- src/types.cpp | 145 ++++++++++++++++++++++++++++++++- 25 files changed, 677 insertions(+), 61 deletions(-) diff --git a/base/runtime/core.odin b/base/runtime/core.odin index 983f104e3..52993286a 100644 --- a/base/runtime/core.odin +++ b/base/runtime/core.odin @@ -206,6 +206,14 @@ Type_Info_Bit_Field :: struct { field_count: int, } +Type_Info_Fixed_Capacity_Dynamic_Array :: struct { + elem: ^Type_Info, + elem_size: int, + capacity: int, + len_offset: uintptr, +} + + Type_Info_Flag :: enum u8 { Comparable = 0, Simple_Compare = 1, @@ -246,6 +254,7 @@ Type_Info :: struct { Type_Info_Matrix, Type_Info_Soa_Pointer, Type_Info_Bit_Field, + Type_Info_Fixed_Capacity_Dynamic_Array, }, } @@ -425,6 +434,11 @@ Raw_Dynamic_Array :: struct { allocator: Allocator, } +Raw_Fixed_Capacity_Dynamic_Array :: struct($Capacity: uint, $T: typeid) { + data: [Capacity]T, + len: int, +} + // The raw, type-erased representation of a map. // // 32-bytes on 64-bit diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 974b2f048..7a1a38f9a 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -254,6 +254,7 @@ non_zero_reserve :: proc{ @builtin resize :: proc{ resize_dynamic_array, + resize_fixed_capacity_dynamic_array, resize_soa, } @@ -261,6 +262,7 @@ resize :: proc{ @builtin non_zero_resize :: proc{ non_zero_resize_dynamic_array, + non_zero_resize_fixed_capacity_dynamic_array, non_zero_resize_soa, } @@ -686,6 +688,43 @@ append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_ return } + +// `append_fixed_capacity_elem` appends an element to the end of a fixed capacity dynamic array. Returns 0 on failure +@builtin +append_fixed_capacity_elem :: proc(array: ^$T/[dynamic; $N]$E, #no_broadcast arg: E) -> (n: int) { + Raw :: Raw_Fixed_Capacity_Dynamic_Array(N, E) + + if (^Raw)(array).len >= N { + return 0 + } + + when size_of(E) != 0 { + #no_bounds_check (^Raw)(array).data[(^Raw)(array).len] = arg + } + (^Raw)(array).len += 1 + return 1 +} + + +// `append_fixed_capacity_elem` appends an element to the end of a fixed capacity dynamic array. Returns 0 on failure +@builtin +append_fixed_capacity_elems :: proc(array: ^$T/[dynamic; $N]$E, #no_broadcast args: ..E) -> (n: int) { + Raw :: Raw_Fixed_Capacity_Dynamic_Array(N, E) + raw := (^Raw)(array) + + n = min(N - len(args), len(args)) + + when size_of(E) != 0 { + for i in 0.. bool { + if array == nil { + return false + } + raw := (^Raw_Fixed_Capacity_Dynamic_Array(N, E))(array) + if raw.len < length { + size_of_elem :: size_of(E) + + num_reused := min(N, length) - a.len + intrinsics.mem_zero(([^]byte)(a.data)[a.len*size_of_elem:], num_reused*size_of_elem) + } + new_length := clamp(length, 0, N) + raw.len = new_length + return true +} + +// `non_zero_resize_fixed_capacity_dynamic_array` will try to resize memory of a passed fixed capacity dynamic array or map to the requested element count (setting the `len`, and possibly `cap`). +// +// Note: Prefer the procedure group `resize` +@builtin +non_zero_resize_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, #any_int length: int) -> bool { + if array == nil { + return false + } + raw := (^Raw_Fixed_Capacity_Dynamic_Array(N, E))(array) + new_length := clamp(length, 0, N) + raw.len = new_length + return true +} + // Shrinks the capacity of a dynamic array down to the current length, or the given capacity. // // If `new_cap` is negative, then `len(array)` is used. diff --git a/base/runtime/print.odin b/base/runtime/print.odin index 6569ece6c..ad205d887 100644 --- a/base/runtime/print.odin +++ b/base/runtime/print.odin @@ -392,6 +392,12 @@ print_type :: #force_no_inline proc "contextless" (ti: ^Type_Info) { print_string("[]") print_type(info.elem) + case Type_Info_Fixed_Capacity_Dynamic_Array: + print_string("[dynamic; ") + print_u64(u64(info.capacity)) + print_string("]") + print_type(info.elem) + case Type_Info_Map: print_string("map[") print_type(info.key) @@ -807,6 +813,12 @@ write_write_type :: #force_no_inline proc "contextless" (i: ^int, buf: []byte, t write_string (i, buf, "[]") or_return write_write_type(i, buf, info.elem) or_return + case Type_Info_Fixed_Capacity_Dynamic_Array: + write_string (i, buf, "[dynamic; ") or_return + write_u64 (i, buf, u64(info.capacity)) or_return + write_string (i, buf, "]") or_return + write_write_type(i, buf, info.elem) or_return + case Type_Info_Map: write_string (i, buf, "map[") or_return write_write_type(i, buf, info.key) or_return diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index f1601f278..4ecb19c21 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -3246,6 +3246,21 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) { } fmt_array(fi, ptr, n, info.elem_size, info.elem, verb) + + case runtime.Type_Info_Fixed_Capacity_Dynamic_Array: + n := (^int)(uintptr(v.data) + info.len_offset)^ + + ptr := v.data // data is stored at the start + if ol, ok := fi.optional_len.?; ok { + fi.optional_len = nil + n = min(n, ol) + } else if fi.use_nul_termination { + fi.use_nul_termination = false + fmt_array_nul_terminated(fi, ptr, n, info.elem_size, info.elem, verb) + return + } + fmt_array(fi, ptr, n, info.elem_size, info.elem, verb) + case runtime.Type_Info_Simd_Vector: io.write_byte(fi.writer, '<', &fi.n) defer io.write_byte(fi.writer, '>', &fi.n) diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index 2cee6e385..8f5f466cb 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -786,6 +786,16 @@ Dynamic_Array_Type :: struct { elem: ^Expr, } +Fixed_Capacity_Dynamic_Array_Type :: struct { + using node: Expr, + tag: ^Expr, // possibly nil + open: tokenizer.Pos, + dynamic_pos: tokenizer.Pos, + capacity: ^Expr, + close: tokenizer.Pos, + elem: ^Expr, +} + Struct_Type :: struct { using node: Expr, tok_pos: tokenizer.Pos, @@ -931,6 +941,7 @@ Any_Node :: union { ^Multi_Pointer_Type, ^Array_Type, ^Dynamic_Array_Type, + ^Fixed_Capacity_Dynamic_Array_Type, ^Struct_Type, ^Union_Type, ^Enum_Type, @@ -1017,6 +1028,7 @@ Any_Expr :: union { ^Multi_Pointer_Type, ^Array_Type, ^Dynamic_Array_Type, + ^Fixed_Capacity_Dynamic_Array_Type, ^Struct_Type, ^Union_Type, ^Enum_Type, diff --git a/core/odin/ast/clone.odin b/core/odin/ast/clone.odin index df3e1df0d..163485840 100644 --- a/core/odin/ast/clone.odin +++ b/core/odin/ast/clone.odin @@ -311,10 +311,16 @@ clone_node :: proc(node: ^Node) -> ^Node { case ^Multi_Pointer_Type: r.elem = clone(r.elem) case ^Array_Type: + r.tag = clone(r.tag) r.len = clone(r.len) r.elem = clone(r.elem) case ^Dynamic_Array_Type: + r.tag = clone(r.tag) r.elem = clone(r.elem) + case ^Fixed_Capacity_Dynamic_Array_Type: + r.tag = clone(r.tag) + r.capacity = clone(r.capacity) + r.elem = clone(r.elem) case ^Struct_Type: r.poly_params = auto_cast clone(r.poly_params) r.align = clone(r.align) diff --git a/core/odin/ast/walk.odin b/core/odin/ast/walk.odin index 5b9340c62..d3824c213 100644 --- a/core/odin/ast/walk.odin +++ b/core/odin/ast/walk.odin @@ -380,6 +380,12 @@ walk :: proc(v: ^Visitor, node: ^Node) { walk(v, n.tag) } walk(v, n.elem) + case ^Fixed_Capacity_Dynamic_Array_Type: + if n.tag != nil { + walk(v, n.tag) + } + walk(v, n.capacity) + walk(v, n.elem) case ^Struct_Type: if n.poly_params != nil { walk(v, n.poly_params) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 643673c69..0f3ac78b2 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -2389,6 +2389,7 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { case ^ast.Array_Type: t.tag = bd case ^ast.Dynamic_Array_Type: t.tag = bd case ^ast.Pointer_Type: t.tag = bd + case ^ast.Fixed_Capacity_Dynamic_Array_Type: t.tag = bd case: error(p, original_type.pos, "expected an array or pointer type after #%s", name.text) } @@ -2626,6 +2627,20 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr { return t case .Dynamic: tok := expect_token(p, .Dynamic) + if allow_token(p, .Semicolon) { + capacity := parse_expr(p, false) + close := expect_token(p, .Close_Bracket) + elem := parse_type(p) + + da := ast.new(ast.Fixed_Capacity_Dynamic_Array_Type, open.pos, elem) + da.open = open.pos + da.dynamic_pos = tok.pos + da.capacity = capacity + da.close = close.pos + da.elem = elem + return da + } + close := expect_token(p, .Close_Bracket) elem := parse_type(p) da := ast.new(ast.Dynamic_Array_Type, open.pos, elem) @@ -3107,6 +3122,7 @@ is_literal_type :: proc(expr: ^ast.Expr) -> bool { ^ast.Union_Type, ^ast.Enum_Type, ^ast.Dynamic_Array_Type, + ^ast.Fixed_Capacity_Dynamic_Array_Type, ^ast.Map_Type, ^ast.Bit_Set_Type, ^ast.Matrix_Type, diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index b39c6ac6b..924120464 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -6,33 +6,34 @@ _ :: intrinsics Type_Info :: runtime.Type_Info -Type_Info_Named :: runtime.Type_Info_Named -Type_Info_Integer :: runtime.Type_Info_Integer -Type_Info_Rune :: runtime.Type_Info_Rune -Type_Info_Float :: runtime.Type_Info_Float -Type_Info_Complex :: runtime.Type_Info_Complex -Type_Info_Quaternion :: runtime.Type_Info_Quaternion -Type_Info_String :: runtime.Type_Info_String -Type_Info_Boolean :: runtime.Type_Info_Boolean -Type_Info_Any :: runtime.Type_Info_Any -Type_Info_Type_Id :: runtime.Type_Info_Type_Id -Type_Info_Pointer :: runtime.Type_Info_Pointer -Type_Info_Multi_Pointer :: runtime.Type_Info_Multi_Pointer -Type_Info_Procedure :: runtime.Type_Info_Procedure -Type_Info_Array :: runtime.Type_Info_Array -Type_Info_Enumerated_Array :: runtime.Type_Info_Enumerated_Array -Type_Info_Dynamic_Array :: runtime.Type_Info_Dynamic_Array -Type_Info_Slice :: runtime.Type_Info_Slice -Type_Info_Parameters :: runtime.Type_Info_Parameters -Type_Info_Struct :: runtime.Type_Info_Struct -Type_Info_Union :: runtime.Type_Info_Union -Type_Info_Enum :: runtime.Type_Info_Enum -Type_Info_Map :: runtime.Type_Info_Map -Type_Info_Bit_Set :: runtime.Type_Info_Bit_Set -Type_Info_Simd_Vector :: runtime.Type_Info_Simd_Vector -Type_Info_Matrix :: runtime.Type_Info_Matrix -Type_Info_Soa_Pointer :: runtime.Type_Info_Soa_Pointer -Type_Info_Bit_Field :: runtime.Type_Info_Bit_Field +Type_Info_Named :: runtime.Type_Info_Named +Type_Info_Integer :: runtime.Type_Info_Integer +Type_Info_Rune :: runtime.Type_Info_Rune +Type_Info_Float :: runtime.Type_Info_Float +Type_Info_Complex :: runtime.Type_Info_Complex +Type_Info_Quaternion :: runtime.Type_Info_Quaternion +Type_Info_String :: runtime.Type_Info_String +Type_Info_Boolean :: runtime.Type_Info_Boolean +Type_Info_Any :: runtime.Type_Info_Any +Type_Info_Type_Id :: runtime.Type_Info_Type_Id +Type_Info_Pointer :: runtime.Type_Info_Pointer +Type_Info_Multi_Pointer :: runtime.Type_Info_Multi_Pointer +Type_Info_Procedure :: runtime.Type_Info_Procedure +Type_Info_Array :: runtime.Type_Info_Array +Type_Info_Enumerated_Array :: runtime.Type_Info_Enumerated_Array +Type_Info_Dynamic_Array :: runtime.Type_Info_Dynamic_Array +Type_Info_Slice :: runtime.Type_Info_Slice +Type_Info_Parameters :: runtime.Type_Info_Parameters +Type_Info_Struct :: runtime.Type_Info_Struct +Type_Info_Union :: runtime.Type_Info_Union +Type_Info_Enum :: runtime.Type_Info_Enum +Type_Info_Map :: runtime.Type_Info_Map +Type_Info_Bit_Set :: runtime.Type_Info_Bit_Set +Type_Info_Simd_Vector :: runtime.Type_Info_Simd_Vector +Type_Info_Matrix :: runtime.Type_Info_Matrix +Type_Info_Soa_Pointer :: runtime.Type_Info_Soa_Pointer +Type_Info_Bit_Field :: runtime.Type_Info_Bit_Field +Type_Info_Fixed_Capacity_Dynamic_Array :: runtime.Type_Info_Fixed_Capacity_Dynamic_Array Type_Info_Enum_Value :: runtime.Type_Info_Enum_Value @@ -67,6 +68,7 @@ Type_Kind :: enum { Matrix, Soa_Pointer, Bit_Field, + Fixed_Capacity_Dynamic_Array, } @@ -76,33 +78,34 @@ type_kind :: proc(T: typeid) -> Type_Kind { ti := type_info_of(T) if ti != nil { switch _ in ti.variant { - case Type_Info_Named: return .Named - case Type_Info_Integer: return .Integer - case Type_Info_Rune: return .Rune - case Type_Info_Float: return .Float - case Type_Info_Complex: return .Complex - case Type_Info_Quaternion: return .Quaternion - case Type_Info_String: return .String - case Type_Info_Boolean: return .Boolean - case Type_Info_Any: return .Any - case Type_Info_Type_Id: return .Type_Id - case Type_Info_Pointer: return .Pointer - case Type_Info_Multi_Pointer: return .Multi_Pointer - case Type_Info_Procedure: return .Procedure - case Type_Info_Array: return .Array - case Type_Info_Enumerated_Array: return .Enumerated_Array - case Type_Info_Dynamic_Array: return .Dynamic_Array - case Type_Info_Slice: return .Slice - case Type_Info_Parameters: return .Parameters - case Type_Info_Struct: return .Struct - case Type_Info_Union: return .Union - case Type_Info_Enum: return .Enum - case Type_Info_Map: return .Map - case Type_Info_Bit_Set: return .Bit_Set - case Type_Info_Simd_Vector: return .Simd_Vector - case Type_Info_Matrix: return .Matrix - case Type_Info_Soa_Pointer: return .Soa_Pointer - case Type_Info_Bit_Field: return .Bit_Field + case Type_Info_Named: return .Named + case Type_Info_Integer: return .Integer + case Type_Info_Rune: return .Rune + case Type_Info_Float: return .Float + case Type_Info_Complex: return .Complex + case Type_Info_Quaternion: return .Quaternion + case Type_Info_String: return .String + case Type_Info_Boolean: return .Boolean + case Type_Info_Any: return .Any + case Type_Info_Type_Id: return .Type_Id + case Type_Info_Pointer: return .Pointer + case Type_Info_Multi_Pointer: return .Multi_Pointer + case Type_Info_Procedure: return .Procedure + case Type_Info_Array: return .Array + case Type_Info_Enumerated_Array: return .Enumerated_Array + case Type_Info_Dynamic_Array: return .Dynamic_Array + case Type_Info_Slice: return .Slice + case Type_Info_Parameters: return .Parameters + case Type_Info_Struct: return .Struct + case Type_Info_Union: return .Union + case Type_Info_Enum: return .Enum + case Type_Info_Map: return .Map + case Type_Info_Bit_Set: return .Bit_Set + case Type_Info_Simd_Vector: return .Simd_Vector + case Type_Info_Matrix: return .Matrix + case Type_Info_Soa_Pointer: return .Soa_Pointer + case Type_Info_Bit_Field: return .Bit_Field + case Type_Info_Fixed_Capacity_Dynamic_Array: return .Fixed_Capacity_Dynamic_Array } } @@ -1919,6 +1922,23 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_ } } return true + case Type_Info_Fixed_Capacity_Dynamic_Array: + a_count := (^int)(uintptr(a.data) + v.len_offset)^ + b_count := (^int)(uintptr(b.data) + v.len_offset)^ + if a_count != b_count { + return false + } + + for i in 0.. bool { y := b.variant.(Type_Info_Slice) or_return return are_types_identical(x.elem, y.elem) + case Type_Info_Fixed_Capacity_Dynamic_Array: + y := b.variant.(Type_Info_Fixed_Capacity_Dynamic_Array) or_return + if x.capacity != y.capacity { return false } + return are_types_identical(x.elem, y.elem) + case Type_Info_Parameters: y := b.variant.(Type_Info_Parameters) or_return if len(x.types) != len(y.types) { return false } @@ -661,6 +666,12 @@ write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_writt io.write_string(w, "[]", &n) or_return write_type(w, info.elem, &n) or_return + case Type_Info_Fixed_Capacity_Dynamic_Array: + io.write_string(w, "[dynamic;", &n) or_return + io.write_i64(w, i64(info.capacity), 10, &n) or_return + io.write_string(w, "]", &n) or_return + write_type(w, info.elem, &n) or_return + case Type_Info_Map: io.write_string(w, "map[", &n) or_return write_type(w, info.key, &n) or_return diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 1a094c1f0..02826a209 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -2634,6 +2634,16 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As mode = Addressing_Constant; value = exact_value_i64(at->Array.count); type = t_untyped_integer; + } else if (is_type_fixed_capacity_dynamic_array(op_type)) { + Type *at = core_type(op_type); + if (id == BuiltinProc_cap) { + mode = Addressing_Constant; + value = exact_value_i64(at->FixedCapacityDynamicArray.capacity); + type = t_untyped_integer; + } else { + GB_ASSERT(id == BuiltinProc_len); + mode = Addressing_Value; + } } else if (is_type_enumerated_array(op_type) && id == BuiltinProc_len) { Type *at = core_type(op_type); mode = Addressing_Constant; @@ -5167,6 +5177,7 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As case Type_Array: case Type_EnumeratedArray: case Type_SimdVector: + case Type_FixedCapacityDynamicArray: operand->type = alloc_type_multi_pointer(base_array_type(base)); break; case Type_Matrix: diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 80df35edc..9f66f8fec 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1490,6 +1490,22 @@ gb_internal bool is_polymorphic_type_assignable(CheckerContext *c, Type *poly, T return is_polymorphic_type_assignable(c, poly->DynamicArray.elem, source->DynamicArray.elem, true, modify_type); } return false; + + case Type_FixedCapacityDynamicArray: + if (source->kind == Type_FixedCapacityDynamicArray) { + if (poly->FixedCapacityDynamicArray.generic_capacity != nullptr) { + if (!polymorphic_assign_index(&poly->FixedCapacityDynamicArray.generic_capacity, + &poly->FixedCapacityDynamicArray.capacity, + source->FixedCapacityDynamicArray.capacity)) { + return false; + } + } + if (poly->FixedCapacityDynamicArray.capacity == source->FixedCapacityDynamicArray.capacity) { + return is_polymorphic_type_assignable(c, poly->FixedCapacityDynamicArray.elem, source->FixedCapacityDynamicArray.elem, true, modify_type); + } + } + return false; + case Type_Slice: if (source->kind == Type_Slice) { return is_polymorphic_type_assignable(c, poly->Slice.elem, source->Slice.elem, true, modify_type); @@ -8761,6 +8777,14 @@ gb_internal bool check_set_index_data(Operand *o, Type *t, bool indirection, i64 o->mode = Addressing_Variable; } return true; + + case Type_FixedCapacityDynamicArray: + o->type = t->FixedCapacityDynamicArray.elem; + if (o->mode != Addressing_Constant) { + o->mode = Addressing_Variable; + } + return true; + case Type_Struct: if (t->Struct.soa_kind != StructSoa_None) { if (t->Struct.soa_kind == StructSoa_Fixed) { @@ -11410,6 +11434,8 @@ gb_internal ExprKind check_index_expr(CheckerContext *c, Operand *o, Ast *node, // Okay } else if (is_type_enumerated_array(t)) { // Okay + } else if (is_type_fixed_capacity_dynamic_array(t)) { + // Okay } else if (is_type_string(t)) { // Okay } else if (is_type_matrix(t)) { @@ -11556,6 +11582,11 @@ gb_internal ExprKind check_slice_expr(CheckerContext *c, Operand *o, Ast *node, o->type = alloc_type_slice(t->DynamicArray.elem); break; + case Type_FixedCapacityDynamicArray: + valid = true; + o->type = alloc_type_slice(t->FixedCapacityDynamicArray.elem); + break; + case Type_Struct: if (is_type_soa_struct(t)) { valid = true; @@ -12110,6 +12141,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast case Ast_MultiPointerType: case Ast_ArrayType: case Ast_DynamicArrayType: + case Ast_FixedCapacityDynamicArrayType: case Ast_StructType: case Ast_UnionType: case Ast_EnumType: diff --git a/src/check_type.cpp b/src/check_type.cpp index 82e70dd33..5cfc24981 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -3642,6 +3642,31 @@ gb_internal bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, T return true; case_end; + case_ast_node(dat, FixedCapacityDynamicArrayType, e); + Operand o = {}; + i64 capacity = check_array_count(ctx, &o, dat->capacity); + Type *generic_type = nullptr; + if (o.mode == Addressing_Type && o.type->kind == Type_Generic) { + generic_type = o.type; + } + + if (capacity < 0) { + error(dat->capacity, "? can only be used in conjunction with compound literals of fixed-length arrays"); + capacity = 0; + } + + + Type *elem = check_type(ctx, dat->elem); + if (dat->tag != nullptr) { + GB_ASSERT(dat->tag->kind == Ast_BasicDirective); + String name = dat->tag->BasicDirective.name.string; + error(dat->tag, "Invalid tag applied to fixed capacity dynamic array, got #%.*s", LIT(name)); + } + *type = alloc_type_fixed_capacity_dynamic_array(elem, capacity, generic_type); + set_base_type(named_type, *type); + return true; + case_end; + case_ast_node(st, StructType, e); CheckerContext c = *ctx; c.in_polymorphic_specialization = false; diff --git a/src/checker.cpp b/src/checker.cpp index 8acc5f4ae..2ac5aa62d 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2276,6 +2276,11 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { add_type_info_type_internal(c, t_int); break; + case Type_FixedCapacityDynamicArray: + add_type_info_type_internal(c, bt->FixedCapacityDynamicArray.elem); + add_type_info_type_internal(c, t_allocator); + break; + case Type_Enum: add_type_info_type_internal(c, bt->Enum.base_type); break; @@ -2514,6 +2519,10 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { add_min_dep_type_info(c, t_int); break; + case Type_FixedCapacityDynamicArray: + add_min_dep_type_info(c, bt->FixedCapacityDynamicArray.elem); + add_min_dep_type_info(c, t_int); + case Type_Enum: add_min_dep_type_info(c, bt->Enum.base_type); break; @@ -3350,6 +3359,7 @@ gb_internal void init_core_type_info(Checker *c) { t_type_info_matrix = find_core_type(c, str_lit("Type_Info_Matrix")); t_type_info_soa_pointer = find_core_type(c, str_lit("Type_Info_Soa_Pointer")); t_type_info_bit_field = find_core_type(c, str_lit("Type_Info_Bit_Field")); + t_type_info_fixed_capacity_dynamic_array = find_core_type(c, str_lit("Type_Info_Fixed_Capacity_Dynamic_Array")); t_type_info_named_ptr = alloc_type_pointer(t_type_info_named); t_type_info_integer_ptr = alloc_type_pointer(t_type_info_integer); @@ -3378,6 +3388,7 @@ gb_internal void init_core_type_info(Checker *c) { t_type_info_matrix_ptr = alloc_type_pointer(t_type_info_matrix); t_type_info_soa_pointer_ptr = alloc_type_pointer(t_type_info_soa_pointer); t_type_info_bit_field_ptr = alloc_type_pointer(t_type_info_bit_field); + t_type_info_fixed_capacity_dynamic_array_ptr = alloc_type_pointer(t_type_info_fixed_capacity_dynamic_array); } gb_internal void init_mem_allocator(Checker *c) { diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 0f199907d..48c5be546 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -533,6 +533,7 @@ gb_internal lbValue lb_dynamic_array_elem(lbProcedure *p, lbValue da); gb_internal lbValue lb_dynamic_array_len(lbProcedure *p, lbValue da); gb_internal lbValue lb_dynamic_array_cap(lbProcedure *p, lbValue da); gb_internal lbValue lb_dynamic_array_allocator(lbProcedure *p, lbValue da); +gb_internal lbValue lb_fixed_capacity_dynamic_array_len(lbProcedure *p, lbValue da); gb_internal lbValue lb_map_len(lbProcedure *p, lbValue value); gb_internal lbValue lb_map_cap(lbProcedure *p, lbValue value); gb_internal lbValue lb_soa_struct_len(lbProcedure *p, lbValue value); diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index e9b0f72cb..4b09d3d5e 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -409,6 +409,69 @@ gb_internal LLVMMetadataRef lb_debug_dynamic_array(lbModule *m, Type *type, Stri return final_decl; } +gb_internal LLVMMetadataRef lb_debug_fixed_capacity_dynamic_array(lbModule *m, Type *type, String name, LLVMMetadataRef scope, LLVMMetadataRef file, unsigned line) { + Type *bt = base_type(type); + GB_ASSERT(bt->kind == Type_FixedCapacityDynamicArray); + + unsigned const int_bits = cast(unsigned)(8*build_context.int_size); + + u64 size_in_bits = 8*type_size_of(bt); + u32 align_in_bits = 8*cast(u32)type_align_of(bt); + + LLVMMetadataRef temp_forward_decl = LLVMDIBuilderCreateReplaceableCompositeType( + m->debug_builder, DW_TAG_structure_type, + cast(char const *)name.text, cast(size_t)name.len, + scope, file, line, 0, size_in_bits, align_in_bits, LLVMDIFlagZero, "", 0 + ); + + lb_set_llvm_metadata(m, type, temp_forward_decl); + + unsigned element_count = 2; + LLVMMetadataRef elements[2]; + + // LLVMMetadataRef member_scope = lb_get_llvm_metadata(m, bt->DynamicArray.scope); + LLVMMetadataRef member_scope = nullptr; + + Type *elem_type = alloc_type_array(bt->FixedCapacityDynamicArray.elem, bt->FixedCapacityDynamicArray.capacity); + elements[0] = LLVMDIBuilderCreateMemberType( + m->debug_builder, member_scope, + "data", 4, + file, line, + 8*cast(u64)type_size_of(elem_type), 8*cast(u32)type_align_of(elem_type), + 0, + LLVMDIFlagZero, lb_debug_type(m, elem_type) + ); + + i64 len_offset = type_offset_of(bt, 1); + + elements[1] = LLVMDIBuilderCreateMemberType( + m->debug_builder, member_scope, + "len", 3, + file, line, + int_bits, int_bits, + len_offset, + LLVMDIFlagZero, lb_debug_type(m, t_int) + ); + + LLVMMetadataRef final_decl = LLVMDIBuilderCreateStructType( + m->debug_builder, scope, + cast(char const *)name.text, cast(size_t)name.len, + file, line, + size_in_bits, align_in_bits, + LLVMDIFlagZero, + nullptr, + elements, element_count, + 0, + nullptr, + "", 0 + ); + + LLVMMetadataReplaceAllUsesWith(temp_forward_decl, final_decl); + lb_set_llvm_metadata(m, type, final_decl); + return final_decl; +} + + gb_internal LLVMMetadataRef lb_debug_union(lbModule *m, Type *type, String name, LLVMMetadataRef scope, LLVMMetadataRef file, unsigned line) { Type *bt = base_type(type); GB_ASSERT(bt->kind == Type_Union); @@ -899,6 +962,7 @@ gb_internal LLVMMetadataRef lb_debug_type_internal(lbModule *m, Type *type) { case Type_BitSet: return lb_debug_bitset( m, type, type_to_canonical_string(temporary_allocator(), type), nullptr, nullptr, 0); case Type_Enum: return lb_debug_enum( m, type, type_to_canonical_string(temporary_allocator(), type), nullptr, nullptr, 0); case Type_BitField: return lb_debug_bitfield( m, type, type_to_canonical_string(temporary_allocator(), type), nullptr, nullptr, 0); + case Type_FixedCapacityDynamicArray: return lb_debug_fixed_capacity_dynamic_array(m, type, type_to_canonical_string(temporary_allocator(), type), nullptr, nullptr, 0); case Type_Tuple: if (type->Tuple.variables.count == 1) { diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index 1685f9627..e352a33e9 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4700,6 +4700,25 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { return lb_addr(elem); } + case Type_FixedCapacityDynamicArray: { + lbValue array = {}; + array = lb_build_addr_ptr(p, ie->expr); + if (deref) { + array = lb_emit_load(p, array); + } + lbValue index = lb_build_expr(p, ie->index); + index = lb_emit_conv(p, index, t_int); + + lbValue array_ptr = lb_emit_struct_ep(p, array, 0); + lbValue elem = lb_emit_array_ep(p, array_ptr, index); + + auto index_tv = type_and_value_of_expr(ie->index); + lbValue len = lb_emit_struct_ep(p, array, 1); + len = lb_emit_load(p, len); + lb_emit_bounds_check(p, ast_token(ie->index), index, len); + return lb_addr(elem); + } + case Type_EnumeratedArray: { lbValue array = {}; array = lb_build_addr_ptr(p, ie->expr); @@ -4943,6 +4962,31 @@ gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) { return slice; } + case Type_FixedCapacityDynamicArray: { + Type *elem_type = type->FixedCapacityDynamicArray.elem; + Type *slice_type = alloc_type_slice(elem_type); + + lbValue len = lb_fixed_capacity_dynamic_array_len(p, base); + if (high.value == nullptr) high = len; + + bool low_const = type_and_value_of_expr(se->low).mode == Addressing_Constant; + bool high_const = type_and_value_of_expr(se->high).mode == Addressing_Constant; + + if (!low_const || !high_const) { + if (!no_indices) { + lb_emit_slice_bounds_check(p, se->open, low, high, len, se->low != nullptr); + } + } + lbValue data_ptr = lb_addr_get_ptr(p, addr); + lbValue array_ptr = lb_emit_struct_ep(p, data_ptr, 0); + lbValue elem = lb_emit_ptr_offset(p, lb_array_elem(p, array_ptr), low); + lbValue new_len = lb_emit_arith(p, Token_Sub, high, low, t_int); + + lbAddr slice = lb_add_local_generated(p, slice_type, false); + lb_fill_slice(p, slice, elem, new_len); + return slice; + } + case Type_Basic: { if (is_type_string16(type)) { GB_ASSERT_MSG(are_types_identical(type, t_string16), "got %s", type_to_string(type)); diff --git a/src/llvm_backend_general.cpp b/src/llvm_backend_general.cpp index 572e3990c..32a669235 100644 --- a/src/llvm_backend_general.cpp +++ b/src/llvm_backend_general.cpp @@ -2234,6 +2234,26 @@ gb_internal LLVMTypeRef lb_type_internal(lbModule *m, Type *type) { } break; + case Type_FixedCapacityDynamicArray: + { + unsigned field_count = 0; + + LLVMTypeRef fields[3] = {}; + m->internal_type_level += 1; + fields[field_count++] = llvm_array_type(lb_type(m, type->FixedCapacityDynamicArray.elem), type->FixedCapacityDynamicArray.capacity); + m->internal_type_level -= 1; + + gb_unused(type_size_of(type)); + if (type->FixedCapacityDynamicArray.padding_needed > 0) { + fields[field_count++] = lb_type_padding_filler(m, type->FixedCapacityDynamicArray.padding_needed, 1); // padding + } + + fields[field_count++] = lb_type(m, t_int); // len + + return LLVMStructTypeInContext(ctx, fields, field_count, false); + } + break; + case Type_Map: init_map_internal_debug_types(type); GB_ASSERT(t_raw_map != nullptr); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index f72726af1..840bbfb19 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -2323,6 +2323,8 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu return lb_slice_len(p, v); } else if (is_type_dynamic_array(t)) { return lb_dynamic_array_len(p, v); + } else if (is_type_fixed_capacity_dynamic_array(t)) { + return lb_fixed_capacity_dynamic_array_len(p, v); } else if (is_type_map(t)) { return lb_map_len(p, v); } else if (is_type_soa_struct(t)) { @@ -2344,6 +2346,8 @@ gb_internal lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValu GB_PANIC("Unreachable"); } else if (is_type_array(t)) { GB_PANIC("Array lengths are constant"); + } else if (is_type_fixed_capacity_dynamic_array(t)) { + GB_PANIC("Fixed capacity dynamic array capacities are constant"); } else if (is_type_slice(t)) { return lb_slice_len(p, v); } else if (is_type_dynamic_array(t)) { diff --git a/src/llvm_backend_type.cpp b/src/llvm_backend_type.cpp index 89c671f7d..82156d32a 100644 --- a/src/llvm_backend_type.cpp +++ b/src/llvm_backend_type.cpp @@ -69,6 +69,7 @@ gb_internal u64 lb_typeid_kind(lbModule *m, Type *type, u64 id=0) { case Type_SimdVector: kind = Typeid_Simd_Vector; break; case Type_SoaPointer: kind = Typeid_SoaPointer; break; case Type_BitField: kind = Typeid_Bit_Field; break; + case Type_FixedCapacityDynamicArray: kind = Typeid_Fixed_Capacity_Dynamic_Array; break; } return kind; @@ -644,6 +645,21 @@ gb_internal void lb_setup_type_info_data_giant_array(lbModule *m, i64 global_typ variant_value = llvm_const_named_struct(m, tag_type, vals, gb_count_of(vals)); break; } + case Type_FixedCapacityDynamicArray: { + tag_type = t_type_info_fixed_capacity_dynamic_array; + + i64 len_offset = type_offset_of(t, 1); + + LLVMValueRef vals[4] = { + get_type_info_ptr(m, t->FixedCapacityDynamicArray.elem), + lb_const_int(m, t_int, type_size_of(t->FixedCapacityDynamicArray.elem)).value, + lb_const_int(m, t_int, t->FixedCapacityDynamicArray.capacity).value, + lb_const_int(m, t_uintptr, len_offset).value, + }; + + variant_value = llvm_const_named_struct(m, tag_type, vals, gb_count_of(vals)); + break; + } case Type_Slice: { tag_type = t_type_info_slice; diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 8a7bced59..b4ab5e4d6 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -1234,6 +1234,11 @@ gb_internal lbValue lb_emit_struct_ep(lbProcedure *p, lbValue s, i32 index) { case 2: result_type = t_int; break; case 3: result_type = t_allocator; break; } + } else if (is_type_fixed_capacity_dynamic_array(t)) { + switch (index) { + case 0: result_type = alloc_type_array(t->FixedCapacityDynamicArray.elem, t->FixedCapacityDynamicArray.capacity); break; + case 1: result_type = t_int; break; + } } else if (is_type_map(t)) { init_map_internal_debug_types(t); Type *itp = alloc_type_pointer(t_raw_map); @@ -1749,6 +1754,11 @@ gb_internal lbValue lb_dynamic_array_cap(lbProcedure *p, lbValue da) { return lb_emit_struct_ev(p, da, 2); } +gb_internal lbValue lb_fixed_capacity_dynamic_array_len(lbProcedure *p, lbValue da) { + GB_ASSERT(is_type_fixed_capacity_dynamic_array(da.type)); + return lb_emit_struct_ev(p, da, 1); +} + gb_internal lbValue lb_map_len(lbProcedure *p, lbValue value) { GB_ASSERT_MSG(is_type_map(value.type) || are_types_identical(value.type, t_raw_map), "%s", type_to_string(value.type)); lbValue len = lb_emit_struct_ev(p, value, 1); diff --git a/src/name_canonicalization.cpp b/src/name_canonicalization.cpp index efe115c89..6d5f54cf8 100644 --- a/src/name_canonicalization.cpp +++ b/src/name_canonicalization.cpp @@ -788,6 +788,10 @@ gb_internal void write_type_to_canonical_string(TypeWriter *w, Type *type) { type_writer_appendc(w, "[dynamic]"); write_type_to_canonical_string(w, type->DynamicArray.elem); return; + case Type_FixedCapacityDynamicArray: + type_writer_append_fmt(w, "[dynamic;%lld]", cast(long long)type->FixedCapacityDynamicArray.capacity); + write_type_to_canonical_string(w, type->FixedCapacityDynamicArray.elem); + return; case Type_SimdVector: type_writer_append_fmt(w, "#simd[%lld]", cast(long long)type->SimdVector.count); write_type_to_canonical_string(w, type->SimdVector.elem); diff --git a/src/parser.cpp b/src/parser.cpp index ca81159b4..b302c935d 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -456,6 +456,12 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) { break; case Ast_DynamicArrayType: n->DynamicArrayType.elem = clone_ast(n->DynamicArrayType.elem, f); + n->DynamicArrayType.tag = clone_ast(n->DynamicArrayType.tag, f); + break; + case Ast_FixedCapacityDynamicArrayType: + n->FixedCapacityDynamicArrayType.elem = clone_ast(n->FixedCapacityDynamicArrayType.elem, f); + n->FixedCapacityDynamicArrayType.capacity = clone_ast(n->FixedCapacityDynamicArrayType.capacity, f); + n->FixedCapacityDynamicArrayType.tag = clone_ast(n->FixedCapacityDynamicArrayType.tag, f); break; case Ast_StructType: n->StructType.fields = clone_ast_array(n->StructType.fields, f); @@ -1254,6 +1260,14 @@ gb_internal Ast *ast_dynamic_array_type(AstFile *f, Token token, Ast *elem) { return result; } +gb_internal Ast *ast_fixed_capacity_dynamic_array_type(AstFile *f, Token token, Ast *capacity, Ast *elem) { + Ast *result = alloc_ast_node(f, Ast_FixedCapacityDynamicArrayType); + result->FixedCapacityDynamicArrayType.token = token; + result->FixedCapacityDynamicArrayType.capacity = capacity; + result->FixedCapacityDynamicArrayType.elem = elem; + return result; +} + gb_internal Ast *ast_struct_type(AstFile *f, Token token, Slice fields, isize field_count, Ast *polymorphic_params, bool is_packed, bool is_raw_union, bool is_all_or_none, bool is_simple, Ast *align, Ast *min_field_align, Ast *max_field_align, @@ -2470,9 +2484,10 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { Ast *original_type = parse_type(f); Ast *type = unparen_expr(original_type); switch (type->kind) { - case Ast_ArrayType: type->ArrayType.tag = tag; break; - case Ast_DynamicArrayType: type->DynamicArrayType.tag = tag; break; - case Ast_PointerType: type->PointerType.tag = tag; break; + case Ast_ArrayType: type->ArrayType.tag = tag; break; + case Ast_DynamicArrayType: type->DynamicArrayType.tag = tag; break; + case Ast_PointerType: type->PointerType.tag = tag; break; + case Ast_FixedCapacityDynamicArrayType: type->FixedCapacityDynamicArrayType.tag = tag; break; default: syntax_error(type, "Expected an array or pointer type after #%.*s, got %.*s", LIT(name.string), LIT(ast_strings[type->kind])); break; @@ -2702,8 +2717,25 @@ gb_internal Ast *parse_operand(AstFile *f, bool lhs) { } else if (f->curr_token.kind == Token_Question) { count_expr = ast_unary_expr(f, expect_token(f, Token_Question), nullptr); } else if (allow_token(f, Token_dynamic)) { + Ast *capacity = nullptr; + if (f->curr_token.kind == Token_Semicolon && f->curr_token.string == ";") { + expect_token(f, Token_Semicolon); + capacity = parse_expr(f, false); + } else if (allow_token(f, Token_Comma) || allow_token(f, Token_Semicolon)) { + String p = token_to_string(f->prev_token); + syntax_error(token_end_of_line(f, f->prev_token), "Expected a semicolon, got a %.*s", LIT(p)); + + capacity = parse_expr(f, false); + } expect_token(f, Token_CloseBracket); - return ast_dynamic_array_type(f, token, parse_type(f)); + + Ast *elem = parse_type(f); + + if (capacity == nullptr) { + return ast_dynamic_array_type(f, token, elem); + } else { + return ast_fixed_capacity_dynamic_array_type(f, token, capacity, elem); + } } else if (f->curr_token.kind != Token_CloseBracket) { f->expr_level++; count_expr = parse_expr(f, false); @@ -3186,6 +3218,7 @@ gb_internal bool is_literal_type(Ast *node) { case Ast_StructType: case Ast_UnionType: case Ast_EnumType: + case Ast_FixedCapacityDynamicArrayType: case Ast_DynamicArrayType: case Ast_MapType: case Ast_BitSetType: diff --git a/src/parser.hpp b/src/parser.hpp index d3527285d..c68b3614f 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -764,8 +764,14 @@ AST_KIND(_TypeBegin, "", bool) \ }) \ AST_KIND(DynamicArrayType, "dynamic array type", struct { \ Token token; \ - Ast *elem; \ - Ast *tag; \ + Ast *elem; \ + Ast *tag; \ + }) \ + AST_KIND(FixedCapacityDynamicArrayType, "fixed capacity dynamic array type", struct { \ + Token token; \ + Ast *capacity; \ + Ast *elem; \ + Ast *tag; \ }) \ AST_KIND(StructType, "struct type", struct { \ Scope *scope; \ diff --git a/src/types.cpp b/src/types.cpp index b11e95452..418a64b04 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -248,6 +248,12 @@ struct TypeNamed { }) \ TYPE_KIND(Slice, struct { Type *elem; }) \ TYPE_KIND(DynamicArray, struct { Type *elem; }) \ + TYPE_KIND(FixedCapacityDynamicArray, struct { \ + i64 capacity; \ + Type *generic_capacity; \ + Type *elem; \ + i64 padding_needed; /*-1 if unknown*/ \ + }) \ TYPE_KIND(Map, struct { \ Type *key; \ Type *value; \ @@ -378,6 +384,7 @@ enum Typeid_Kind : u8 { Typeid_Matrix, Typeid_SoaPointer, Typeid_Bit_Field, + Typeid_Fixed_Capacity_Dynamic_Array, Typeid__COUNT @@ -698,6 +705,7 @@ gb_global Type *t_type_info_simd_vector = nullptr; gb_global Type *t_type_info_matrix = nullptr; gb_global Type *t_type_info_soa_pointer = nullptr; gb_global Type *t_type_info_bit_field = nullptr; +gb_global Type *t_type_info_fixed_capacity_dynamic_array = nullptr; gb_global Type *t_type_info_named_ptr = nullptr; gb_global Type *t_type_info_integer_ptr = nullptr; @@ -726,6 +734,7 @@ gb_global Type *t_type_info_simd_vector_ptr = nullptr; gb_global Type *t_type_info_matrix_ptr = nullptr; gb_global Type *t_type_info_soa_pointer_ptr = nullptr; gb_global Type *t_type_info_bit_field_ptr = nullptr; +gb_global Type *t_type_info_fixed_capacity_dynamic_array_ptr = nullptr; gb_global Type *t_allocator = nullptr; gb_global Type *t_allocator_ptr = nullptr; @@ -1094,6 +1103,23 @@ gb_internal Type *alloc_type_dynamic_array(Type *elem) { return t; } +gb_internal Type *alloc_type_fixed_capacity_dynamic_array(Type *elem, i64 capacity, Type *generic_capacity = nullptr) { + if (generic_capacity != nullptr) { + Type *t = alloc_type(Type_FixedCapacityDynamicArray); + t->FixedCapacityDynamicArray.elem = elem; + t->FixedCapacityDynamicArray.capacity = capacity; + t->FixedCapacityDynamicArray.generic_capacity = generic_capacity; + t->FixedCapacityDynamicArray.padding_needed = 0; + return t; + } + Type *t = alloc_type(Type_FixedCapacityDynamicArray); + t->FixedCapacityDynamicArray.elem = elem; + t->FixedCapacityDynamicArray.capacity = capacity; + t->FixedCapacityDynamicArray.padding_needed = 0; + return t; +} + + gb_internal Type *alloc_type_struct() { Type *t = alloc_type(Type_Struct); @@ -1658,6 +1684,11 @@ gb_internal bool is_type_dynamic_array(Type *t) { if (t == nullptr) { return false; } return t->kind == Type_DynamicArray; } +gb_internal bool is_type_fixed_capacity_dynamic_array(Type *t) { + t = base_type(t); + if (t == nullptr) { return false; } + return t->kind == Type_FixedCapacityDynamicArray; +} gb_internal bool is_type_slice(Type *t) { t = base_type(t); if (t == nullptr) { return false; } @@ -1687,6 +1718,8 @@ gb_internal Type *base_array_type(Type *t) { return bt->EnumeratedArray.elem; } else if (is_type_simd_vector(bt)) { return bt->SimdVector.elem; + } else if (is_type_fixed_capacity_dynamic_array(bt)) { + return bt->FixedCapacityDynamicArray.elem; } else if (is_type_matrix(bt)) { return bt->Matrix.elem; } @@ -1702,6 +1735,8 @@ gb_internal Type *base_any_array_type(Type *t) { return bt->Slice.elem; } else if (is_type_dynamic_array(bt)) { return bt->DynamicArray.elem; + } else if (is_type_fixed_capacity_dynamic_array(bt)) { + return bt->FixedCapacityDynamicArray.elem; } else if (is_type_enumerated_array(bt)) { return bt->EnumeratedArray.elem; } else if (is_type_simd_vector(bt)) { @@ -2234,6 +2269,7 @@ gb_internal bool is_type_indexable(Type *t) { case Type_Array: case Type_Slice: case Type_DynamicArray: + case Type_FixedCapacityDynamicArray: case Type_Map: return true; case Type_MultiPointer: @@ -2254,6 +2290,7 @@ gb_internal bool is_type_sliceable(Type *t) { case Type_Array: case Type_Slice: case Type_DynamicArray: + case Type_FixedCapacityDynamicArray: return true; case Type_EnumeratedArray: return false; @@ -2394,6 +2431,11 @@ gb_internal bool is_type_polymorphic(Type *t, bool or_specialized=false) { return is_type_polymorphic(t->SimdVector.elem, or_specialized); case Type_DynamicArray: return is_type_polymorphic(t->DynamicArray.elem, or_specialized); + case Type_FixedCapacityDynamicArray: + if (t->FixedCapacityDynamicArray.generic_capacity != nullptr) { + return true; + } + return is_type_polymorphic(t->FixedCapacityDynamicArray.elem, or_specialized); case Type_Slice: return is_type_polymorphic(t->Slice.elem, or_specialized); @@ -2515,6 +2557,11 @@ gb_internal bool type_has_nil(Type *t) { case Type_DynamicArray: case Type_Map: return true; + + case Type_FixedCapacityDynamicArray: + // TODO(bill): should it have `nil`? + return false; + case Type_Union: return t->Union.kind != UnionType_no_nil; case Type_Struct: @@ -2642,6 +2689,9 @@ gb_internal bool is_type_comparable(Type *t) { case Type_Matrix: return is_type_comparable(t->Matrix.elem); + case Type_FixedCapacityDynamicArray: + return false; + case Type_BitSet: return true; @@ -2688,6 +2738,10 @@ gb_internal bool is_type_simple_compare(Type *t) { case Type_EnumeratedArray: return is_type_simple_compare(t->EnumeratedArray.elem); + case Type_FixedCapacityDynamicArray: + return false; + // return is_type_simple_compare(t->FixedCapacityDynamicArray.elem); + case Type_Basic: if (t->Basic.flags & BasicFlag_SimpleCompare) { return true; @@ -2758,6 +2812,10 @@ gb_internal bool is_type_nearly_simple_compare(Type *t) { case Type_EnumeratedArray: return is_type_nearly_simple_compare(t->EnumeratedArray.elem); + case Type_FixedCapacityDynamicArray: + return false; + // return is_type_nearly_simple_compare(t->FixedCapacityDynamicArray.elem); + case Type_Basic: if (t->Basic.flags & (BasicFlag_SimpleCompare|BasicFlag_Numeric)) { return true; @@ -2838,6 +2896,7 @@ gb_internal bool is_type_load_safe(Type *type) { case Type_DynamicArray: case Type_Proc: case Type_SoaPointer: + case Type_FixedCapacityDynamicArray: return false; case Type_Enum: @@ -3047,6 +3106,10 @@ gb_internal bool are_types_identical_internal(Type *x, Type *y, bool check_tuple case Type_DynamicArray: return are_types_identical(x->DynamicArray.elem, y->DynamicArray.elem); + case Type_FixedCapacityDynamicArray: + return (x->FixedCapacityDynamicArray.capacity == y->FixedCapacityDynamicArray.capacity) && + are_types_identical(x->FixedCapacityDynamicArray.elem, y->FixedCapacityDynamicArray.elem); + case Type_Slice: return are_types_identical(x->Slice.elem, y->Slice.elem); @@ -4144,9 +4207,22 @@ gb_internal i64 type_align_of_internal(Type *t, TypePath *path) { } case Type_DynamicArray: - // data, count, capacity, allocator + // data, len, cap, allocator return build_context.int_size; + case Type_FixedCapacityDynamicArray: + // data, len + { + Type *elem = t->FixedCapacityDynamicArray.elem; + bool pop = type_path_push(path, elem); + if (path->failure) { + return FAILURE_ALIGNMENT; + } + i64 align = type_align_of_internal(elem, path); + if (pop) type_path_pop(path); + return gb_max(build_context.int_size, align); + } + case Type_Slice: return build_context.int_size; @@ -4419,6 +4495,34 @@ gb_internal i64 type_size_of_internal(Type *t, TypePath *path) { // data + len + cap + allocator(procedure+data) return 3*build_context.int_size + 2*build_context.ptr_size; + case Type_FixedCapacityDynamicArray: + { + // data + len + i64 capacity = t->FixedCapacityDynamicArray.capacity; + Type *elem = t->FixedCapacityDynamicArray.elem; + i64 align = type_align_of_internal(elem, path); + if (path->failure) { + return FAILURE_SIZE; + } + align = gb_max(build_context.int_size, align); + + i64 size = type_size_of(elem); + size *= capacity; + + i64 old_size = size; + size = align_formula(size, build_context.int_size); + + i64 padding = size - old_size; + if (t->FixedCapacityDynamicArray.padding_needed >= 0) { + GB_ASSERT(t->FixedCapacityDynamicArray.padding_needed == padding); + } + t->FixedCapacityDynamicArray.padding_needed = padding; + + size += 1*build_context.int_size; + size = align_formula(size, align); + return size; + } + case Type_Map: /* struct { @@ -4644,6 +4748,23 @@ gb_internal i64 type_offset_of(Type *t, i64 index, Type **field_type_) { return 3*build_context.int_size; // allocator } break; + + case Type_FixedCapacityDynamicArray: + switch (index) { + case 0: + if (field_type_) *field_type_ = alloc_type_array(t->FixedCapacityDynamicArray.elem, t->FixedCapacityDynamicArray.capacity); + return 0; // data + + case 1: // len + if (field_type_) *field_type_ = t_int; + { + i64 offset = 0; + offset = type_size_of(alloc_type_array(t->FixedCapacityDynamicArray.elem, t->FixedCapacityDynamicArray.capacity)); + offset = align_formula(offset, build_context.int_size); + return offset; + } + } + break; case Type_Union: if (!is_type_union_maybe_pointer(t)) { /* i64 s = */ type_size_of(t); @@ -4711,6 +4832,12 @@ gb_internal i64 type_offset_of_from_selection(Type *type, Selection sel) { case 3: t = t_allocator; break; } break; + case Type_FixedCapacityDynamicArray: + switch (index) { + case 0: t = alloc_type_array(t->FixedCapacityDynamicArray.elem, t->FixedCapacityDynamicArray.capacity); break; + case 1: t = t_int; break; + } + break; } } } @@ -4970,6 +5097,15 @@ gb_internal Type *type_internal_index(Type *t, isize index) { default: GB_PANIC("invalid raw dynamic array index"); }; } + + case Type_FixedCapacityDynamicArray: + { + switch (index) { + case 0: t = alloc_type_array(t->FixedCapacityDynamicArray.elem, t->FixedCapacityDynamicArray.capacity); break; + case 1: return t_int; + default: GB_PANIC("invalid raw fixed capacity dynamic array index"); + }; + } case Type_Struct: return get_struct_field_type(bt, index); case Type_Union: @@ -5068,6 +5204,13 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha str = write_type_to_string(str, type->DynamicArray.elem, shorthand, allow_polymorphic); break; + case Type_FixedCapacityDynamicArray: + str = gb_string_appendc(str, "[dynamic; "); + str = gb_string_appendc(str, gb_bprintf("%lld", cast(long long)type->FixedCapacityDynamicArray.capacity)); + str = gb_string_appendc(str, "]"); + str = write_type_to_string(str, type->FixedCapacityDynamicArray.elem, shorthand, allow_polymorphic); + break; + case Type_Enum: str = gb_string_appendc(str, "enum"); if (type->Enum.base_type != nullptr) { From 4df2de057bd45d9ebd809fe3d34b978869df7a3e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 11 Mar 2026 18:43:01 +0000 Subject: [PATCH 02/27] Add `Type_Info_Fixed_Capacity_Dynamic_Array` to `json` --- core/encoding/json/marshal.odin | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/encoding/json/marshal.odin b/core/encoding/json/marshal.odin index 011fc6f91..a38c6b6d9 100644 --- a/core/encoding/json/marshal.odin +++ b/core/encoding/json/marshal.odin @@ -320,6 +320,16 @@ marshal_to_writer :: proc(w: io.Writer, v: any, opt: ^Marshal_Options) -> (err: } opt_write_end(w, opt, ']') or_return + case runtime.Type_Info_Fixed_Capacity_Dynamic_Array: + opt_write_start(w, opt, '[') or_return + len := (^int)(uintptr(v.data) + info.len_offset)^ + for i in 0.. Date: Thu, 12 Mar 2026 09:37:27 +0000 Subject: [PATCH 03/27] Add other `@builtin` procedures for fixed capacity dynamic arrays --- base/runtime/core_builtin.odin | 317 +++++++++++++++++++++++++++++++-- 1 file changed, 301 insertions(+), 16 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 7a1a38f9a..bcb00f0f9 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -165,28 +165,50 @@ remove_range :: proc(array: ^$D/[dynamic]$T, #any_int lo, hi: int, loc := #calle } -// `pop` will remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. +// `pop_dynamic_array` will remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. // // Note: If the dynamic array has no elements (`len(array) == 0`), this procedure will panic. @builtin -pop :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { +pop_dynamic_array :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { assert(len(array) > 0, loc=loc) - _pop_type_erased(&res, (^Raw_Dynamic_Array)(array), size_of(E)) + _pop_dynamic_array_type_erased(&res, (^Raw_Dynamic_Array)(array), size_of(E)) return res } -_pop_type_erased :: proc(res: rawptr, array: ^Raw_Dynamic_Array, elem_size: int, loc := #caller_location) { +_pop_dynamic_array_type_erased :: proc(res: rawptr, array: ^Raw_Dynamic_Array, elem_size: int) { end := rawptr(uintptr(array.data) + uintptr(elem_size*(array.len-1))) intrinsics.mem_copy_non_overlapping(res, end, elem_size) array.len -= 1 } +// `pop_fixed_capacity_dynamic_array` will remove and return the end value of fixed capacity dynamic array `array` and reduces the length of `array` by 1. +// +// Note: If the fixed capacity dynamic array has no elements (`len(array) == 0`), this procedure will panic. +@builtin +pop_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, loc := #caller_location) -> (res: E) #no_bounds_check { + assert(len(array) > 0, loc=loc) -// `pop_safe` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. + elem_size :: size_of(E) + end := rawptr(uintptr(array) + uintptr(elem_size*(len(array)-1))) + intrinsics.mem_copy_non_overlapping(&res, end, elem_size) + (^Raw_Fixed_Capacity_Dynamic_Array(N, E))(array).len -= 1 +} + + +// `pop` will remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. +// +// Note: If the dynamic array has no elements (`len(array) == 0`), this procedure will panic. +@builtin +pop :: proc{ + pop_dynamic_array, + pop_fixed_capacity_dynamic_array, +} + +// `pop_safe_dynamic_array` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. // If the operation is not possible, it will return false. @builtin -pop_safe :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { +pop_safe_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { if len(array) == 0 { return } @@ -195,11 +217,32 @@ pop_safe :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #n return } -// `pop_front` will remove and return the first value of dynamic array `array` and reduces the length of `array` by 1. +// `pop_safe_fixed_capacity_dynamic_array` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. +// If the operation is not possible, it will return false. +@builtin +pop_safe_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic; $N]$E) -> (res: E, ok: bool) #no_bounds_check { + if len(array) == 0 { + return + } + res, ok = array[len(array)-1], true + (^Raw_Fixed_Capacity_Dynamic_Array(N, E))(array).len -= 1 + return +} + +// `pop_safe` trys to remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. +// If the operation is not possible, it will return false. +@builtin +pop_safe :: proc{ + pop_safe_dynamic_array, + pop_safe_fixed_capacity_dynamic_array, +} + + +// `pop_front_dynamic_array` will remove and return the first value of dynamic array `array` and reduces the length of `array` by 1. // // Note: If the dynamic array as no elements (`len(array) == 0`), this procedure will panic. @builtin -pop_front :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { +pop_front_dynamic_array :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) #no_bounds_check { assert(len(array) > 0, loc=loc) res = array[0] if len(array) > 1 { @@ -209,10 +252,35 @@ pop_front :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (res: E) # return res } -// `pop_front_safe` trys to return and remove the first value of dynamic array `array` and reduces the length of `array` by 1. +// `pop_front_fixed_capacity_dynamic_array` will remove and return the first value of fixed capacity dynamic array `array` and reduces the length of `array` by 1. +// +// Note: If the fixed capacity dynamic array as no elements (`len(array) == 0`), this procedure will panic. +@builtin +pop_front_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, loc := #caller_location) -> (res: E) #no_bounds_check { + assert(len(array) > 0, loc=loc) + res = array[0] + if len(array) > 1 { + copy(array[0:], array[1:]) + } + (^Raw_Fixed_Capacity_Dynamic_Array(N, E))(array).len -= 1 + return res +} + + +// `pop_front` will remove and return the first value of dynamic array `array` and reduces the length of `array` by 1. +// +// Note: If the dynamic array as no elements (`len(array) == 0`), this procedure will panic. +@builtin +pop_front :: proc{ + pop_front_dynamic_array, + pop_front_fixed_capacity_dynamic_array, +} + + +// `pop_front_safe_dynamic_array` trys to return and remove the first value of dynamic array `array` and reduces the length of `array` by 1. // If the operation is not possible, it will return false. @builtin -pop_front_safe :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { +pop_front_safe_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bool) #no_bounds_check { if len(array) == 0 { return } @@ -224,12 +292,37 @@ pop_front_safe :: proc "contextless" (array: ^$T/[dynamic]$E) -> (res: E, ok: bo return } +// `pop_front_safe_fixed_capacity_dynamic_array` trys to return and remove the first value of dynamic array `array` and reduces the length of `array` by 1. +// If the operation is not possible, it will return false. +@builtin +pop_front_safe_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic; $N]$E) -> (res: E, ok: bool) #no_bounds_check { + if len(array) == 0 { + return + } + res, ok = array[0], true + if len(array) > 1 { + copy(array[0:], array[1:]) + } + (^Raw_Fixed_Capacity_Dynamic_Array(N, E))(array).len -= 1 + return +} + +// `pop_front_safe` trys to return and remove the first value of dynamic array `array` and reduces the length of `array` by 1. +// If the operation is not possible, it will return false. +@builtin +pop_front_safe :: proc { + pop_front_safe_dynamic_array, + pop_front_safe_fixed_capacity_dynamic_array, +} + + // `clear` will set the length of a passed dynamic array or map to `0` @builtin clear :: proc{ clear_dynamic_array, clear_map, + clear_fixed_capacity_dynamic_array, clear_soa_dynamic_array, } @@ -671,6 +764,15 @@ non_zero_append_elem_string :: proc(array: ^$T/[dynamic]$E/u8, arg: $A/string, l return _append_elem_string(array, arg, false, loc) } +// `non_zero_append_elem_fixed_capacity_string` appends a string to the end of a dynamic array of bytes, without zeroing any reserved memory +// +// Note: Prefer using the procedure group `non_zero_append`. +@builtin +non_zero_append_elem_fixed_capacity_string :: proc "contextless" (array: ^$T/[dynamic; $N]$E/u8, arg: $A/string) -> (n: int) { + return append_fixed_capacity_elem(array, transmute([]byte)arg) +} + + // The append_string built-in procedure appends multiple strings to the end of a [dynamic]u8 like type // @@ -691,7 +793,7 @@ append_string :: proc(array: ^$T/[dynamic]$E/u8, args: ..string, loc := #caller_ // `append_fixed_capacity_elem` appends an element to the end of a fixed capacity dynamic array. Returns 0 on failure @builtin -append_fixed_capacity_elem :: proc(array: ^$T/[dynamic; $N]$E, #no_broadcast arg: E) -> (n: int) { +append_fixed_capacity_elem :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #no_broadcast arg: E) -> (n: int) { Raw :: Raw_Fixed_Capacity_Dynamic_Array(N, E) if (^Raw)(array).len >= N { @@ -708,7 +810,7 @@ append_fixed_capacity_elem :: proc(array: ^$T/[dynamic; $N]$E, #no_broadcast arg // `append_fixed_capacity_elem` appends an element to the end of a fixed capacity dynamic array. Returns 0 on failure @builtin -append_fixed_capacity_elems :: proc(array: ^$T/[dynamic; $N]$E, #no_broadcast args: ..E) -> (n: int) { +append_fixed_capacity_elems :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #no_broadcast args: ..E) -> (n: int) { Raw :: Raw_Fixed_Capacity_Dynamic_Array(N, E) raw := (^Raw)(array) @@ -724,6 +826,22 @@ append_fixed_capacity_elems :: proc(array: ^$T/[dynamic; $N]$E, #no_broadcast ar return n } +// The append_fixed_capacity_string built-in procedure appends multiple strings to the end of a [dynamic]u8 like type +// +// Note: Prefer using the procedure group `append`. +@builtin +append_fixed_capacity_string :: proc "contextless" (array: ^$T/[dynamic; $N]$E/u8, args: ..string, loc := #caller_location) -> (n: int) { + n_arg: int + for arg in args { + n_arg = append_fixed_capacity_elems(array, ..transmute([]E)(arg), loc=loc) + n += n_arg + if n_arg < len(arg) { + return + } + } + return +} + // The append built-in procedure appends elements to the end of a dynamic array @builtin @@ -734,6 +852,7 @@ append :: proc{ append_fixed_capacity_elem, append_fixed_capacity_elems, + append_fixed_capacity_string, append_soa_elem, append_soa_elems, @@ -746,6 +865,7 @@ non_zero_append :: proc{ non_zero_append_elem_string, append_fixed_capacity_elem, + non_zero_append_elem_fixed_capacity_string, non_zero_append_soa_elem, non_zero_append_soa_elems, @@ -755,7 +875,7 @@ non_zero_append :: proc{ // `append_nothing` appends an empty value to a dynamic array. It returns `1, nil` if successful, and `0, err` when it was not possible, // whatever `err` happens to be. @builtin -append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { +append_nothing_dynamic_array :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: int, err: Allocator_Error) #optional_allocator_error { if array == nil { return 0, nil } @@ -764,6 +884,27 @@ append_nothing :: proc(array: ^$T/[dynamic]$E, loc := #caller_location) -> (n: i return len(array)-prev_len, nil } +// `append_nothing` appends an empty value to a dynamic array. It returns `1, nil` if successful, and `0, err` when it was not possible, +// whatever `err` happens to be. +@builtin +append_nothing_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic; $N]$E) -> (n: int, ok: bool) { + if array == nil { + return 0, true + } + prev_len := len(array) + resize_fixed_capacity_dynamic_array(array, len(array)+1) or_return + return len(array)-prev_len, true +} + + +// `append_nothing` appends an empty value to a dynamic array. It returns `1, nil` if successful, and `0, err` when it was not possible, +// whatever `err` happens to be. +@builtin +append_nothing :: proc{ + append_nothing_dynamic_array, + append_nothing_fixed_capacity_dynamic_array, +} + // `inject_at_elem` injects an element in a dynamic array at a specified index and moves the previous elements after that index "across" @builtin @@ -839,11 +980,87 @@ inject_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, ar return } + +// `inject_at_elem_fixed_capacity_dynamic_array` injects an element in a dynamic array at a specified index and moves the previous elements after that index "across" +@builtin +inject_at_elem_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, #any_int index: int, #no_broadcast arg: E, loc := #caller_location) -> (ok: bool) #no_bounds_check { + when !ODIN_NO_BOUNDS_CHECK { + ensure(index >= 0, "Index must be positive.", loc) + } + if array == nil { + return false + } + n := max(len(array), index) + m :: 1 + new_size := n + m + + resize(array, new_size, loc) or_return + when size_of(E) != 0 { + copy(array[index + m:], array[index:]) + array[index] = arg + } + return true +} + +// `inject_at_elems_fixed_capacity_dynamic_array` injects multiple elements in a dynamic array at a specified index and moves the previous elements after that index "across" +@builtin +inject_at_elems_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, #any_int index: int, #no_broadcast args: ..E, loc := #caller_location) -> (ok: bool) #no_bounds_check { + when !ODIN_NO_BOUNDS_CHECK { + ensure(index >= 0, "Index must be positive.", loc) + } + if array == nil { + return false + } + if len(args) == 0 { + return true + } + + n := max(len(array), index) + m := len(args) + new_size := n + m + + resize(array, new_size, loc) or_return + when size_of(E) != 0 { + copy(array[index + m:], array[index:]) + copy(array[index:], args) + } + return true +} + +// `inject_at_elem_string_fixed_capacity_dynamic_array` injects a string into a dynamic array at a specified index and moves the previous elements after that index "across" +@builtin +inject_at_elem_string_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E/u8, #any_int index: int, arg: string, loc := #caller_location) -> (ok: bool) #no_bounds_check { + when !ODIN_NO_BOUNDS_CHECK { + ensure(index >= 0, "Index must be positive.", loc) + } + if array == nil { + return false + } + if len(arg) == 0 { + return true + } + + n := max(len(array), index) + m := len(arg) + new_size := n + m + + resize(array, new_size, loc) or_return + copy(array[index+m:], array[index:]) + copy(array[index:], arg) + return true +} + + // `inject_at` injects something into a dynamic array at a specified index and moves the previous elements after that index "across" -@builtin inject_at :: proc{ +@builtin +inject_at :: proc{ inject_at_elem, inject_at_elems, inject_at_elem_string, + + inject_at_elem_fixed_capacity_dynamic_array, + inject_at_elems_fixed_capacity_dynamic_array, + inject_at_elem_string_fixed_capacity_dynamic_array, } @@ -900,6 +1117,60 @@ assign_at_elem_string :: proc(array: ^$T/[dynamic]$E/u8, #any_int index: int, ar return } + +// `assign_at_elem_fixed_capacity_dynamic_array` assigns a value at a given index. If the requested index is past the end of the current +// size of the dynamic array, it will attempt to `resize` the a new length of `index+1` and then assign as `index`. +@builtin +assign_at_elem_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #any_int index: int, arg: E) -> (ok: bool) #no_bounds_check { + if index < len(array) { + array[index] = arg + ok = true + } else { + resize(array, index+1, loc) or_return + array[index] = arg + ok = true + } + return +} + + +// `assign_at_elems_fixed_capacity_dynamic_array` assigns a values at a given index. If the requested index is past the end of the current +// size of the dynamic array, it will attempt to `resize` the a new length of `index+len(args)` and then assign as `index`. +@builtin +assign_at_elems_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #any_int index: int, #no_broadcast args: ..E) -> (ok: bool) #no_bounds_check { + new_size := index + len(args) + if len(args) == 0 { + ok = true + } else if new_size < len(array) { + copy(array[index:], args) + ok = true + } else { + resize(array, new_size, loc) or_return + copy(array[index:], args) + ok = true + } + return +} + +// `assign_at_elem_string_fixed_capacity_dynamic_array` assigns a string at a given index. If the requested index is past the end of the current +// size of the dynamic array, it will attempt to `resize` the a new length of `index+len(arg)` and then assign as `index`. +@builtin +assign_at_elem_string_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic; $N]$E/u8, #any_int index: int, arg: string) -> (ok: bool) #no_bounds_check { + new_size := index + len(arg) + if len(arg) == 0 { + ok = true + } else if new_size < len(array) { + copy(array[index:], arg) + ok = true + } else { + resize(array, new_size, loc) or_return + copy(array[index:], arg) + ok = true + } + return +} + + // `assign_at` assigns a value at a given index. If the requested index is past the end of the current // size of the dynamic array, it will attempt to `resize` the a new length of `index+size_needed` and then assign as `index`. @builtin @@ -907,6 +1178,10 @@ assign_at :: proc{ assign_at_elem, assign_at_elems, assign_at_elem_string, + + assign_at_elem_fixed_capacity_dynamic_array, + assign_at_elems_fixed_capacity_dynamic_array, + assign_at_elem_string_fixed_capacity_dynamic_array, } @@ -921,6 +1196,16 @@ clear_dynamic_array :: proc "contextless" (array: ^$T/[dynamic]$E) { } } +// `clear_fixed_capacity_dynamic_array` will set the length of a passed dynamic array to `0` +// +// Note: Prefer the procedure group `clear`. +@builtin +clear_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic; $N]$E) { + if array != nil { + (^Raw_Fixed_Capacity_Dynamic_Array(N, E))(array).len = 0 + } +} + // `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`). @@ -1046,7 +1331,7 @@ non_zero_resize_dynamic_array :: proc(array: ^$T/[dynamic]$E, #any_int length: i // // Note: Prefer the procedure group `resize` @builtin -resize_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, #any_int length: int) -> bool { +resize_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #any_int length: int) -> bool { if array == nil { return false } @@ -1066,7 +1351,7 @@ resize_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, #any_int // // Note: Prefer the procedure group `resize` @builtin -non_zero_resize_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, #any_int length: int) -> bool { +non_zero_resize_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic; $N]$E, #any_int length: int) -> bool { if array == nil { return false } From 6a03cf5d683870317fe0236d552ee4c1bb6ff951 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 Mar 2026 09:40:31 +0000 Subject: [PATCH 04/27] Add "remove" procedures to fixed capacity dynamic arrays --- base/runtime/core_builtin.odin | 77 +++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index bcb00f0f9..0a59167b2 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -119,14 +119,14 @@ copy :: proc{copy_slice, copy_from_string, copy_from_string16} -// `unordered_remove` removed the element at the specified `index`. It does so by replacing the current end value +// `unordered_remove_dynamic_array` removed the element at the specified `index`. It does so by replacing the current end value // with the old value, and reducing the length of the dynamic array by 1. // // Note: This is an O(1) operation. // Note: If you want the elements to remain in their order, use `ordered_remove`. // Note: If the index is out of bounds, this procedure will panic. @builtin -unordered_remove :: proc(array: ^$D/[dynamic]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { +unordered_remove_dynamic_array :: proc(array: ^$D/[dynamic]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) n := len(array)-1 if index != n { @@ -134,13 +134,13 @@ unordered_remove :: proc(array: ^$D/[dynamic]$T, #any_int index: int, loc := #ca } (^Raw_Dynamic_Array)(array).len -= 1 } -// `ordered_remove` removed the element at the specified `index` whilst keeping the order of the other elements. +// `ordered_remove_dynamic_array` removed the element at the specified `index` whilst keeping the order of the other elements. // // Note: This is an O(N) operation. // Note: If the elements do not have to remain in their order, prefer `unordered_remove`. // Note: If the index is out of bounds, this procedure will panic. @builtin -ordered_remove :: proc(array: ^$D/[dynamic]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { +ordered_remove_dynamic_array :: proc(array: ^$D/[dynamic]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) if index+1 < len(array) { copy(array[index:], array[index+1:]) @@ -148,12 +148,12 @@ ordered_remove :: proc(array: ^$D/[dynamic]$T, #any_int index: int, loc := #call (^Raw_Dynamic_Array)(array).len -= 1 } -// `remove_range` removes a range of elements specified by the range `lo` and `hi`, whilst keeping the order of the other elements. +// `remove_range_dynamic_array` removes a range of elements specified by the range `lo` and `hi`, whilst keeping the order of the other elements. // // Note: This is an O(N) operation. // Note: If the range is out of bounds, this procedure will panic. @builtin -remove_range :: proc(array: ^$D/[dynamic]$T, #any_int lo, hi: int, loc := #caller_location) #no_bounds_check { +remove_range_dynamic_array :: proc(array: ^$D/[dynamic]$T, #any_int lo, hi: int, loc := #caller_location) #no_bounds_check { slice_expr_error_lo_hi_loc(loc, lo, hi, len(array)) n := max(hi-lo, 0) if n > 0 { @@ -164,6 +164,71 @@ remove_range :: proc(array: ^$D/[dynamic]$T, #any_int lo, hi: int, loc := #calle } } +// `unordered_remove_fixed_capacity_dynamic_array` removed the element at the specified `index`. It does so by replacing the current end value +// with the old value, and reducing the length of the dynamic array by 1. +// +// Note: This is an O(1) operation. +// Note: If you want the elements to remain in their order, use `ordered_remove`. +// Note: If the index is out of bounds, this procedure will panic. +@builtin +unordered_remove_fixed_capacity_dynamic_array :: proc(array: ^$D/[dynamic; $N]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { + bounds_check_error_loc(loc, index, len(array)) + n := len(array)-1 + if index != n { + array[index] = array[n] + } + (^Raw_Fixed_Capacity_Dynamic_Array(N, E))(array).len -= 1 +} +// `ordered_remove_fixed_capacity_dynamic_array` removed the element at the specified `index` whilst keeping the order of the other elements. +// +// Note: This is an O(N) operation. +// Note: If the elements do not have to remain in their order, prefer `unordered_remove`. +// Note: If the index is out of bounds, this procedure will panic. +@builtin +ordered_remove_fixed_capacity_dynamic_array :: proc(array: ^$D/[dynamic; $N]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { + bounds_check_error_loc(loc, index, len(array)) + if index+1 < len(array) { + copy(array[index:], array[index+1:]) + } + (^Raw_Fixed_Capacity_Dynamic_Array(N, E))(array).len -= 1 +} + +// `remove_range_fixed_capacity_dynamic_array` removes a range of elements specified by the range `lo` and `hi`, whilst keeping the order of the other elements. +// +// Note: This is an O(N) operation. +// Note: If the range is out of bounds, this procedure will panic. +@builtin +remove_range_fixed_capacity_dynamic_array :: proc(array: ^$D/[dynamic; $N]$T, #any_int lo, hi: int, loc := #caller_location) #no_bounds_check { + slice_expr_error_lo_hi_loc(loc, lo, hi, len(array)) + n := max(hi-lo, 0) + if n > 0 { + if hi != len(array) { + copy(array[lo:], array[hi:]) + } + (^Raw_Fixed_Capacity_Dynamic_Array(N, E))(array).len -= n + } +} + +@builtin +unordered_remove :: proc{ + unordered_remove_dynamic_array, + unordered_remove_fixed_capacity_dynamic_array, +} + + +@builtin +ordered_remove :: proc{ + ordered_remove_dynamic_array, + ordered_remove_fixed_capacity_dynamic_array, +} + +@builtin +remove_range :: proc{ + remove_range_dynamic_array, + remove_range_fixed_capacity_dynamic_array, +} + + // `pop_dynamic_array` will remove and return the end value of dynamic array `array` and reduces the length of `array` by 1. // From 8ff07d29be715c5daf6983a336024d3aebef80b2 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 Mar 2026 09:42:46 +0000 Subject: [PATCH 05/27] Fix parser position for fixed capacity dynamic arrays --- src/parser_pos.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/parser_pos.cpp b/src/parser_pos.cpp index 1ffd3a82f..1d2b5090a 100644 --- a/src/parser_pos.cpp +++ b/src/parser_pos.cpp @@ -107,6 +107,7 @@ gb_internal Token ast_token(Ast *node) { case Ast_MultiPointerType: return node->MultiPointerType.token; case Ast_ArrayType: return node->ArrayType.token; case Ast_DynamicArrayType: return node->DynamicArrayType.token; + case Ast_FixedCapacityDynamicArrayType: return node->FixedCapacityDynamicArrayType.token; case Ast_StructType: return node->StructType.token; case Ast_UnionType: return node->UnionType.token; case Ast_EnumType: return node->EnumType.token; @@ -342,6 +343,7 @@ Token ast_end_token(Ast *node) { case Ast_MultiPointerType: return ast_end_token(node->MultiPointerType.type); case Ast_ArrayType: return ast_end_token(node->ArrayType.elem); case Ast_DynamicArrayType: return ast_end_token(node->DynamicArrayType.elem); + case Ast_FixedCapacityDynamicArrayType: return ast_end_token(node->FixedCapacityDynamicArrayType.elem); case Ast_StructType: if (node->StructType.fields.count > 0) { return ast_end_token(node->StructType.fields[node->StructType.fields.count-1]); From a6160770ff550426ad1b475d29a5356a51f6facb Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 Mar 2026 10:03:58 +0000 Subject: [PATCH 06/27] Support compound literals for fixed capacity dynamic arrays --- src/check_expr.cpp | 5 ++ src/llvm_backend_const.cpp | 124 +++++++++++++++++++++++++++++++++++ src/llvm_backend_expr.cpp | 33 ++++++++-- src/llvm_backend_utility.cpp | 15 ++++- src/types.cpp | 7 +- 5 files changed, 171 insertions(+), 13 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 9f66f8fec..39af640d8 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -10321,6 +10321,7 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * case Type_DynamicArray: case Type_SimdVector: case Type_Matrix: + case Type_FixedCapacityDynamicArray: { Type *elem_type = nullptr; String context_name = {}; @@ -10351,6 +10352,10 @@ gb_internal ExprKind check_compound_literal(CheckerContext *c, Operand *o, Ast * elem_type = t->DynamicArray.elem; context_name = str_lit("dynamic array literal"); is_constant = false; + } else if (t->kind == Type_FixedCapacityDynamicArray) { + elem_type = t->FixedCapacityDynamicArray.elem; + context_name = str_lit("fixed capacity dynamic array literal"); + max_type_count = t->FixedCapacityDynamicArray.capacity; } else if (t->kind == Type_SimdVector) { elem_type = t->SimdVector.elem; context_name = str_lit("simd vector literal"); diff --git a/src/llvm_backend_const.cpp b/src/llvm_backend_const.cpp index 9f2052960..289d3daa2 100644 --- a/src/llvm_backend_const.cpp +++ b/src/llvm_backend_const.cpp @@ -694,6 +694,28 @@ gb_internal void lb_const_array_spread(lbModule *m, lbConstContext cc, Type *arr res->value = llvm_const_array(m, lb_type(m, elem), elems, cast(unsigned)count); } +gb_internal LLVMValueRef lb_fill_fixed_capacity_dynamic_array(lbModule *m, i64 elem_count, Type *original_type, LLVMValueRef *values, lbConstContext cc) { + Type *bt = base_type(original_type); + GB_ASSERT(bt->kind == Type_FixedCapacityDynamicArray); + Type *elem_type = bt->FixedCapacityDynamicArray.elem; + i64 capacity = bt->FixedCapacityDynamicArray.capacity; + + Type *array_backing_type = alloc_type_array(elem_type, capacity); + LLVMValueRef array_backing = lb_build_constant_array_values(m, array_backing_type, elem_type, cast(isize)capacity, values, cc); + LLVMValueRef array_len = lb_const_int(m, t_int, elem_count).value; + + isize svalue_count = 0; + LLVMValueRef svalues[3] = {}; + svalues[svalue_count++] = array_backing; + i64 padding = bt->FixedCapacityDynamicArray.padding_needed; + if (padding > 0) { + svalues[svalue_count++] = LLVMConstNull(lb_type_padding_filler(m, padding, 1)); + } + svalues[svalue_count++] = array_len; + + return llvm_const_named_struct(m, original_type, svalues, svalue_count); +} + gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lbConstContext cc, Type *value_type) { if (cc.allow_local) { cc.is_rodata = false; @@ -1554,6 +1576,108 @@ gb_internal lbValue lb_const_value(lbModule *m, Type *type, ExactValue value, lb res.value = lb_build_constant_array_values(m, type, elem_type, cast(isize)type->EnumeratedArray.count, values, cc); return res; } + } else if (is_type_fixed_capacity_dynamic_array(type)) { + ast_node(cl, CompoundLit, value.value_compound); + Type *elem_type = type->FixedCapacityDynamicArray.elem; + i64 capacity = type->FixedCapacityDynamicArray.capacity; + isize elem_count = cl->elems.count; + if (elem_count == 0 || !elem_type_can_be_constant(elem_type)) { + return lb_const_nil(m, original_type); + } + if (cl->elems[0]->kind == Ast_FieldValue) { + // TODO(bill): This is O(N*M) and will be quite slow; it should probably be sorted before hand + LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, cast(isize)capacity); + + i64 max_index = -1; + isize value_index = 0; + for (i64 i = 0; i < capacity; i++) { + bool found = false; + + for (isize j = 0; j < elem_count; j++) { + Ast *elem = cl->elems[j]; + ast_node(fv, FieldValue, elem); + if (is_ast_range(fv->field)) { + ast_node(ie, BinaryExpr, fv->field); + TypeAndValue lo_tav = ie->left->tav; + TypeAndValue hi_tav = ie->right->tav; + GB_ASSERT(lo_tav.mode == Addressing_Constant); + GB_ASSERT(hi_tav.mode == Addressing_Constant); + + TokenKind op = ie->op.kind; + i64 lo = exact_value_to_i64(lo_tav.value); + i64 hi = exact_value_to_i64(hi_tav.value); + if (op != Token_RangeHalf) { + hi += 1; + } + max_index = gb_max(max_index, hi-1); + + if (lo == i) { + TypeAndValue tav = fv->value->tav; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; + for (i64 k = lo; k < hi; k++) { + values[value_index++] = val; + } + + found = true; + i += (hi-lo-1); + break; + } + } else { + TypeAndValue index_tav = fv->field->tav; + GB_ASSERT(index_tav.mode == Addressing_Constant); + i64 index = exact_value_to_i64(index_tav.value); + + max_index = gb_max(max_index, index); + + if (index == i) { + TypeAndValue tav = fv->value->tav; + LLVMValueRef val = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; + values[value_index++] = val; + found = true; + break; + } + } + } + + if (!found) { + values[value_index++] = LLVMConstNull(lb_type(m, elem_type)); + } + } + + i64 count = max_index+1; + GB_ASSERT(0 < count); + GB_ASSERT(count <= capacity); + + res.value = lb_fill_fixed_capacity_dynamic_array(m, count, original_type, values, cc); + return res; + } else if (are_types_identical(value.value_compound->tav.type, elem_type)) { + // Compound is of array item type; expand its value to all items in array. + LLVMValueRef* values = gb_alloc_array(temporary_allocator(), LLVMValueRef, cast(isize)capacity); + + for (isize i = 0; i < capacity; i++) { + values[i] = lb_const_value(m, elem_type, value, cc, elem_type).value; + } + + res.value = lb_fill_fixed_capacity_dynamic_array(m, capacity, original_type, values, cc); + return res; + } else { + // Assume that compound value is an array literal + GB_ASSERT_MSG(elem_count <= capacity, "%td <= %td", elem_count, capacity); + + LLVMValueRef *values = gb_alloc_array(temporary_allocator(), LLVMValueRef, cast(isize)capacity); + + for (isize i = 0; i < elem_count; i++) { + TypeAndValue tav = cl->elems[i]->tav; + GB_ASSERT(tav.mode != Addressing_Invalid); + values[i] = lb_const_value(m, elem_type, tav.value, cc, tav.type).value; + } + for (isize i = elem_count; i < capacity; i++) { + values[i] = LLVMConstNull(lb_type(m, elem_type)); + } + + res.value = lb_fill_fixed_capacity_dynamic_array(m, elem_count, original_type, values, cc); + return res; + } } else if (is_type_simd_vector(type)) { ast_node(cl, CompoundLit, value.value_compound); diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index e352a33e9..af498214e 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4495,6 +4495,7 @@ gb_internal void lb_build_addr_compound_lit_populate(lbProcedure *p, SliceDynamicArray.elem; break; case Type_SimdVector: et = bt->SimdVector.elem; break; case Type_Matrix: et = bt->Matrix.elem; break; + case Type_FixedCapacityDynamicArray: et = bt->FixedCapacityDynamicArray.elem; break; } GB_ASSERT(et != nullptr); @@ -5102,12 +5103,13 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { Type *et = nullptr; switch (bt->kind) { - case Type_Array: et = bt->Array.elem; break; - case Type_EnumeratedArray: et = bt->EnumeratedArray.elem; break; - case Type_Slice: et = bt->Slice.elem; break; - case Type_BitSet: et = bt->BitSet.elem; break; - case Type_SimdVector: et = bt->SimdVector.elem; break; - case Type_Matrix: et = bt->Matrix.elem; break; + case Type_Array: et = bt->Array.elem; break; + case Type_EnumeratedArray: et = bt->EnumeratedArray.elem; break; + case Type_Slice: et = bt->Slice.elem; break; + case Type_BitSet: et = bt->BitSet.elem; break; + case Type_SimdVector: et = bt->SimdVector.elem; break; + case Type_Matrix: et = bt->Matrix.elem; break; + case Type_FixedCapacityDynamicArray: et = bt->FixedCapacityDynamicArray.elem; break; } String proc_name = {}; @@ -5489,6 +5491,25 @@ gb_internal lbAddr lb_build_addr_compound_lit(lbProcedure *p, Ast *expr) { break; } + case Type_FixedCapacityDynamicArray: { + if (cl->elems.count > 0) { + 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); + + lbValue dst_ptr = lb_addr_get_ptr(p, v); + for_array(i, temp_data) { + i32 index = cast(i32)(temp_data[i].elem_index); + temp_data[i].gep = lb_emit_array_epi(p, dst_ptr, index); + } + + lb_build_addr_compound_lit_assign_array(p, temp_data); + } + break; + } + case Type_DynamicArray: { if (cl->elems.count == 0) { break; diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index b4ab5e4d6..349e8c85b 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -1567,16 +1567,27 @@ gb_internal lbValue lb_emit_array_epi(lbProcedure *p, lbValue s, isize index) { Type *t = s.type; GB_ASSERT(is_type_pointer(t)); Type *st = base_type(type_deref(t)); - GB_ASSERT_MSG(is_type_array(st) || is_type_enumerated_array(st) || is_type_matrix(st), "%s", type_to_string(st)); + GB_ASSERT(0 <= index); + if (is_type_fixed_capacity_dynamic_array(st)) { + lbValue data = lb_emit_struct_ep(p, s, 0); + return lb_emit_epi(p, data, index); + } + + GB_ASSERT_MSG(is_type_array(st) || is_type_enumerated_array(st) || is_type_matrix(st), "%s", type_to_string(st)); return lb_emit_epi(p, s, index); } gb_internal lbValue lb_emit_array_epi(lbModule *m, lbValue s, isize index) { Type *t = s.type; GB_ASSERT(is_type_pointer(t)); Type *st = base_type(type_deref(t)); - GB_ASSERT_MSG(is_type_array(st) || is_type_enumerated_array(st) || is_type_matrix(st), "%s", type_to_string(st)); GB_ASSERT(0 <= index); + if (is_type_fixed_capacity_dynamic_array(st)) { + lbValue data = lb_emit_epi(m, s, 0); + return lb_emit_epi(m, data, index); + } + + GB_ASSERT_MSG(is_type_array(st) || is_type_enumerated_array(st) || is_type_matrix(st), "%s", type_to_string(st)); return lb_emit_epi(m, s, index); } diff --git a/src/types.cpp b/src/types.cpp index 418a64b04..73563968d 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -1109,13 +1109,13 @@ gb_internal Type *alloc_type_fixed_capacity_dynamic_array(Type *elem, i64 capaci t->FixedCapacityDynamicArray.elem = elem; t->FixedCapacityDynamicArray.capacity = capacity; t->FixedCapacityDynamicArray.generic_capacity = generic_capacity; - t->FixedCapacityDynamicArray.padding_needed = 0; + t->FixedCapacityDynamicArray.padding_needed = -1; return t; } Type *t = alloc_type(Type_FixedCapacityDynamicArray); t->FixedCapacityDynamicArray.elem = elem; t->FixedCapacityDynamicArray.capacity = capacity; - t->FixedCapacityDynamicArray.padding_needed = 0; + t->FixedCapacityDynamicArray.padding_needed = -1; return t; } @@ -4513,9 +4513,6 @@ gb_internal i64 type_size_of_internal(Type *t, TypePath *path) { size = align_formula(size, build_context.int_size); i64 padding = size - old_size; - if (t->FixedCapacityDynamicArray.padding_needed >= 0) { - GB_ASSERT(t->FixedCapacityDynamicArray.padding_needed == padding); - } t->FixedCapacityDynamicArray.padding_needed = padding; size += 1*build_context.int_size; From bc636e4b368dceb073ea2756a3503378f3ac04aa Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 Mar 2026 12:56:00 +0000 Subject: [PATCH 07/27] raddbg debug view for fixed capacity dynamic arrays --- src/llvm_backend.cpp | 1 + src/llvm_backend_debug.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 931813f42..a4259b01e 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -3583,6 +3583,7 @@ gb_internal bool lb_generate_code(lbGenerator *gen) { lb_add_raddbg_string(m, "type_view: {type: \"[]?\", expr: \"array(data, len)\"}"); lb_add_raddbg_string(m, "type_view: {type: \"string\", expr: \"array(data, len)\"}"); lb_add_raddbg_string(m, "type_view: {type: \"[dynamic]?\", expr: \"rows($, array(data, len), len, cap, allocator)\"}"); + lb_add_raddbg_string(m, "type_view: {type: \"[dynamic;?]?\", expr: \"rows($, array(data, len), len)\"}"); // column major matrices lb_add_raddbg_string(m, "type_view: {type: \"matrix[1, ?]?\", expr: \"columns($.data, $[0])\"}"); diff --git a/src/llvm_backend_debug.cpp b/src/llvm_backend_debug.cpp index 4b09d3d5e..d3e63cbf7 100644 --- a/src/llvm_backend_debug.cpp +++ b/src/llvm_backend_debug.cpp @@ -442,14 +442,14 @@ gb_internal LLVMMetadataRef lb_debug_fixed_capacity_dynamic_array(lbModule *m, T LLVMDIFlagZero, lb_debug_type(m, elem_type) ); - i64 len_offset = type_offset_of(bt, 1); + i64 len_offset_in_bits = 8*type_offset_of(bt, 1); elements[1] = LLVMDIBuilderCreateMemberType( m->debug_builder, member_scope, "len", 3, file, line, int_bits, int_bits, - len_offset, + len_offset_in_bits, LLVMDIFlagZero, lb_debug_type(m, t_int) ); From e485d82c9d0b2cb526a9f94cff6718e0d2ef49f9 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 Mar 2026 13:01:29 +0000 Subject: [PATCH 08/27] cbor support for fixed capacity dynamic arrays --- core/encoding/cbor/marshal.odin | 26 +++++++++++++++++++++++++ core/encoding/cbor/unmarshal.odin | 32 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index b23087c90..366473bcc 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -285,6 +285,32 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er } return + + case runtime.Type_Info_Fixed_Capacity_Dynamic_Array: + if info.elem.id == byte { + raw := (^[dynamic]byte)(v.data) + return err_conv(_encode_bytes(e, raw[:])) + } + + array_len := (^int)(uintptr(v.data) + info.len_offset)^ + array_data := uintptr(v.data) + err_conv(_encode_u64(e, u64(array_len), .Array)) or_return + + if impl, ok := _tag_implementations_type[info.elem.id]; ok { + for i in 0..marshal(e, any{rawptr(data), info.elem.id}) or_return + } + return + } + + elem_ti := runtime.type_info_core(type_info_of(info.elem.id)) + for i in 0.. t.capacity { return _unsupported(v, hdr) } + + // Copy into array type, delete original. + slice := ([^]byte)(v.data)[:len(bytes)] + n := copy(slice, bytes) + assert(n == len(bytes)) + (^int)(uintptr(v.data) + t.len_offset)^ = n + return } return _unsupported(v, hdr) @@ -553,6 +570,21 @@ _unmarshal_array :: proc(d: Decoder, v: any, ti: ^reflect.Type_Info, hdr: Header if out_of_space { return _unsupported(v, hdr) } return + case reflect.Type_Info_Fixed_Capacity_Dynamic_Array: + length, _ := err_conv(_decode_len_container(d, add)) or_return + if length > t.capacity { + return _unsupported(v, hdr) + } + + da := mem.Raw_Dynamic_Array{rawptr(v.data), 0, length, allocator } + + out_of_space := assign_array(d, &da, t.elem, length, growable=false) or_return + if out_of_space { return _unsupported(v, hdr) } + + (^int)(uintptr(v.data) + t.len_offset)^ = length + + return + case reflect.Type_Info_Complex: length, _ := err_conv(_decode_len_container(d, add)) or_return if length > 2 { From 43d8c2bb344be20d9f4849e5e075cf5699f3a077 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 Mar 2026 16:41:24 +0000 Subject: [PATCH 09/27] Add basic tests I know this is not the best place to put them but since `[dynamic; N]T` is meant to a replacement for `small_array.Small_Array(N, T)`, I thought it would be fine for the time being. --- .../test_fixed_capacity_dynamic_array.odin | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tests/core/container/test_fixed_capacity_dynamic_array.odin diff --git a/tests/core/container/test_fixed_capacity_dynamic_array.odin b/tests/core/container/test_fixed_capacity_dynamic_array.odin new file mode 100644 index 000000000..91639fec6 --- /dev/null +++ b/tests/core/container/test_fixed_capacity_dynamic_array.odin @@ -0,0 +1,75 @@ +package test_core_container + +import "core:testing" + +@(test) +test_fixed_capacity_dynamic_array_removes :: proc(t: ^testing.T) { + array: [dynamic; 10]int + append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + + ordered_remove(&array, 0) + testing.expect(t, slice_equal(array[:], []int { 1, 2, 3, 4, 5, 6, 7, 8, 9 })) + ordered_remove(&array, 5) + testing.expect(t, slice_equal(array[:], []int { 1, 2, 3, 4, 5, 7, 8, 9 })) + ordered_remove(&array, 6) + testing.expect(t, slice_equal(array[:], []int { 1, 2, 3, 4, 5, 7, 9 })) + unordered_remove(&array, 0) + testing.expect(t, slice_equal(array[:], []int { 9, 2, 3, 4, 5, 7 })) + unordered_remove(&array, 2) + testing.expect(t, slice_equal(array[:], []int { 9, 2, 7, 4, 5 })) + unordered_remove(&array, 4) + testing.expect(t, slice_equal(array[:], []int { 9, 2, 7, 4 })) +} + +@(test) +test_fixed_capacity_dynamic_array_inject_at :: proc(t: ^testing.T) { + array: [dynamic; 13]int + append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) + + testing.expect(t, inject_at(&array, 0, 0), "Expected to be able to inject into small array") + testing.expect(t, slice_equal(array[:], []int { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })) + testing.expect(t, inject_at(&array, 0, 5), "Expected to be able to inject into small array") + testing.expect(t, slice_equal(array[:], []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9 })) + testing.expect(t, inject_at(&array, 0, len(array)), "Expected to be able to inject into small array") + testing.expect(t, slice_equal(array[:], []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 0 })) +} + +@(test) +test_fixed_capacity_dynamic_array_push_back_elems :: proc(t: ^testing.T) { + array: [dynamic; 2]int + testing.expect(t, slice_equal(array[:], []int { })) + testing.expect(t, append(&array, 0), "Expected to be able to append to empty small array") + testing.expect(t, slice_equal(array[:], []int { 0 })) + testing.expect(t, append(&array, 1, 2) == false, "Expected to fail appending multiple elements beyond capacity of small array") + testing.expect(t, append(&array, 1), "Expected to be able to append to small array") + testing.expect(t, slice_equal(array[:], []int { 0, 1 })) + testing.expect(t, append(&array, 1) == false, "Expected to fail appending to full small array") + testing.expect(t, append(&array, 1, 2) == false, "Expected to fail appending multiple elements to full small array") + clear(&array) + testing.expect(t, slice_equal(array[:], []int { })) + testing.expect(t, append(&array, 1, 2, 3) == false, "Expected to fail appending multiple elements to empty small array") + testing.expect(t, slice_equal(array[:], []int { })) + testing.expect(t, append(&array, 1, 2), "Expected to be able to append multiple elements to empty small array") + testing.expect(t, slice_equal(array[:], []int { 1, 2 })) +} + +@(test) +test_fixed_capacity_dynamic_array_resize :: proc(t: ^testing.T) { + + array: [dynamic; 4]int + + for i in 0..<4 { + append(&array, i+1) + } + testing.expect(t, slice_equal(array[:], []int{1, 2, 3, 4}), "Expected to initialize the array with 1, 2, 3, 4") + + clear(&array) + testing.expect(t, slice_equal(array[:], []int{}), "Expected to clear the array") + + non_zero_resize(&array, 4) + testing.expect(t, slice_equal(array[:], []int{1, 2, 3, 4}), "Expected non_zero_resize to set length 4 with previous values") + + clear(&array) + resize(&array, 4) + testing.expect(t, slice_equal(array[:], []int{0, 0, 0, 0}), "Expected resize to set length 4 with zeroed values") +} From 6898cbe678496a5cb71d3192354ac9a92169f273 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 Mar 2026 16:46:51 +0000 Subject: [PATCH 10/27] Replace usage of `Small_Array(N; T)` with `[dynamic; N]T` in `core:nbio` for posix systems --- core/nbio/impl_posix.odin | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/core/nbio/impl_posix.odin b/core/nbio/impl_posix.odin index 8469c9ade..3845882da 100644 --- a/core/nbio/impl_posix.odin +++ b/core/nbio/impl_posix.odin @@ -11,7 +11,6 @@ import "core:strings" import "core:sys/posix" import "core:time" import kq "core:sys/kqueue" -import sa "core:container/small_array" @(private="package") _FULLY_SUPPORTED :: true @@ -23,7 +22,7 @@ _Event_Loop :: struct { // that would be the same (ident, filter) pair we need to bundle the operations under one kevent. submitted: map[Queue_Identifier]^Operation, // Holds all events we want to flush. Flushing is done each tick at which point this is emptied. - pending: sa.Small_Array(QUEUE_SIZE, kq.KEvent), + pending: [dynamic; QUEUE_SIZE]kq.KEvent, // Holds what should be in `pending` but didn't fit. // When `pending`is flushed these are moved to `pending`. overflow: queue.Queue(kq.KEvent), @@ -116,7 +115,7 @@ _init :: proc(l: ^Event_Loop, allocator: mem.Allocator) -> (rerr: General_Error) l.kqueue = kqueue - sa.append(&l.pending, kq.KEvent{ + append(&l.pending, kq.KEvent{ ident = IDENT_WAKE_UP, filter = .User, flags = {.Add, .Enable, .Clear}, @@ -150,7 +149,7 @@ __tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> General_Error { } if NBIO_DEBUG { - npending := sa.len(l.pending) + npending := len(l.pending) if npending > 0 { debug("queueing", npending, "new events, there are", int(len(l.submitted)), "events pending") } else { @@ -177,9 +176,9 @@ __tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> General_Error { results_buf: [128]kq.KEvent results := kevent(l, results_buf[:], ts_pointer) or_return - sa.clear(&l.pending) + clear(&l.pending) for overflow in queue.pop_front_safe(&l.overflow) { - sa.append(&l.pending, overflow) or_break + (append(&l.pending, overflow) != 0) or_break } l.now = time.now() @@ -202,7 +201,7 @@ __tick :: proc(l: ^Event_Loop, timeout: time.Duration) -> General_Error { kevent :: proc(l: ^Event_Loop, buf: []kq.KEvent, ts: ^posix.timespec) -> ([]kq.KEvent, General_Error) { for { - new_events, err := kq.kevent(l.kqueue, sa.slice(&l.pending), buf, ts) + new_events, err := kq.kevent(l.kqueue, l.pending[:], buf, ts) #partial switch err { case nil: assert(new_events >= 0) @@ -1188,7 +1187,7 @@ add_pending :: proc(op: ^Operation, filter: kq.Filter, ident: uintptr) { } append_pending :: #force_inline proc(l: ^Event_Loop, ev: kq.KEvent) { - if !sa.append(&l.pending, ev) { + if append(&l.pending, ev) == 0 { warn("queue is full, adding to overflow, should QUEUE_SIZE be increased?") _, err := queue.append(&l.overflow, ev) ensure(err == nil, "allocation failure") @@ -1325,7 +1324,7 @@ timeout_and_delete :: proc(target: ^Operation) { flags = {.Add, .Enable, .One_Shot}, udata = target._impl.next, } - if !sa.append(&target.l.pending, ev) { + if append(&target.l.pending, ev) == 0 { warn("just removed the head operation of a list of multiple, and the queue is full, have to force this update through inefficiently") // This has to happen the next time we submit or we could have udata pointing wrong. // Very inefficient but probably never hit. From 6e9d6bfbe5f85a5cc0a9cd7dcd9b865b1d672543 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 Mar 2026 16:55:46 +0000 Subject: [PATCH 11/27] Fixed tests --- base/runtime/core_builtin.odin | 4 ++-- .../test_fixed_capacity_dynamic_array.odin | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 0a59167b2..7fa9ab4c8 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -1404,8 +1404,8 @@ resize_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic; if raw.len < length { size_of_elem :: size_of(E) - num_reused := min(N, length) - a.len - intrinsics.mem_zero(([^]byte)(a.data)[a.len*size_of_elem:], num_reused*size_of_elem) + num_reused := min(N, length) - raw.len + intrinsics.mem_zero(([^]byte)(a.data)[raw.len*size_of_elem:], num_reused*size_of_elem) } new_length := clamp(length, 0, N) raw.len = new_length diff --git a/tests/core/container/test_fixed_capacity_dynamic_array.odin b/tests/core/container/test_fixed_capacity_dynamic_array.odin index 91639fec6..bca4e20f3 100644 --- a/tests/core/container/test_fixed_capacity_dynamic_array.odin +++ b/tests/core/container/test_fixed_capacity_dynamic_array.odin @@ -26,11 +26,11 @@ test_fixed_capacity_dynamic_array_inject_at :: proc(t: ^testing.T) { array: [dynamic; 13]int append(&array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - testing.expect(t, inject_at(&array, 0, 0), "Expected to be able to inject into small array") + testing.expect(t, inject_at(&array, 0, 0), "Expected to be able to inject into fixed capacity dynamic array") testing.expect(t, slice_equal(array[:], []int { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 })) - testing.expect(t, inject_at(&array, 0, 5), "Expected to be able to inject into small array") + testing.expect(t, inject_at(&array, 0, 5), "Expected to be able to inject into fixed capacity dynamic array") testing.expect(t, slice_equal(array[:], []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9 })) - testing.expect(t, inject_at(&array, 0, len(array)), "Expected to be able to inject into small array") + testing.expect(t, inject_at(&array, 0, len(array)), "Expected to be able to inject into fixed capacity dynamic array") testing.expect(t, slice_equal(array[:], []int { 0, 0, 1, 2, 3, 0, 4, 5, 6, 7, 8, 9, 0 })) } @@ -38,18 +38,21 @@ test_fixed_capacity_dynamic_array_inject_at :: proc(t: ^testing.T) { test_fixed_capacity_dynamic_array_push_back_elems :: proc(t: ^testing.T) { array: [dynamic; 2]int testing.expect(t, slice_equal(array[:], []int { })) - testing.expect(t, append(&array, 0), "Expected to be able to append to empty small array") + testing.expect(t, append(&array, 0) == 1, "Expected to be able to append to empty fixed capacity dynamic array") testing.expect(t, slice_equal(array[:], []int { 0 })) - testing.expect(t, append(&array, 1, 2) == false, "Expected to fail appending multiple elements beyond capacity of small array") - testing.expect(t, append(&array, 1), "Expected to be able to append to small array") + testing.expect(t, append(&array, 1) == 1, "Expected to be able to append to fixed capacity dynamic array") testing.expect(t, slice_equal(array[:], []int { 0, 1 })) - testing.expect(t, append(&array, 1) == false, "Expected to fail appending to full small array") - testing.expect(t, append(&array, 1, 2) == false, "Expected to fail appending multiple elements to full small array") + testing.expect(t, append(&array, 1, 2) == 1, "Expected to fail appending multiple elements beyond capacity of fixed capacity dynamic array") + clear(&array) + testing.expect(t, append(&array, 1) == 1, "Expected to be able to append to fixed capacity dynamic array") + testing.expect(t, append(&array, 2) == 1, "Expected to be able to append to fixed capacity dynamic array") + testing.expect(t, append(&array, 1) != 1, "Expected to fail appending to full fixed capacity dynamic array") + testing.expect(t, append(&array, 1, 2) != 2, "Expected to fail appending multiple elements to full fixed capacity dynamic array") clear(&array) testing.expect(t, slice_equal(array[:], []int { })) - testing.expect(t, append(&array, 1, 2, 3) == false, "Expected to fail appending multiple elements to empty small array") + testing.expect(t, append(&array, 1, 2, 3) != 3, "Expected to fail appending multiple elements to empty fixed capacity dynamic array") testing.expect(t, slice_equal(array[:], []int { })) - testing.expect(t, append(&array, 1, 2), "Expected to be able to append multiple elements to empty small array") + testing.expect(t, append(&array, 1, 2) == 2, "Expected to be able to append multiple elements to empty fixed capacity dynamic array") testing.expect(t, slice_equal(array[:], []int { 1, 2 })) } From fac847101983b2f9402ab419937bba51e531cbfc Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 Mar 2026 16:59:11 +0000 Subject: [PATCH 12/27] Fix typos --- base/runtime/core_builtin.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 7fa9ab4c8..268ac6d33 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -171,7 +171,7 @@ remove_range_dynamic_array :: proc(array: ^$D/[dynamic]$T, #any_int lo, hi: int, // Note: If you want the elements to remain in their order, use `ordered_remove`. // Note: If the index is out of bounds, this procedure will panic. @builtin -unordered_remove_fixed_capacity_dynamic_array :: proc(array: ^$D/[dynamic; $N]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { +unordered_remove_fixed_capacity_dynamic_array :: proc(array: ^$D/[dynamic; $N]$E, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) n := len(array)-1 if index != n { @@ -185,7 +185,7 @@ unordered_remove_fixed_capacity_dynamic_array :: proc(array: ^$D/[dynamic; $N]$T // Note: If the elements do not have to remain in their order, prefer `unordered_remove`. // Note: If the index is out of bounds, this procedure will panic. @builtin -ordered_remove_fixed_capacity_dynamic_array :: proc(array: ^$D/[dynamic; $N]$T, #any_int index: int, loc := #caller_location) #no_bounds_check { +ordered_remove_fixed_capacity_dynamic_array :: proc(array: ^$D/[dynamic; $N]$E, #any_int index: int, loc := #caller_location) #no_bounds_check { bounds_check_error_loc(loc, index, len(array)) if index+1 < len(array) { copy(array[index:], array[index+1:]) @@ -198,7 +198,7 @@ ordered_remove_fixed_capacity_dynamic_array :: proc(array: ^$D/[dynamic; $N]$T, // Note: This is an O(N) operation. // Note: If the range is out of bounds, this procedure will panic. @builtin -remove_range_fixed_capacity_dynamic_array :: proc(array: ^$D/[dynamic; $N]$T, #any_int lo, hi: int, loc := #caller_location) #no_bounds_check { +remove_range_fixed_capacity_dynamic_array :: proc(array: ^$D/[dynamic; $N]$E, #any_int lo, hi: int, loc := #caller_location) #no_bounds_check { slice_expr_error_lo_hi_loc(loc, lo, hi, len(array)) n := max(hi-lo, 0) if n > 0 { @@ -1405,7 +1405,7 @@ resize_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic; size_of_elem :: size_of(E) num_reused := min(N, length) - raw.len - intrinsics.mem_zero(([^]byte)(a.data)[raw.len*size_of_elem:], num_reused*size_of_elem) + intrinsics.mem_zero(([^]byte)(raw.data)[raw.len*size_of_elem:], num_reused*size_of_elem) } new_length := clamp(length, 0, N) raw.len = new_length From 6c61b1d46ca4492915876cfbe06ec0ed7b422e24 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 Mar 2026 16:59:58 +0000 Subject: [PATCH 13/27] Remove `loc` being passed --- base/runtime/core_builtin.odin | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 268ac6d33..9bc476d1f 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -1059,7 +1059,7 @@ inject_at_elem_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, m :: 1 new_size := n + m - resize(array, new_size, loc) or_return + resize(array, new_size) or_return when size_of(E) != 0 { copy(array[index + m:], array[index:]) array[index] = arg @@ -1084,7 +1084,7 @@ inject_at_elems_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; $N]$E, m := len(args) new_size := n + m - resize(array, new_size, loc) or_return + resize(array, new_size) or_return when size_of(E) != 0 { copy(array[index + m:], array[index:]) copy(array[index:], args) @@ -1109,7 +1109,7 @@ inject_at_elem_string_fixed_capacity_dynamic_array :: proc(array: ^$T/[dynamic; m := len(arg) new_size := n + m - resize(array, new_size, loc) or_return + resize(array, new_size) or_return copy(array[index+m:], array[index:]) copy(array[index:], arg) return true From 26eb58b589e36689c37217c4c70fa566cf7afa41 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 Mar 2026 17:03:07 +0000 Subject: [PATCH 14/27] Move `raw` closer to usage --- base/runtime/core_builtin.odin | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 9bc476d1f..99705b0c3 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -1400,13 +1400,14 @@ resize_fixed_capacity_dynamic_array :: proc "contextless" (array: ^$T/[dynamic; if array == nil { return false } - raw := (^Raw_Fixed_Capacity_Dynamic_Array(N, E))(array) - if raw.len < length { + if len(array) < length { size_of_elem :: size_of(E) - num_reused := min(N, length) - raw.len - intrinsics.mem_zero(([^]byte)(raw.data)[raw.len*size_of_elem:], num_reused*size_of_elem) + num_reused := min(N, length) - len(array) + intrinsics.mem_zero(([^]byte)(array)[len(array)*size_of_elem:], num_reused*size_of_elem) } + + raw := (^Raw_Fixed_Capacity_Dynamic_Array(N, E))(array) new_length := clamp(length, 0, N) raw.len = new_length return true From c7308d86d4fb5ddd066c1bc97629b2bb44259063 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 Mar 2026 17:33:54 +0000 Subject: [PATCH 15/27] Fix tests as they are not direct matches to `small_array` --- base/runtime/core_builtin.odin | 2 +- .../container/test_fixed_capacity_dynamic_array.odin | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 99705b0c3..ad3420770 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -879,7 +879,7 @@ append_fixed_capacity_elems :: proc "contextless" (array: ^$T/[dynamic; $N]$E, # Raw :: Raw_Fixed_Capacity_Dynamic_Array(N, E) raw := (^Raw)(array) - n = min(N - len(args), len(args)) + n = min(N - len(array), len(args)) when size_of(E) != 0 { for i in 0.. Date: Thu, 12 Mar 2026 17:39:44 +0000 Subject: [PATCH 16/27] Add intrinsics `type_fixed_capacity_dynamic_array_len_offset` and `type_is_fixed_capacity_dynamic_array` --- base/intrinsics/intrinsics.odin | 3 +++ src/check_builtin.cpp | 26 ++++++++++++++++++++++++++ src/checker_builtin_procs.hpp | 6 ++++++ src/types.cpp | 3 ++- 4 files changed, 37 insertions(+), 1 deletion(-) diff --git a/base/intrinsics/intrinsics.odin b/base/intrinsics/intrinsics.odin index f06dbdfbf..a4a761f44 100644 --- a/base/intrinsics/intrinsics.odin +++ b/base/intrinsics/intrinsics.odin @@ -180,6 +180,7 @@ type_is_bit_set :: proc($T: typeid) -> bool --- type_is_bit_field :: proc($T: typeid) -> bool --- type_is_simd_vector :: proc($T: typeid) -> bool --- type_is_matrix :: proc($T: typeid) -> bool --- +type_is_fixed_capacity_dynamic_array :: proc($T: typeid) -> bool --- type_has_nil :: proc($T: typeid) -> bool --- @@ -222,6 +223,8 @@ type_is_superset_of :: proc($Super, $Sub: typeid) -> bool --- type_field_index_of :: proc($T: typeid, $name: string) -> uintptr --- +type_fixed_capacity_dynamic_array_len_offset :: proc($T: typeid/[dynamic; $N]$E) -> uintptr --- + // "Contiguous" means that the set of enum constants, when sorted, have a difference of either 0 or 1 between consecutive values. // This is the exact opposite of "sparse". type_enum_is_contiguous :: proc($T: typeid) -> bool where type_is_enum(T) --- diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 02826a209..186c0bd67 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -58,6 +58,7 @@ gb_global BuiltinTypeIsProc *builtin_type_is_procs[BuiltinProc__type_simple_bool is_type_simd_vector, is_type_matrix, is_type_raw_union, + is_type_fixed_capacity_dynamic_array, is_type_polymorphic_record_specialized, is_type_polymorphic_record_unspecialized, @@ -7628,6 +7629,31 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } break; + case BuiltinProc_type_fixed_capacity_dynamic_array_len_offset: + { + Operand op = {}; + Type *bt = check_type(c, ce->args[0]); + Type *type = base_type(bt); + if (type == nullptr || type == t_invalid) { + error(ce->args[0], "Expected a fixed capacity dynamic array type for '%.*s'", LIT(builtin_name)); + return false; + } + if (!is_type_fixed_capacity_dynamic_array(type)) { + error(ce->args[0], "Expected a fixed capacity dynamic array type for '%.*s'", LIT(builtin_name)); + return false; + } + + i64 sz = type_size_of(type); + gb_unused(sz); + i64 offset = type_offset_of(type, 1); + + operand->mode = Addressing_Constant; + operand->value = exact_value_u64(cast(u64)offset); + operand->type = t_uintptr; + break; + } + break; + case BuiltinProc_type_bit_set_backing_type: { Operand op = {}; diff --git a/src/checker_builtin_procs.hpp b/src/checker_builtin_procs.hpp index a13ffc3cd..107f333fd 100644 --- a/src/checker_builtin_procs.hpp +++ b/src/checker_builtin_procs.hpp @@ -297,6 +297,7 @@ BuiltinProc__type_simple_boolean_begin, BuiltinProc_type_is_simd_vector, BuiltinProc_type_is_matrix, BuiltinProc_type_is_raw_union, + BuiltinProc_type_is_fixed_capacity_dynamic_array, BuiltinProc_type_is_specialized_polymorphic_record, @@ -342,6 +343,8 @@ BuiltinProc__type_simple_boolean_end, BuiltinProc_type_field_index_of, + BuiltinProc_type_fixed_capacity_dynamic_array_len_offset, + BuiltinProc_type_bit_set_backing_type, BuiltinProc_type_enum_is_contiguous, @@ -674,6 +677,7 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("type_is_simd_vector"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_matrix"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_raw_union"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_is_fixed_capacity_dynamic_array"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_specialized_polymorphic_record"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_is_unspecialized_polymorphic_record"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, @@ -717,6 +721,8 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = { {STR_LIT("type_field_index_of"), 2, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_fixed_capacity_dynamic_array_len_offset"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, + {STR_LIT("type_bit_set_backing_type"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics}, {STR_LIT("type_enum_is_contiguous"), 1, false, Expr_Expr, BuiltinProcPkg_intrinsics }, diff --git a/src/types.cpp b/src/types.cpp index 73563968d..1fb274f6a 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -4755,8 +4755,9 @@ gb_internal i64 type_offset_of(Type *t, i64 index, Type **field_type_) { case 1: // len if (field_type_) *field_type_ = t_int; { + i64 elem_size = type_size_of(t->FixedCapacityDynamicArray.elem); i64 offset = 0; - offset = type_size_of(alloc_type_array(t->FixedCapacityDynamicArray.elem, t->FixedCapacityDynamicArray.capacity)); + offset = elem_size * t->FixedCapacityDynamicArray.capacity; offset = align_formula(offset, build_context.int_size); return offset; } From fa72a38036f89b3dae8856868e349c9e72ac347e Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 Mar 2026 17:44:05 +0000 Subject: [PATCH 17/27] Add fixed capacity dynamic array stuff to `core:reflect` --- core/reflect/iterator.odin | 9 +++++++++ core/reflect/reflect.odin | 18 ++++++++++++++++++ core/reflect/types.odin | 9 +++++++++ 3 files changed, 36 insertions(+) diff --git a/core/reflect/iterator.odin b/core/reflect/iterator.odin index e96019f68..d283f190f 100644 --- a/core/reflect/iterator.odin +++ b/core/reflect/iterator.odin @@ -44,6 +44,15 @@ iterate_array :: proc(val: any, it: ^int) -> (elem: any, index: int, ok: bool) { index = it^ it^ += 1 } + case Type_Info_Fixed_Capacity_Dynamic_Array: + count := (^int)(uintptr(val.data) + info.len_offset)^ + if it^ < count { + elem.data = rawptr(uintptr(val.data) + uintptr(it^ * info.elem_size)) + elem.id = info.elem.id + ok = true + index = it^ + it^ += 1 + } } return diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index 924120464..a0cc468f2 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -218,6 +218,7 @@ typeid_elem :: proc(id: typeid) -> typeid { case Type_Info_Slice: return v.elem.id case Type_Info_Dynamic_Array: return v.elem.id case Type_Info_Simd_Vector: return v.elem.id + case Type_Info_Fixed_Capacity_Dynamic_Array: return v.elem.id } return id } @@ -308,6 +309,9 @@ length :: proc(val: any) -> int { case Type_Info_Dynamic_Array: return (^runtime.Raw_Dynamic_Array)(val.data).len + case Type_Info_Fixed_Capacity_Dynamic_Array: + return (^int)(uintptr(val.data) + a.len_offset)^ + case Type_Info_Map: return runtime.map_len((^runtime.Raw_Map)(val.data)^) @@ -360,6 +364,9 @@ capacity :: proc(val: any) -> int { case Type_Info_Dynamic_Array: return (^runtime.Raw_Dynamic_Array)(val.data).cap + case Type_Info_Fixed_Capacity_Dynamic_Array: + return a.capacity + case Type_Info_Map: return runtime.map_cap((^runtime.Raw_Map)(val.data)^) @@ -420,6 +427,13 @@ index :: proc(val: any, i: int, loc := #caller_location) -> any { data := rawptr(uintptr(raw.data) + offset) return any{data, a.elem.id} + case Type_Info_Fixed_Capacity_Dynamic_Array: + count := (^int)(uintptr(val.data) + a.len_offset)^ + runtime.bounds_check_error_loc(loc, i, count) + offset := uintptr(a.elem.size * i) + data := rawptr(uintptr(val.data) + offset) + return any{data, a.elem.id} + case Type_Info_String: if a.is_cstring { return nil } @@ -1776,6 +1790,10 @@ as_raw_data :: proc(a: any) -> (value: rawptr, valid: bool) { valid = true value = (^runtime.Raw_Slice)(a.data).data + case Type_Info_Fixed_Capacity_Dynamic_Array: + valid = true + value = a.data + case Type_Info_Dynamic_Array: valid = true value = (^runtime.Raw_Dynamic_Array)(a.data).data diff --git a/core/reflect/types.odin b/core/reflect/types.odin index ff78708e3..079a4a172 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -433,6 +433,13 @@ is_simd_vector :: proc(info: ^Type_Info) -> bool { _, ok := type_info_base(info).variant.(Type_Info_Simd_Vector) return ok } +// Returns true when the type is a dynamic-array type ([dynamic]T), false otherwise. +@(require_results) +is_fixed_capacity_dynamic_array :: proc(info: ^Type_Info) -> bool { + if info == nil { return false } + _, ok := type_info_base(info).variant.(Type_Info_Fixed_Capacity_Dynamic_Array) + return ok +} // Returns true when the core-type is represented with a platform-native endian type, and returns false otherwise. @@ -840,6 +847,8 @@ has_no_indirections :: proc(ti: ^Type_Info) -> bool { return has_no_indirections(info.elem) case Type_Info_Enumerated_Array: return has_no_indirections(info.elem) + case Type_Info_Fixed_Capacity_Dynamic_Array: + return has_no_indirections(info.elem) case Type_Info_Simd_Vector: return true From 987aa04d6ca99883483fa5a7098c24e6c6606512 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 13 Mar 2026 11:08:50 +0000 Subject: [PATCH 18/27] Minor formatting improvements, and more use of `or_return` --- core/reflect/types.odin | 113 ++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 56 deletions(-) diff --git a/core/reflect/types.odin b/core/reflect/types.odin index 079a4a172..bda2209ff 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -32,36 +32,36 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool { return x.signed == y.signed && x.endianness == y.endianness case Type_Info_Rune: - _, ok := b.variant.(Type_Info_Rune) - return ok + _ = b.variant.(Type_Info_Rune) or_return + return true case Type_Info_Float: - _, ok := b.variant.(Type_Info_Float) - return ok + y := b.variant.(Type_Info_Float) or_return + return x.endianness == y.endianness case Type_Info_Complex: - _, ok := b.variant.(Type_Info_Complex) - return ok + _ = b.variant.(Type_Info_Complex) or_return + return true case Type_Info_Quaternion: - _, ok := b.variant.(Type_Info_Quaternion) - return ok + _ = b.variant.(Type_Info_Quaternion) or_return + return true case Type_Info_Type_Id: - _, ok := b.variant.(Type_Info_Type_Id) - return ok + _ = b.variant.(Type_Info_Type_Id) or_return + return true case Type_Info_String: - _, ok := b.variant.(Type_Info_String) - return ok + y := b.variant.(Type_Info_String) or_return + return x.is_cstring == y.is_cstring && x.encoding == y.encoding case Type_Info_Boolean: - _, ok := b.variant.(Type_Info_Boolean) - return ok + _ = b.variant.(Type_Info_Boolean) or_return + return true case Type_Info_Any: - _, ok := b.variant.(Type_Info_Any) - return ok + _ = b.variant.(Type_Info_Any) or_return + return true case Type_Info_Pointer: y := b.variant.(Type_Info_Pointer) or_return @@ -78,22 +78,19 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool { case Type_Info_Procedure: y := b.variant.(Type_Info_Procedure) or_return - switch { - case x.variadic != y.variadic, - x.convention != y.convention: - return false - } + (x.variadic == y.variadic) or_return + (x.convention == y.convention) or_return return are_types_identical(x.params, y.params) && are_types_identical(x.results, y.results) case Type_Info_Array: y := b.variant.(Type_Info_Array) or_return - if x.count != y.count { return false } + (x.count == y.count) or_return return are_types_identical(x.elem, y.elem) case Type_Info_Enumerated_Array: y := b.variant.(Type_Info_Enumerated_Array) or_return - if x.count != y.count { return false } + (x.count == y.count) or_return return are_types_identical(x.index, y.index) && are_types_identical(x.elem, y.elem) @@ -107,17 +104,15 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool { case Type_Info_Fixed_Capacity_Dynamic_Array: y := b.variant.(Type_Info_Fixed_Capacity_Dynamic_Array) or_return - if x.capacity != y.capacity { return false } + (x.capacity == y.capacity) or_return return are_types_identical(x.elem, y.elem) case Type_Info_Parameters: y := b.variant.(Type_Info_Parameters) or_return - if len(x.types) != len(y.types) { return false } + (len(x.types) == len(y.types)) or_return for _, i in x.types { xt, yt := x.types[i], y.types[i] - if !are_types_identical(xt, yt) { - return false - } + are_types_identical(xt, yt) or_return } return true @@ -136,59 +131,64 @@ are_types_identical :: proc(a, b: ^Type_Info) -> bool { xt, yt := x.types[i], y.types[i] xl, yl := x.tags[i], y.tags[i] - if xn != yn { return false } - if !are_types_identical(xt, yt) { return false } - if xl != yl { return false } + (xn == yn) or_return + are_types_identical(xt, yt) or_return + (xl == yl) or_return } return true case Type_Info_Union: - y := b.variant.(Type_Info_Union) or_return - if len(x.variants) != len(y.variants) { return false } + y := b.variant.(Type_Info_Union) or_return + (len(x.variants) == len(y.variants)) or_return for _, i in x.variants { xv, yv := x.variants[i], y.variants[i] - if !are_types_identical(xv, yv) { return false } + are_types_identical(xv, yv) or_return } return true case Type_Info_Enum: - // NOTE(bill): Should be handled above - return false + y := b.variant.(Type_Info_Enum) or_return + are_types_identical(x.base, y.base) or_return + (len(x.names) == len(y.names)) or_return + + for _, i in x.names { + (x.names[i] == y.names[i]) or_return + (x.values[i] == y.values[i]) or_return + } + return true case Type_Info_Map: y := b.variant.(Type_Info_Map) or_return return are_types_identical(x.key, y.key) && are_types_identical(x.value, y.value) case Type_Info_Bit_Set: - y := b.variant.(Type_Info_Bit_Set) or_return - return x.elem == y.elem && x.lower == y.lower && x.upper == y.upper + y := b.variant.(Type_Info_Bit_Set) or_return + are_types_identical(x.underlying, y.underlying) or_return + are_types_identical(x.elem, y.elem) or_return + + return x.lower == y.lower && x.upper == y.upper case Type_Info_Simd_Vector: y := b.variant.(Type_Info_Simd_Vector) or_return return x.count == y.count && x.elem == y.elem case Type_Info_Matrix: - y := b.variant.(Type_Info_Matrix) or_return - if x.row_count != y.row_count { return false } - if x.column_count != y.column_count { return false } - if x.layout != y.layout { return false } + y := b.variant.(Type_Info_Matrix) or_return + (x.row_count == y.row_count) or_return + (x.column_count == y.column_count) or_return + (x.layout == y.layout) or_return return are_types_identical(x.elem, y.elem) case Type_Info_Bit_Field: - y := b.variant.(Type_Info_Bit_Field) or_return - if !are_types_identical(x.backing_type, y.backing_type) { return false } - if x.field_count != y.field_count { return false } + y := b.variant.(Type_Info_Bit_Field) or_return + are_types_identical(x.backing_type, y.backing_type) or_return + (x.field_count == y.field_count) or_return + for _, i in x.names[:x.field_count] { - if x.names[i] != y.names[i] { - return false - } - if !are_types_identical(x.types[i], y.types[i]) { - return false - } - if x.bit_sizes[i] != y.bit_sizes[i] { - return false - } + (x.names[i] == y.names[i]) or_return + are_types_identical(x.types[i], y.types[i]) or_return + (x.bit_sizes[i] == y.bit_sizes[i]) or_return } return true } @@ -334,7 +334,8 @@ is_soa_pointer :: proc(info: ^Type_Info) -> bool { is_pointer_internally :: proc(info: ^Type_Info) -> bool { if info == nil { return false } #partial switch v in type_info_base(info).variant { - case Type_Info_Pointer, Type_Info_Multi_Pointer, + case Type_Info_Pointer, + Type_Info_Multi_Pointer, Type_Info_Procedure: return true case Type_Info_String: @@ -674,7 +675,7 @@ write_type_writer :: #force_no_inline proc(w: io.Writer, ti: ^Type_Info, n_writt write_type(w, info.elem, &n) or_return case Type_Info_Fixed_Capacity_Dynamic_Array: - io.write_string(w, "[dynamic;", &n) or_return + io.write_string(w, "[dynamic; ", &n) or_return io.write_i64(w, i64(info.capacity), 10, &n) or_return io.write_string(w, "]", &n) or_return write_type(w, info.elem, &n) or_return From b5801ea5c1402894de66dfef1f2d0b6aae4ae947 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 13 Mar 2026 11:10:28 +0000 Subject: [PATCH 19/27] Handle endianness for floats --- core/reflect/types.odin | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/reflect/types.odin b/core/reflect/types.odin index bda2209ff..c6400eb63 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -454,6 +454,8 @@ is_endian_platform :: proc(info: ^Type_Info) -> bool { #partial switch v in info.variant { case Type_Info_Integer: return v.endianness == .Platform + case Type_Info_Float: + return v.endianness == .Platform case Type_Info_Bit_Set: return is_endian_platform(v.underlying) case Type_Info_Pointer: @@ -476,6 +478,11 @@ is_endian_little :: proc(info: ^Type_Info) -> bool { return ODIN_ENDIAN == .Little } return v.endianness == .Little + case Type_Info_Float: + if v.endianness == .Platform { + return ODIN_ENDIAN == .Little + } + return v.endianness == .Little case Type_Info_Bit_Set: return is_endian_little(v.underlying) case Type_Info_Pointer: @@ -498,6 +505,11 @@ is_endian_big :: proc(info: ^Type_Info) -> bool { return ODIN_ENDIAN == .Big } return v.endianness == .Big + case Type_Info_Float: + if v.endianness == .Platform { + return ODIN_ENDIAN == .Big + } + return v.endianness == .Big case Type_Info_Bit_Set: return is_endian_big(v.underlying) case Type_Info_Pointer: From 411e85f02ef642705da80feb084cdca547b58709 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 13 Mar 2026 13:37:34 +0000 Subject: [PATCH 20/27] Fix copy-and-paste doc typo --- core/reflect/types.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/reflect/types.odin b/core/reflect/types.odin index c6400eb63..fa40c1d1b 100644 --- a/core/reflect/types.odin +++ b/core/reflect/types.odin @@ -434,7 +434,7 @@ is_simd_vector :: proc(info: ^Type_Info) -> bool { _, ok := type_info_base(info).variant.(Type_Info_Simd_Vector) return ok } -// Returns true when the type is a dynamic-array type ([dynamic]T), false otherwise. +// Returns true when the type is a fixed-capacity dynamic-array type ([dynamic; N]T), false otherwise. @(require_results) is_fixed_capacity_dynamic_array :: proc(info: ^Type_Info) -> bool { if info == nil { return false } From ee667ec02bcf2ef954f7a3c551977bf42bb2f5cf Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 Mar 2026 16:21:38 +0000 Subject: [PATCH 21/27] Update core/reflect/reflect.odin Co-authored-by: Laytan --- core/reflect/reflect.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/reflect/reflect.odin b/core/reflect/reflect.odin index a0cc468f2..e7115fc96 100644 --- a/core/reflect/reflect.odin +++ b/core/reflect/reflect.odin @@ -1948,7 +1948,7 @@ equal :: proc(a, b: any, including_indirect_array_recursion := false, recursion_ } for i in 0.. Date: Sat, 14 Mar 2026 16:22:01 +0000 Subject: [PATCH 22/27] Update core/encoding/cbor/marshal.odin Co-authored-by: Laytan --- core/encoding/cbor/marshal.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/encoding/cbor/marshal.odin b/core/encoding/cbor/marshal.odin index 366473bcc..b9333bf2b 100644 --- a/core/encoding/cbor/marshal.odin +++ b/core/encoding/cbor/marshal.odin @@ -287,13 +287,13 @@ _marshal_into_encoder :: proc(e: Encoder, v: any, ti: ^runtime.Type_Info) -> (er case runtime.Type_Info_Fixed_Capacity_Dynamic_Array: + array_data := uintptr(v.data) + array_len := (^int)(array_data + info.len_offset)^ if info.elem.id == byte { - raw := (^[dynamic]byte)(v.data) - return err_conv(_encode_bytes(e, raw[:])) + raw := runtime.Raw_Slice{v.data, array_len} + return err_conv(_encode_bytes(e, transmute([]byte)raw)) } - array_len := (^int)(uintptr(v.data) + info.len_offset)^ - array_data := uintptr(v.data) err_conv(_encode_u64(e, u64(array_len), .Array)) or_return if impl, ok := _tag_implementations_type[info.elem.id]; ok { From 59bc428782717f4fc565e4d137df697d2dea961a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 Mar 2026 16:22:12 +0000 Subject: [PATCH 23/27] Update core/odin/ast/ast.odin Co-authored-by: Laytan --- core/odin/ast/ast.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/odin/ast/ast.odin b/core/odin/ast/ast.odin index 8f5f466cb..22139d7f9 100644 --- a/core/odin/ast/ast.odin +++ b/core/odin/ast/ast.odin @@ -787,7 +787,7 @@ Dynamic_Array_Type :: struct { } Fixed_Capacity_Dynamic_Array_Type :: struct { - using node: Expr, + using node: Expr, tag: ^Expr, // possibly nil open: tokenizer.Pos, dynamic_pos: tokenizer.Pos, From 0e6ea3884d4e6f36c981d858f5e9ff6e1484a939 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sat, 14 Mar 2026 16:26:42 +0000 Subject: [PATCH 24/27] General improves --- src/checker.cpp | 4 +++- src/types.cpp | 2 +- .../test_fixed_capacity_dynamic_array.odin | 17 ++++++++++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) rename tests/{core/container => internal}/test_fixed_capacity_dynamic_array.odin (94%) diff --git a/src/checker.cpp b/src/checker.cpp index 2ac5aa62d..cd5842b10 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2278,7 +2278,7 @@ gb_internal void add_type_info_type_internal(CheckerContext *c, Type *t) { case Type_FixedCapacityDynamicArray: add_type_info_type_internal(c, bt->FixedCapacityDynamicArray.elem); - add_type_info_type_internal(c, t_allocator); + add_type_info_type_internal(c, t_int); break; case Type_Enum: @@ -2521,6 +2521,8 @@ gb_internal void add_min_dep_type_info(Checker *c, Type *t) { case Type_FixedCapacityDynamicArray: add_min_dep_type_info(c, bt->FixedCapacityDynamicArray.elem); + add_min_dep_type_info(c, alloc_type_pointer(bt->FixedCapacityDynamicArray.elem)); + add_min_dep_type_info(c, alloc_type_array(bt->FixedCapacityDynamicArray.elem, bt->FixedCapacityDynamicArray.capacity)); add_min_dep_type_info(c, t_int); case Type_Enum: diff --git a/src/types.cpp b/src/types.cpp index 1fb274f6a..2f13ed1f1 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2559,7 +2559,7 @@ gb_internal bool type_has_nil(Type *t) { return true; case Type_FixedCapacityDynamicArray: - // TODO(bill): should it have `nil`? + // it's like a normal array, so no, similar to `#soa[N]T return false; case Type_Union: diff --git a/tests/core/container/test_fixed_capacity_dynamic_array.odin b/tests/internal/test_fixed_capacity_dynamic_array.odin similarity index 94% rename from tests/core/container/test_fixed_capacity_dynamic_array.odin rename to tests/internal/test_fixed_capacity_dynamic_array.odin index 648938e6b..49cf3a8f9 100644 --- a/tests/core/container/test_fixed_capacity_dynamic_array.odin +++ b/tests/internal/test_fixed_capacity_dynamic_array.odin @@ -1,7 +1,22 @@ -package test_core_container +package test_internal import "core:testing" +@(private="file") +slice_equal :: proc(a, b: []int) -> bool { + if len(a) != len(b) { + return false + } + + for a, i in a { + if b[i] != a { + return false + } + } + return true +} + + @(test) test_fixed_capacity_dynamic_array_removes :: proc(t: ^testing.T) { array: [dynamic; 10]int From 310def1e71ef37869f3c41da10bae8cd73beb096 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 15 Mar 2026 11:42:24 +0000 Subject: [PATCH 25/27] Fix `append_fixed_capacity_elems` --- base/runtime/core_builtin.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 80c8f8752..20ce502b0 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -883,7 +883,7 @@ append_fixed_capacity_elems :: proc "contextless" (array: ^$T/[dynamic; $N]$E, # when size_of(E) != 0 { for i in 0.. Date: Sun, 15 Mar 2026 11:48:49 +0000 Subject: [PATCH 26/27] use `intrinsics.mem_copy` instead of a for-loop --- base/runtime/core_builtin.odin | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/base/runtime/core_builtin.odin b/base/runtime/core_builtin.odin index 20ce502b0..022778a24 100644 --- a/base/runtime/core_builtin.odin +++ b/base/runtime/core_builtin.odin @@ -881,10 +881,8 @@ append_fixed_capacity_elems :: proc "contextless" (array: ^$T/[dynamic; $N]$E, # n = min(N - len(array), len(args)) - when size_of(E) != 0 { - for i in 0.. (n: int) { +append_fixed_capacity_string :: proc "contextless" (array: ^$T/[dynamic; $N]$E/u8, args: ..string) -> (n: int) { n_arg: int for arg in args { - n_arg = append_fixed_capacity_elems(array, ..transmute([]E)(arg), loc=loc) + n_arg = append_fixed_capacity_elems(array, ..transmute([]E)(arg)) n += n_arg if n_arg < len(arg) { return From 2f8da5ec677489ac67ae7f56cb979339e0e521c6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Sun, 15 Mar 2026 11:55:04 +0000 Subject: [PATCH 27/27] Add fixed capacity dynamic array to the doc-format --- core/odin/doc-format/doc_format.odin | 113 ++++++++++++++------------- src/docs_format.cpp | 110 +++++++++++++------------- src/docs_writer.cpp | 6 ++ 3 files changed, 120 insertions(+), 109 deletions(-) diff --git a/core/odin/doc-format/doc_format.odin b/core/odin/doc-format/doc_format.odin index df36c1b55..3ae75a3f8 100644 --- a/core/odin/doc-format/doc_format.odin +++ b/core/odin/doc-format/doc_format.odin @@ -12,7 +12,7 @@ String :: distinct Array(byte) Version_Type_Major :: 0 Version_Type_Minor :: 3 -Version_Type_Patch :: 1 +Version_Type_Patch :: 2 Version_Type :: struct { major, minor, patch: u8, @@ -168,32 +168,33 @@ Attribute :: struct { } Type_Kind :: enum u32le { - Invalid = 0, - Basic = 1, - Named = 2, - Generic = 3, - Pointer = 4, - Array = 5, - Enumerated_Array = 6, - Slice = 7, - Dynamic_Array = 8, - Map = 9, - Struct = 10, - Union = 11, - Enum = 12, - Parameters = 13, - Proc = 14, - Bit_Set = 15, - Simd_Vector = 16, - SOA_Struct_Fixed = 17, - SOA_Struct_Slice = 18, - SOA_Struct_Dynamic = 19, - Relative_Pointer = 20, - Relative_Multi_Pointer = 21, - Multi_Pointer = 22, - Matrix = 23, - Soa_Pointer = 24, - Bit_Field = 25, + Invalid = 0, + Basic = 1, + Named = 2, + Generic = 3, + Pointer = 4, + Array = 5, + Enumerated_Array = 6, + Slice = 7, + Dynamic_Array = 8, + Map = 9, + Struct = 10, + Union = 11, + Enum = 12, + Parameters = 13, + Proc = 14, + Bit_Set = 15, + Simd_Vector = 16, + SOA_Struct_Fixed = 17, + SOA_Struct_Slice = 18, + SOA_Struct_Dynamic = 19, + Relative_Pointer = 20, + Relative_Multi_Pointer = 21, + Multi_Pointer = 22, + Matrix = 23, + Soa_Pointer = 24, + Bit_Field = 25, + Fixed_Capacity_Dynamic_Array = 26, } Type_Elems_Cap :: 4 @@ -219,13 +220,14 @@ Type :: struct { custom_align: String, // Used by: - // .Array - 1 count: 0=len - // .Enumerated_Array - 1 count: 0=len - // .SOA_Struct_Fixed - 1 count: 0=len - // .Bit_Set - 2 count: 0=lower, 1=upper - // .Simd_Vector - 1 count: 0=len - // .Matrix - 2 count: 0=row_count, 1=column_count - // .Struct - <=2 count: 0=min_field_align, 1=max_field_align + // .Array - 1 count: 0=len + // .Enumerated_Array - 1 count: 0=len + // .SOA_Struct_Fixed - 1 count: 0=len + // .Bit_Set - 2 count: 0=lower, 1=upper + // .Simd_Vector - 1 count: 0=len + // .Matrix - 2 count: 0=row_count, 1=column_count + // .Struct - <=2 count: 0=min_field_align, 1=max_field_align + // .Fixed_Capacity_Dynamic_Array - 1 count: 0=cap elem_count_len: u32le, elem_counts: [Type_Elems_Cap]i64le, @@ -234,27 +236,28 @@ Type :: struct { calling_convention: String, // Used by: - // .Named - 1 type: 0=base type - // .Generic - <1 type: 0=specialization - // .Pointer - 1 type: 0=element - // .Array - 1 type: 0=element - // .Enumerated_Array - 2 types: 0=index and 1=element - // .Slice - 1 type: 0=element - // .Dynamic_Array - 1 type: 0=element - // .Map - 2 types: 0=key, 1=value - // .SOA_Struct_Fixed - 1 type: underlying SOA struct element - // .SOA_Struct_Slice - 1 type: underlying SOA struct element - // .SOA_Struct_Dynamic - 1 type: underlying SOA struct element - // .Union - 0+ types: variants - // .Enum - <1 type: 0=base type - // .Proc - 2 types: 0=parameters, 1=results - // .Bit_Set - <=2 types: 0=element type, 1=underlying type (Underlying_Type flag will be set) - // .Simd_Vector - 1 type: 0=element - // .Relative_Pointer - 2 types: 0=pointer type, 1=base integer - // .Multi_Pointer - 1 type: 0=element - // .Matrix - 1 type: 0=element - // .Soa_Pointer - 1 type: 0=element - // .Bit_Field - 1 type: 0=backing type + // .Named - 1 type: 0=base type + // .Generic - <1 type: 0=specialization + // .Pointer - 1 type: 0=element + // .Array - 1 type: 0=element + // .Enumerated_Array - 2 types: 0=index and 1=element + // .Slice - 1 type: 0=element + // .Dynamic_Array - 1 type: 0=element + // .Map - 2 types: 0=key, 1=value + // .SOA_Struct_Fixed - 1 type: underlying SOA struct element + // .SOA_Struct_Slice - 1 type: underlying SOA struct element + // .SOA_Struct_Dynamic - 1 type: underlying SOA struct element + // .Union - 0+ types: variants + // .Enum - <1 type: 0=base type + // .Proc - 2 types: 0=parameters, 1=results + // .Bit_Set - <=2 types: 0=element type, 1=underlying type (Underlying_Type flag will be set) + // .Simd_Vector - 1 type: 0=element + // .Relative_Pointer - 2 types: 0=pointer type, 1=base integer + // .Multi_Pointer - 1 type: 0=element + // .Matrix - 1 type: 0=element + // .Soa_Pointer - 1 type: 0=element + // .Bit_Field - 1 type: 0=backing type + // .Fixed_Capacity_Dynamic_Array - 1 type: 0=element types: Array(Type_Index), // Used by: diff --git a/src/docs_format.cpp b/src/docs_format.cpp index 2235789d5..4db85b481 100644 --- a/src/docs_format.cpp +++ b/src/docs_format.cpp @@ -15,7 +15,7 @@ struct OdinDocVersionType { #define OdinDocVersionType_Major 0 #define OdinDocVersionType_Minor 3 -#define OdinDocVersionType_Patch 1 +#define OdinDocVersionType_Patch 2 struct OdinDocHeaderBase { u8 magic[8]; @@ -59,31 +59,31 @@ struct OdinDocPosition { }; enum OdinDocTypeKind : u32 { - OdinDocType_Invalid = 0, - OdinDocType_Basic = 1, - OdinDocType_Named = 2, - OdinDocType_Generic = 3, - OdinDocType_Pointer = 4, - OdinDocType_Array = 5, - OdinDocType_EnumeratedArray = 6, - OdinDocType_Slice = 7, - OdinDocType_DynamicArray = 8, - OdinDocType_Map = 9, - OdinDocType_Struct = 10, - OdinDocType_Union = 11, - OdinDocType_Enum = 12, - OdinDocType_Tuple = 13, - OdinDocType_Proc = 14, - OdinDocType_BitSet = 15, - OdinDocType_SimdVector = 16, - OdinDocType_SOAStructFixed = 17, - OdinDocType_SOAStructSlice = 18, - OdinDocType_SOAStructDynamic = 19, - - OdinDocType_MultiPointer = 22, - OdinDocType_Matrix = 23, - OdinDocType_SoaPointer = 24, - OdinDocType_BitField = 25, + OdinDocType_Invalid = 0, + OdinDocType_Basic = 1, + OdinDocType_Named = 2, + OdinDocType_Generic = 3, + OdinDocType_Pointer = 4, + OdinDocType_Array = 5, + OdinDocType_EnumeratedArray = 6, + OdinDocType_Slice = 7, + OdinDocType_DynamicArray = 8, + OdinDocType_Map = 9, + OdinDocType_Struct = 10, + OdinDocType_Union = 11, + OdinDocType_Enum = 12, + OdinDocType_Tuple = 13, + OdinDocType_Proc = 14, + OdinDocType_BitSet = 15, + OdinDocType_SimdVector = 16, + OdinDocType_SOAStructFixed = 17, + OdinDocType_SOAStructSlice = 18, + OdinDocType_SOAStructDynamic = 19, + OdinDocType_MultiPointer = 22, + OdinDocType_Matrix = 23, + OdinDocType_SoaPointer = 24, + OdinDocType_BitField = 25, + OdinDocType_FixedCapacityDynamicArray = 26, }; enum OdinDocTypeFlag_Basic : u32 { @@ -144,13 +144,14 @@ struct OdinDocType { OdinDocString custom_align; // Used by: - // .Array - 1 count: 0=len - // .Enumerated_Array - 1 count: 0=len - // .SOA_Struct_Fixed - 1 count: 0=len - // .Bit_Set - 2 count: 0=lower, 1=upper - // .Simd_Vector - 1 count: 0=len - // .Matrix - 2 count: 0=row_count, 1=column_count - // .Struct - <=2 count: 0=min_field_align, 1=max_field_align + // .Array - 1 count: 0=len + // .Enumerated_Array - 1 count: 0=len + // .SOA_Struct_Fixed - 1 count: 0=len + // .Bit_Set - 2 count: 0=lower, 1=upper + // .Simd_Vector - 1 count: 0=len + // .Matrix - 2 count: 0=row_count, 1=column_count + // .Struct - <=2 count: 0=min_field_align, 1=max_field_align + // .Fixed_Capacity_Dynamic_Array - 1 count: 0=cap u32 elem_count_len; i64 elem_counts[OdinDocType_ElemsCap]; @@ -159,27 +160,28 @@ struct OdinDocType { OdinDocString calling_convention; // Used by: - // .Named - 1 type: 0=base type - // .Generic - <1 type: 0=specialization - // .Pointer - 1 type: 0=element - // .Array - 1 type: 0=element - // .Enumerated_Array - 2 types: 0=index and 1=element - // .Slice - 1 type: 0=element - // .Dynamic_Array - 1 type: 0=element - // .Map - 2 types: 0=key, 1=value - // .SOA_Struct_Fixed - 1 type: underlying SOA struct element - // .SOA_Struct_Slice - 1 type: underlying SOA struct element - // .SOA_Struct_Dynamic - 1 type: underlying SOA struct element - // .Union - 0+ types: variants - // .Enum - <1 type: 0=base type - // .Proc - 2 types: 0=parameters, 1=results - // .Bit_Set - <=2 types: 0=element type, 1=underlying type (Underlying_Type flag will be set) - // .Simd_Vector - 1 type: 0=element - // .Relative_Pointer - 2 types: 0=pointer type, 1=base integer - // .Multi_Pointer - 1 type: 0=element - // .Matrix - 1 type: 0=element - // .Soa_Pointer - 1 type: 0=element - // .Bit_Field - 1 type: 0=backing type + // .Named - 1 type: 0=base type + // .Generic - <1 type: 0=specialization + // .Pointer - 1 type: 0=element + // .Array - 1 type: 0=element + // .Enumerated_Array - 2 types: 0=index and 1=element + // .Slice - 1 type: 0=element + // .Dynamic_Array - 1 type: 0=element + // .Map - 2 types: 0=key, 1=value + // .SOA_Struct_Fixed - 1 type: underlying SOA struct element + // .SOA_Struct_Slice - 1 type: underlying SOA struct element + // .SOA_Struct_Dynamic - 1 type: underlying SOA struct element + // .Union - 0+ types: variants + // .Enum - <1 type: 0=base type + // .Proc - 2 types: 0=parameters, 1=results + // .Bit_Set - <=2 types: 0=element type, 1=underlying type (Underlying_Type flag will be set) + // .Simd_Vector - 1 type: 0=element + // .Relative_Pointer - 2 types: 0=pointer type, 1=base integer + // .Multi_Pointer - 1 type: 0=element + // .Matrix - 1 type: 0=element + // .Soa_Pointer - 1 type: 0=element + // .Bit_Field - 1 type: 0=backing type + // .Fixed_Capacity_Dynamic_Array - 1 type: 0=element OdinDocArray types; // Used by: diff --git a/src/docs_writer.cpp b/src/docs_writer.cpp index 3edd7da9d..d021b3fc2 100644 --- a/src/docs_writer.cpp +++ b/src/docs_writer.cpp @@ -576,6 +576,12 @@ gb_internal OdinDocTypeIndex odin_doc_type(OdinDocWriter *w, Type *type, bool ca doc_type.kind = OdinDocType_DynamicArray; doc_type.types = odin_doc_type_as_slice(w, type->DynamicArray.elem); break; + case Type_FixedCapacityDynamicArray: + doc_type.kind = OdinDocType_FixedCapacityDynamicArray; + doc_type.elem_count_len = 1; + doc_type.elem_counts[0] = type->FixedCapacityDynamicArray.capacity; + doc_type.types = odin_doc_type_as_slice(w, type->FixedCapacityDynamicArray.elem); + break; case Type_Map: doc_type.kind = OdinDocType_Map; {